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 / player / survival.rs

use common::entity::Entity;
use common::inventory::PlayerInventory;
use common::item::{Item, ItemStack};
use common::math::floor_f64;
use common::protocol::Packet;
use common::world::ChunkPos;
use glam::DVec3;

use super::ServerPlayer;
use crate::entity::{Arrow, Kind, Stuck};
use crate::level::Level;

impl ServerPlayer {
    /// Keeps the armor the entity wears in step with the inventory: whichever side changed
    /// since the last call is copied to the other.
    pub(super) fn reconcile_armor(&mut self, level: &mut Level) {
        let Some(living) = level
            .entities
            .get_mut(self.entity_id)
            .and_then(|e| e.entity.living.as_mut())
        else {
            return;
        };
        if self.inventory.armor != self.armor {
            living.armor = self.inventory.armor;
        } else if living.armor != self.armor {
            self.inventory.armor = living.armor;
            for (old, new) in self.armor.into_iter().zip(self.inventory.armor) {
                if let Some(stack) = old
                    && new.is_none()
                {
                    self.send_stat(common::stats::BREAK + common::stats::item_id(stack.item), 1);
                }
            }
            self.sync_container(level);
        }
        self.armor = self.inventory.armor;
    }

    pub(super) fn tick_riding_stats(&mut self, level: &Level) {
        if let Some(position) = self.entity(level).and_then(|entity| {
            entity
                .riding
                .and_then(|id| level.entities.get(id))
                .filter(|mount| mount.is_minecart())
                .map(|_| entity.pos.floor())
        }) {
            let start = self.minecart_start.get_or_insert(position);
            if position.distance_squared(*start) >= 1_000_000.0 {
                self.send_stat(common::stats::ACHIEVEMENT + 11, 1);
            }
        } else {
            self.minecart_start = None;
        }
    }

    pub fn tick_survival(&mut self, level: &mut Level) {
        self.tick_riding_stats(level);
        self.flush_stats(level);
        self.tick_maps(level);
        self.reconcile_armor(level);
        self.tick_sleep(level);
        let Some(mut stored) = level.entities.take_for_tick(self.entity_id) else {
            return;
        };
        if let Kind::Player { held, .. } = &mut stored.kind {
            *held = self.inventory.held();
        }
        let entity = &mut stored.entity;
        if entity.spawn_immunity > 0 {
            entity.spawn_immunity -= 1;
        }
        if level.difficulty == 0
            && entity.living.as_ref().is_some_and(|l| l.health < 20)
            && entity.ticks_existed % 20 == 0
        {
            entity.heal(1);
        }
        entity.living_entity_update(level);
        entity.collide_blocks(level);
        if !level.rules.allow_nether {
            entity.portal.inside = false;
        }
        let in_portal = entity.portal.inside;
        if entity.portal.tick() {
            entity.portal.cooldown = 10;
            self.departure = Some(super::Departure::Portal);
        }
        entity.damage_received = 0;
        let health = entity.living.as_ref().map_or(0, |l| l.health);
        level.entities.finish_tick(stored);
        if in_portal {
            self.close_container(level);
        }
        self.reconcile_armor(level);
        if health <= 0 && !self.died {
            self.died = true;
            self.send_stat(2022, 1);
            self.mining = None;
            self.close_container(level);
            let inventory = std::mem::take(&mut self.inventory);
            for stack in inventory.main.into_iter().chain(inventory.armor).flatten() {
                self.drop_stack(level, stack);
            }
            self.sync_container(level);
        } else if health > 0 {
            self.collect_items(level);
        }
        if health != self.health_sent {
            self.health_sent = health;
            self.send(Packet::UpdateHealth {
                health: health.max(0) as i16,
            });
        }
    }

    pub(super) fn collect_items(&mut self, level: &mut Level) {
        let Some(player) = self.entity(level) else {
            return;
        };
        let area = player.bb.inflate(DVec3::new(1.0, 0.0, 1.0));
        let [x0, z0] = [area.min.x, area.min.z].map(|v| floor_f64(v) >> 4);
        let [x1, z1] = [area.max.x, area.max.z].map(|v| floor_f64(v) >> 4);
        let entities = &level.entities;
        let candidates: Vec<i32> = (x0..=x1)
            .flat_map(|x| (z0..=z1).flat_map(move |z| entities.in_chunk(ChunkPos::new(x, z))))
            .filter(|e| area.intersects(&e.entity.bb))
            .map(|e| e.entity.id)
            .collect();
        let mut changed = false;
        for id in candidates {
            let Some(stored) = level.entities.get_mut(id) else {
                continue;
            };
            let original = match stored.kind {
                Kind::Item {
                    stack,
                    pickup_delay: 0,
                    ..
                } => stack,
                Kind::Arrow(Arrow {
                    player: true,
                    stuck: Stuck { grounded: true, .. },
                    shake: 0,
                    ..
                }) => ItemStack::one(Item::ARROW),
                _ => continue,
            };
            let remaining = self.inventory.insert(original);
            if remaining == Some(original) {
                continue;
            }
            changed = true;
            let chunk = stored.chunk();
            let rand = &mut stored.entity.rand;
            let pitch = ((rand.next_float() - rand.next_float()) * 0.7 + 1.0) * 2.0;
            level.events.push(Packet::Sound {
                name: "random.pop".into(),
                pos: stored.entity.pos.to_array(),
                volume: 0.2,
                pitch,
            });
            if let Some(stack) = remaining {
                if let Kind::Item { stack: target, .. } = &mut stored.kind {
                    *target = stack;
                }
            } else {
                level.events.push(Packet::Collect {
                    collected: id,
                    collector: self.entity_id,
                });
                level.entities.remove(id);
            }
            level.mark_entity_chunk(chunk);
            self.picked_up(original.item);
        }
        if changed {
            self.sync_container(level);
        }
    }

    pub(super) fn respawn(&mut self, level: &mut Level) {
        if !self.died {
            return;
        }
        if level.dimension() != 0 {
            self.departure = Some(super::Departure::Respawn);
            return;
        }
        let Some(mut stored) = level.entities.take_for_tick(self.entity_id) else {
            return;
        };
        let mut entity = Entity::new_server_player(self.entity_id, level.entities.random());
        let dedicated = level.rules.dedicated_spawn.is_some();
        if !dedicated {
            let spawn = crate::spawn::repair(level, self.spawn);
            if spawn != self.spawn {
                self.spawn = spawn;
                let [x, y, z] = spawn;
                self.send(Packet::SpawnPosition { x, y, z });
            }
        }
        match self.bed_respawn(level) {
            Some(feet) => entity.set_location_and_angles(feet, 0.0, 0.0),
            None if dedicated => crate::spawn::multiplayer(level, &mut entity, self.spawn),
            None => {
                let [x, y, z] = self.spawn;
                let feet = DVec3::new(f64::from(x) + 0.5, f64::from(y + 1), f64::from(z) + 0.5);
                entity.set_location_and_angles(feet, 0.0, 0.0);
            }
        }
        crate::spawn::prepare_player(level, &mut entity);
        if dedicated {
            entity.spawn_immunity = 60;
        }
        let position = entity.pos;
        stored.entity = entity;
        if let Kind::Player { life, .. } = &mut stored.kind {
            *life += 1;
        }
        level.entities.finish_tick(stored);
        self.inventory = PlayerInventory::default();
        self.died = false;
        self.health_sent = i32::MIN;
        self.send(Packet::Respawn {
            dimension: level.dimension(),
        });
        self.teleport(level, position, 0.0, 0.0);
        // `ServerConfigurationManager.sendWorldInfo`
        self.send(Packet::UpdateTime { time: level.time });
        self.send_weather(level, false);
        self.sync_container(level);
    }
}