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 / items.rs

use common::entity::EntityWorld;
use common::item::{Item, ItemStack};
use common::math::floor_f64;
use common::world::shape::BlockView;
use common::world::{Block, Material};
use glam::DVec3;

use super::{Kind, ServerEntity};
use crate::level::Level;

impl Level {
    pub fn spawn_dropped_items(&mut self) {
        let mut drops = std::mem::take(&mut self.dropped_items);
        for drop in drops.drain(..) {
            if drop.stack.count <= 0 {
                continue;
            }
            let mut stored = self.entities.create(Kind::Item {
                stack: drop.stack,
                age: 0,
                pickup_delay: drop.pickup_delay,
                health: 5,
            });
            let entity = &mut stored.entity;
            entity.set_position(DVec3::new(drop.x, drop.y, drop.z));
            // Drawn even when the drop brings its own motion
            let scatter = DVec3::new(
                f64::from(entity.rand.next_float() * 0.2 - 0.1),
                f64::from(0.2_f32),
                f64::from(entity.rand.next_float() * 0.2 - 0.1),
            );
            entity.motion = drop.motion.unwrap_or(scatter) + drop.impulse;
            self.spawn(stored);
        }
        self.dropped_items = drops;
    }

    pub fn spawn_falling(&mut self, x: i32, y: i32, z: i32, block: Block) {
        let mut stored = self.entities.create(Kind::Falling { block, age: 0 });
        let center = DVec3::new(f64::from(x), f64::from(y), f64::from(z)) + 0.5;
        stored.entity.set_position(center);
        self.entities.insert(stored);
        self.set_block_notify(x, y, z, Block::AIR);
    }

    pub fn prime_tnt(&mut self, position: DVec3, chained: bool) {
        let fuse = if chained {
            self.rand.next_int(20) + 10
        } else {
            80
        };
        let mut stored = self.entities.create(Kind::Tnt { fuse });
        let entity = &mut stored.entity;
        entity.set_position(position);
        let angle = (entity.rand.next_double() * f64::from(std::f32::consts::TAU)) as f32;
        entity.motion = DVec3::new(
            f64::from(-common::math::sin(angle.to_radians()) * 0.02),
            f64::from(0.2_f32),
            f64::from(-common::math::cos(angle.to_radians()) * 0.02),
        );
        if !chained {
            self.play_sound(&stored.entity, "random.fuse", 1.0, 1.0);
        }
        self.spawn(stored);
    }

    pub fn tick_entities(&mut self) {
        self.tick_lightning();
        self.spawn_dropped_items();
        let mut ids = std::mem::take(&mut self.entity_tick_buffer);
        self.entities.extend_ids(&mut ids);
        for id in ids.drain(..) {
            if !self.rides_mount(id) {
                self.update_entity(id);
            }
        }
        self.entity_tick_buffer = ids;
        self.unattributed_damage.clear();
    }

    /// Whether the entity rides a mount that updates it, dropping a stale attachment.
    fn rides_mount(&mut self, id: i32) -> bool {
        let Some(mount) = self.entities.get(id).and_then(|e| e.entity.riding) else {
            return false;
        };
        if self
            .entities
            .get(mount)
            .is_some_and(|m| !m.entity.dead && m.entity.rider == Some(id))
        {
            return true;
        }
        if let Some(rider) = self.entities.get_mut(id) {
            rider.entity.riding = None;
        }
        if let Some(mount) = self.entities.get_mut(mount)
            && mount.entity.rider == Some(id)
        {
            mount.entity.rider = None;
        }
        false
    }

    /// `World.updateEntity`: the entity, then its rider.
    fn update_entity(&mut self, id: i32) {
        let Some(mut stored) = self.entities.take_for_tick(id) else {
            return;
        };
        // `WorldServer.updateEntityWithOptionalForce`
        if !self.rules.spawn_animals
            && let Kind::Mob(mob) = &stored.kind
            && !mob.kind.hostile()
        {
            stored.entity.dead = true;
        }
        let old_chunk = stored.chunk();
        let x = floor_f64(stored.entity.pos.x);
        let z = floor_f64(stored.entity.pos.z);
        if self.chunks_exist((x - 16, 0, z - 16), (x + 16, 127, z + 16)) {
            if stored.entity.riding.is_some() {
                self.update_ridden(&mut stored);
            } else {
                self.tick_entity(&mut stored);
            }
            if !matches!(stored.kind, Kind::Player { .. }) {
                self.mark_entity_chunk(old_chunk);
                self.mark_entity_chunk(stored.chunk());
            }
        }
        let rider = stored.entity.rider;
        self.entities.finish_tick(stored);
        for (rider, distance) in std::mem::take(&mut self.rider_falls) {
            if let Some(mut stored) = self.entities.take_for_tick(rider) {
                stored.entity.fall(self, distance);
                self.entities.finish_tick(stored);
            }
        }
        if let Some(rider) = rider {
            if self
                .entities
                .get(rider)
                .is_some_and(|r| !r.entity.dead && r.entity.riding == Some(id))
            {
                self.update_entity(rider);
            } else if let Some(mount) = self.entities.get_mut(id) {
                mount.entity.rider = None;
            }
        }
    }

    /// `Entity.updateRidden`: updates the rider, then seats it on its mount.
    fn update_ridden(&mut self, stored: &mut ServerEntity) {
        let mount = stored.entity.riding.and_then(|id| self.entities.get(id));
        let Some((id, seat, turn)) = mount.filter(|m| !m.entity.dead).map(|m| {
            (
                m.entity.id,
                m.seat_position(),
                [
                    m.entity.yaw - m.entity.prev_yaw,
                    m.entity.pitch - m.entity.prev_pitch,
                ],
            )
        }) else {
            stored.entity.riding = None;
            return;
        };
        stored.entity.motion = DVec3::ZERO;
        self.tick_entity(stored);
        if stored.entity.riding == Some(id) {
            stored.entity.follow_mount(seat, turn);
        }
    }

    /// `Entity.onUpdate` of everything but players, which their connections update.
    fn tick_entity(&mut self, stored: &mut ServerEntity) {
        let entity = &mut stored.entity;
        let x = floor_f64(entity.pos.x);
        let z = floor_f64(entity.pos.z);
        match &mut stored.kind {
            Kind::Player { .. } => {}
            Kind::Mob(mob) => self.tick_mob(entity, mob),
            Kind::Painting { painting, check } => self.tick_painting(entity, *painting, check),
            Kind::Boat(boat) => self.tick_boat(entity, boat),
            Kind::Cart(cart) => self.tick_cart(entity, cart),
            Kind::Hook(hook) => self.tick_hook(entity, hook),
            Kind::Arrow(arrow) => self.tick_arrow(entity, arrow),
            Kind::Fireball(fireball) => self.tick_fireball(entity, fireball),
            Kind::Thrown(thrown) => self.tick_thrown(entity, thrown),
            Kind::Tnt { fuse } => {
                entity.prev_pos = entity.pos;
                entity.motion.y -= f64::from(0.04_f32);
                entity.move_entity(self, entity.motion);
                entity.motion *= f64::from(0.98_f32);
                if entity.on_ground {
                    entity.motion *= DVec3::new(f64::from(0.7_f32), -0.5, f64::from(0.7_f32));
                }
                if *fuse <= 0 {
                    entity.dead = true;
                    self.explode(entity.pos, 4.0, None, false);
                } else {
                    self.spawn_particle("smoke", entity.pos + DVec3::Y * 0.5, DVec3::ZERO);
                }
                *fuse -= 1;
            }
            Kind::Item {
                age,
                pickup_delay,
                health,
                ..
            } => {
                entity.base_update(self);
                *pickup_delay = (*pickup_delay - 1).max(0);
                entity.motion.y -= f64::from(0.04_f32);
                if BlockView::block(self, x, floor_f64(entity.pos.y), z).material()
                    == Material::Lava
                {
                    entity.motion.y = f64::from(0.2_f32);
                    entity.motion.x =
                        f64::from((entity.rand.next_float() - entity.rand.next_float()) * 0.2);
                    entity.motion.z =
                        f64::from((entity.rand.next_float() - entity.rand.next_float()) * 0.2);
                    let pitch = 2.0 + entity.rand.next_float() * 0.4;
                    self.play_sound(entity, "random.fizz", 0.4, pitch);
                }
                entity.push_out_of_cube(
                    self,
                    entity.pos.x,
                    (entity.bb.min.y + entity.bb.max.y) / 2.0,
                    entity.pos.z,
                );
                entity.move_entity(self, entity.motion);
                let friction = if entity.on_ground {
                    BlockView::block(
                        self,
                        floor_f64(entity.pos.x),
                        floor_f64(entity.bb.min.y) - 1,
                        floor_f64(entity.pos.z),
                    )
                    .slipperiness()
                        * 0.98
                } else {
                    0.98
                };
                entity.motion *= DVec3::new(
                    f64::from(friction),
                    f64::from(0.98_f32),
                    f64::from(friction),
                );
                if entity.on_ground {
                    entity.motion.y *= -0.5;
                }
                *health -= std::mem::take(&mut entity.damage_received);
                *age += 1;
                entity.dead |= *age >= 6000 || *health <= 0;
            }
            Kind::Falling { block, age } => {
                entity.prev_pos = entity.pos;
                *age += 1;
                entity.motion.y -= f64::from(0.04_f32);
                entity.move_entity(self, entity.motion);
                entity.motion *= f64::from(0.98_f32);
                let [x, y, z] = entity.pos.to_array().map(floor_f64);
                let landed = entity.on_ground;
                if landed || *age > 100 {
                    entity.dead = true;
                    let cover = |b: Block| b.material().is_ground_cover();
                    if !landed
                        || cover(BlockView::block(self, x, y - 1, z))
                        || !cover(BlockView::block(self, x, y, z))
                        || !self.set_block_notify(x, y, z, *block)
                    {
                        self.mob_drop(entity, ItemStack::one(Item::from_block(*block)));
                    }
                }
            }
        }
    }
}