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