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