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 / vehicles / cart.rs

use common::entity::{
    Entity, EntityWorld,
    vehicle::{RAIL_ENDPOINTS, VehicleKind, VehiclePose, rail_at, rail_position},
};
use common::item::{Item, ItemStack};
use common::math::floor_f64;
use common::nbt::{Compound, Tag};
use common::world::{Block, shape::BlockView};
use glam::DVec3;

use crate::entity::Kind;
use crate::level::{DroppedItem, Level};

#[derive(Clone, Debug)]
pub struct Cart {
    pub kind: VehicleKind,
    pub items: Vec<Option<ItemStack>>,
    pub fuel: i32,
    pub push: DVec3,
    pub pose: VehiclePose,
    reversed: bool,
}

impl Cart {
    pub fn new(kind: VehicleKind) -> Self {
        Self {
            kind,
            items: if kind == VehicleKind::StorageCart {
                vec![None; 27]
            } else {
                Vec::new()
            },
            fuel: 0,
            push: DVec3::ZERO,
            pose: VehiclePose {
                direction: 1,
                ..Default::default()
            },
            reversed: false,
        }
    }
    pub fn read(nbt: &Compound) -> Self {
        let mut cart = Self::new(match nbt.int("Type") {
            1 => VehicleKind::StorageCart,
            2 => VehicleKind::PoweredCart,
            _ => VehicleKind::Minecart,
        });
        if cart.kind == VehicleKind::PoweredCart {
            cart.fuel = i32::from(nbt.short("Fuel"));
            cart.push = DVec3::new(nbt.double("PushX"), 0.0, nbt.double("PushZ"));
            if !cart.push.is_finite() {
                cart.push = DVec3::ZERO;
            }
        } else if cart.kind == VehicleKind::StorageCart {
            for tag in nbt.list("Items") {
                if let Tag::Compound(item) = tag {
                    let slot = item.byte("Slot") as u8 as usize;
                    if slot < cart.items.len() {
                        let stack = ItemStack::read_nbt(item);
                        cart.items[slot] = (stack.count > 0).then_some(stack);
                    }
                }
            }
        }
        cart
    }
    pub fn write(&self, nbt: &mut Compound) {
        nbt.set_int(
            "Type",
            match self.kind {
                VehicleKind::StorageCart => 1,
                VehicleKind::PoweredCart => 2,
                _ => 0,
            },
        );
        if self.kind == VehicleKind::PoweredCart {
            nbt.set_double("PushX", self.push.x);
            nbt.set_double("PushZ", self.push.z);
            nbt.set_short("Fuel", self.fuel as i16);
        } else if self.kind == VehicleKind::StorageCart {
            let mut items: Vec<_> = nbt
                .list("Items")
                .iter()
                .filter(|tag| matches!(tag, Tag::Compound(item) if item.byte("Slot") as u8 >= 27))
                .cloned()
                .collect();
            for (slot, stack) in self.items.iter().enumerate() {
                if let Some(stack) = stack {
                    let mut item = Compound::default();
                    stack.write_nbt(&mut item);
                    item.set_byte("Slot", slot as i8);
                    items.push(Tag::Compound(item));
                }
            }
            nbt.set_list("Items", items);
        }
    }
}

impl Level {
    pub fn spawn_cart(&mut self, at: DVec3, kind: VehicleKind) {
        let mut stored = self.entities.create(Kind::Cart(Cart::new(kind)));
        let entity = &mut stored.entity;
        entity.set_position(at + DVec3::Y * f64::from(entity.y_offset));
        self.spawn(stored);
    }

    pub(crate) fn hurt_cart(&mut self, entity: &mut Entity, cart: &mut Cart, amount: i32) -> bool {
        cart.pose.direction = -cart.pose.direction;
        cart.pose.hit = 10;
        cart.pose.damage += amount * 10;
        if cart.pose.damage > 40 && !entity.dead {
            self.mob_drop(entity, ItemStack::one(Item::MINECART));
            match cart.kind {
                VehicleKind::StorageCart => {
                    self.mob_drop(entity, ItemStack::one(Item::from_block(Block::CHEST)))
                }
                VehicleKind::PoweredCart => {
                    self.mob_drop(entity, ItemStack::one(Item::from_block(Block::FURNACE)))
                }
                _ => {}
            }
            self.break_cart(entity, cart);
        }
        true
    }

    fn break_cart(&mut self, entity: &mut Entity, cart: &mut Cart) {
        entity.dead = true;
        for slot in &mut cart.items {
            let Some(mut stack) = slot.take() else {
                continue;
            };
            let offset = DVec3::from_array(std::array::from_fn(|_| {
                f64::from(entity.rand.next_float() * 0.8 + 0.1)
            }));
            while stack.count > 0 {
                let count = (entity.rand.next_int(21) + 10).min(stack.count);
                stack.count -= count;
                self.dropped_items.push(DroppedItem {
                    x: entity.pos.x + offset.x,
                    y: entity.pos.y + offset.y,
                    z: entity.pos.z + offset.z,
                    stack: ItemStack::new(stack.item, count, stack.damage),
                    motion: Some(
                        DVec3::from_array(std::array::from_fn(|_| {
                            entity.rand.next_gaussian() * f64::from(0.05_f32)
                        })) + DVec3::Y * f64::from(0.2_f32),
                    ),
                    impulse: glam::DVec3::ZERO,
                    pickup_delay: 10,
                });
            }
        }
        self.eject_rider(entity);
    }

    pub(crate) fn tick_cart(&mut self, entity: &mut Entity, cart: &mut Cart) {
        entity.prev_pos = entity.pos;
        entity.prev_yaw = entity.yaw;
        entity.prev_pitch = entity.pitch;
        entity.ticks_existed += 1;
        cart.pose.hit = (cart.pose.hit - 1).max(0);
        cart.pose.damage = (cart.pose.damage - 1).max(0);
        if entity.pos.y < -64.0 {
            self.break_cart(entity, cart);
            return;
        }
        entity.motion.y -= f64::from(0.04_f32);
        let mut powered = false;
        if let Some(([x, y, z], shape)) = rail_at(self, entity.pos) {
            let old = rail_position(self, entity.pos);
            let block = BlockView::block(self, x, y, z);
            let active =
                block == Block::POWERED_RAIL && BlockView::metadata(self, x, y, z) & 8 != 0;
            let brake = block == Block::POWERED_RAIL && !active;
            let mut rail_y = f64::from(y + i32::from((2..=5).contains(&shape)));
            match shape {
                2 => entity.motion.x -= 1.0 / 128.0,
                3 => entity.motion.x += 1.0 / 128.0,
                4 => entity.motion.z += 1.0 / 128.0,
                5 => entity.motion.z -= 1.0 / 128.0,
                _ => {}
            }
            let ends = RAIL_ENDPOINTS[usize::from(shape)];
            let mut direction =
                DVec3::from_array(std::array::from_fn(|i| f64::from(ends[1][i] - ends[0][i])))
                    .with_y(0.0);
            if entity.motion.dot(direction) < 0.0 {
                direction = -direction;
            }
            let speed = horizontal(entity.motion).length();
            entity.motion = direction.normalize() * speed;
            if brake {
                entity.motion = if speed < 0.03 {
                    DVec3::ZERO
                } else {
                    entity.motion * 0.5
                };
            }
            if let Some(snap) = old {
                entity.set_position(snap.with_y(rail_y + f64::from(entity.y_offset)));
            }
            let motion = entity.motion * if entity.rider.is_some() { 0.75 } else { 1.0 };
            entity.move_entity(
                self,
                motion
                    .clamp(DVec3::splat(-0.4), DVec3::splat(0.4))
                    .with_y(0.0),
            );
            for end in ends {
                if end[1] != 0
                    && floor_f64(entity.pos.x) - x == end[0]
                    && floor_f64(entity.pos.z) - z == end[2]
                {
                    rail_y = entity.pos.y + f64::from(end[1]);
                    entity.set_position(entity.pos.with_y(rail_y));
                }
            }
            if entity.rider.is_some() {
                entity.motion *= f64::from(0.997_f32);
            } else {
                if cart.kind == VehicleKind::PoweredCart {
                    let length = f64::from(horizontal(cart.push).length() as f32);
                    if length > 0.01 {
                        cart.push /= length;
                        powered = true;
                        entity.motion = entity.motion * f64::from(0.8_f32) + cart.push * 0.04;
                    } else {
                        entity.motion *= f64::from(0.9_f32);
                    }
                }
                entity.motion *= f64::from(0.96_f32);
            }
            entity.motion.y = 0.0;
            if let (Some(old), Some(new)) = (old, rail_position(self, entity.pos)) {
                let speed = horizontal(entity.motion).length();
                if speed > 0.0 {
                    entity.motion *= (speed + (old.y - new.y) * 0.05) / speed;
                }
                entity.set_position(entity.pos.with_y(new.y));
            }
            let nx = floor_f64(entity.pos.x);
            let nz = floor_f64(entity.pos.z);
            if nx != x || nz != z {
                let speed = horizontal(entity.motion).length();
                entity.motion =
                    DVec3::new(f64::from(nx - x) * speed, 0.0, f64::from(nz - z) * speed);
            }
            if cart.kind == VehicleKind::PoweredCart {
                let length = f64::from(horizontal(cart.push).length() as f32);
                if length > 0.01 && horizontal(entity.motion).length_squared() > 0.001 {
                    cart.push /= length;
                    cart.push = if cart.push.dot(entity.motion) < 0.0 {
                        DVec3::ZERO
                    } else {
                        entity.motion
                    };
                }
            }
            if active {
                let speed = horizontal(entity.motion).length();
                if speed > 0.01 {
                    entity.motion += horizontal(entity.motion) / speed * 0.06;
                } else if shape == 1 {
                    if normal_cube(self, x - 1, y, z) {
                        entity.motion.x = 0.02;
                    } else if normal_cube(self, x + 1, y, z) {
                        entity.motion.x = -0.02;
                    }
                } else if shape == 0 {
                    if normal_cube(self, x, y, z - 1) {
                        entity.motion.z = 0.02;
                    } else if normal_cube(self, x, y, z + 1) {
                        entity.motion.z = -0.02;
                    }
                }
            }
        } else {
            entity.motion.x = entity.motion.x.clamp(-0.4, 0.4);
            entity.motion.z = entity.motion.z.clamp(-0.4, 0.4);
            if entity.on_ground {
                entity.motion *= 0.5;
            }
            entity.move_entity(self, entity.motion);
            if !entity.on_ground {
                entity.motion *= f64::from(0.95_f32);
            }
        }
        entity.pitch = 0.0;
        let delta = entity.prev_pos - entity.pos;
        if horizontal(delta).length_squared() > 0.001 {
            entity.yaw = (delta.z.atan2(delta.x) * 180.0 / std::f64::consts::PI) as f32;
            if cart.reversed {
                entity.yaw += 180.0;
            }
        }
        let turn = (entity.yaw - entity.prev_yaw + 180.0).rem_euclid(360.0) - 180.0;
        if !(-170.0..170.0).contains(&turn) {
            entity.yaw += 180.0;
            cart.reversed = !cart.reversed;
        }
        // Fire and cactus touched while moving hurt it like a hit
        let damage = std::mem::take(&mut entity.damage_received);
        if damage > 0 {
            self.hurt_cart(entity, cart, damage);
        }
        self.cart_collisions(entity, cart.kind);
        if powered && entity.rand.next_int(4) == 0 {
            cart.fuel -= 1;
            if cart.fuel < 0 {
                cart.push = DVec3::ZERO;
            }
            self.spawn_particle("largesmoke", entity.pos + DVec3::Y * 0.8, DVec3::ZERO);
        }
    }

    fn cart_collisions(&mut self, entity: &mut Entity, kind: VehicleKind) {
        let area = entity
            .bb
            .inflate(DVec3::new(f64::from(0.2_f32), 0.0, f64::from(0.2_f32)));
        let ids: Vec<i32> = self
            .entities
            .near(&area)
            .filter(|other| {
                !other.entity.dead
                    && Some(other.entity.id) != entity.rider
                    && other.entity.bb.intersects(&area)
                    && (other.entity.living.is_some() || matches!(other.kind, Kind::Cart(_)))
            })
            .map(|e| e.entity.id)
            .collect();
        for id in ids {
            let Some(other) = self.entities.get_mut(id) else {
                continue;
            };
            if kind == VehicleKind::Minecart
                && entity.rider.is_none()
                && other.entity.riding.is_none()
                && matches!(other.kind, Kind::Mob(_))
                && horizontal(entity.motion).length_squared() > 0.01
            {
                entity.rider = Some(id);
                other.entity.riding = Some(entity.id);
                self.events.push(common::protocol::Packet::AttachEntity {
                    entity: id,
                    vehicle: entity.id,
                });
            }
            let delta = horizontal(other.entity.pos - entity.pos);
            let square = delta.length_squared();
            if square < f64::from(0.0001_f32) {
                continue;
            }
            let length = f64::from(square.sqrt() as f32);
            let push = delta / length * (1.0 / length).min(1.0) * f64::from(0.1_f32) * 0.5;
            if let Kind::Cart(other_cart) = &other.kind {
                // Avoid the original coordinate-dependent collision rejection
                if other_cart.kind == VehicleKind::PoweredCart && kind != VehicleKind::PoweredCart {
                    entity.motion = entity.motion * f64::from(0.2_f32) + other.entity.motion - push;
                    other.entity.motion *= f64::from(0.7_f32);
                } else if kind == VehicleKind::PoweredCart
                    && other_cart.kind != VehicleKind::PoweredCart
                {
                    other.entity.motion =
                        other.entity.motion * f64::from(0.2_f32) + entity.motion + push;
                    entity.motion *= f64::from(0.7_f32);
                } else {
                    let average = (entity.motion + other.entity.motion) / 2.0;
                    entity.motion = entity.motion * f64::from(0.2_f32) + average - push;
                    other.entity.motion = other.entity.motion * f64::from(0.2_f32) + average + push;
                }
            } else {
                entity.motion -= push;
                other.entity.motion += push / 4.0;
            }
        }
    }
}

fn horizontal(vector: DVec3) -> DVec3 {
    vector.with_y(0.0)
}

fn normal_cube(level: &Level, x: i32, y: i32, z: i32) -> bool {
    BlockView::block(level, x, y, z).is_normal_cube()
}