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

use common::entity::mob::MobKind;
use common::entity::painting::Painting;
use common::item::ItemStack;
use common::nbt::{Compound, Tag};
use common::world::Block;
use glam::DVec3;

use super::{Arrow, Boat, Cart, Entities, Fireball, Kind, Mob, ServerEntity, Stuck, Thrown};

impl Stuck {
    fn read(nbt: &Compound) -> Self {
        Self {
            tile: [nbt.short("xTile"), nbt.short("yTile"), nbt.short("zTile")].map(i32::from),
            block: Block(nbt.byte("inTile") as u8),
            grounded: nbt.bool("inGround"),
            ..Default::default()
        }
    }

    fn write_tile(&self, nbt: &mut Compound) {
        for (key, value) in ["xTile", "yTile", "zTile"].into_iter().zip(self.tile) {
            nbt.set_short(key, value as i16);
        }
        nbt.set_byte("inTile", self.block.0 as i8);
    }
}

impl ServerEntity {
    pub fn read(nbt: &Compound, entities: &mut Entities) -> Option<Self> {
        let shake = i32::from(nbt.byte("shake") as u8);
        let kind = match nbt.string("id") {
            "Item" => {
                let stack = ItemStack::read_nbt(nbt.compound("Item")?);
                if stack.count <= 0 {
                    return None;
                }
                Kind::Item {
                    stack,
                    age: i32::from(nbt.short("Age")),
                    health: i32::from(nbt.short("Health")) & 255,
                    pickup_delay: 0,
                }
            }
            "FallingSand" => Kind::Falling {
                block: Block(nbt.byte("Tile") as u8),
                age: 0,
            },
            "Painting" => Kind::Painting {
                painting: Painting::from_name(
                    nbt.string("Motive"),
                    [nbt.int("TileX"), nbt.int("TileY"), nbt.int("TileZ")],
                    nbt.byte("Dir") as u8,
                ),
                check: 0,
            },
            "PrimedTnt" => Kind::Tnt {
                fuse: i32::from(nbt.byte("Fuse")),
            },
            "Minecart" => Kind::Cart(Cart::read(nbt)),
            "Boat" => Kind::Boat(Boat::default()),
            "Fireball" => Kind::Fireball(Fireball {
                stuck: Stuck::read(nbt),
                shake,
                ..Default::default()
            }),
            "Snowball" => Kind::Thrown(Thrown {
                egg: false,
                owner: None,
                air_ticks: 0,
            }),
            "Arrow" => Kind::Arrow(Arrow {
                stuck: Stuck::read(nbt),
                metadata: nbt.byte("inData") as u8,
                shake,
                player: nbt.bool("player"),
                ..Default::default()
            }),
            name => {
                let mut mob = Mob::new(MobKind::from_name(name)?);
                mob.color = nbt.byte("Color") as u8 & 15;
                mob.sheared = nbt.bool("Sheared");
                mob.saddled = nbt.bool("Saddle");
                mob.powered = nbt.bool("powered");
                mob.dropped = nbt.contains("Health") && nbt.short("Health") <= 0;
                Kind::Mob(mob)
            }
        };
        let [Tag::Double(x), Tag::Double(y), Tag::Double(z)] = nbt.list("Pos") else {
            return None;
        };
        let pos = DVec3::new(*x, *y, *z);
        if !pos.is_finite() {
            return None;
        }
        let mut stored = entities.create(kind);
        let entity = &mut stored.entity;
        if let Kind::Mob(mob) = &mut stored.kind {
            let living = entity.living.as_mut()?;
            living.health = if nbt.contains("Health") {
                i32::from(nbt.short("Health"))
            } else {
                10
            };
            living.hurt_time = i32::from(nbt.short("HurtTime"));
            living.death_time = i32::from(nbt.short("DeathTime"));
            living.attack_time = i32::from(nbt.short("AttackTime"));
            // `EntityChicken`: the egg countdown is not saved but drawn on construction
            if mob.kind == MobKind::Chicken {
                mob.egg_timer += entity.rand.next_int(6000);
            }
            mob.special.read(entity, nbt);
        }
        entity.set_position(pos);
        entity.prev_pos = pos;
        entity.last_tick_pos = pos;
        if let [Tag::Double(x), Tag::Double(y), Tag::Double(z)] = nbt.list("Motion") {
            entity.motion = DVec3::new(*x, *y, *z).map(|v| {
                if v.is_finite() && v.abs() <= 10.0 {
                    v
                } else {
                    0.0
                }
            });
        }
        if let [Tag::Float(yaw), Tag::Float(pitch)] = nbt.list("Rotation") {
            entity.set_rotation(*yaw, *pitch);
            entity.prev_yaw = entity.yaw;
            entity.prev_pitch = entity.pitch;
        }
        if let Kind::Painting { painting, .. } = &stored.kind {
            let yaw = f32::from(painting.direction) * 90.0;
            entity.set_location_and_angles(painting.position(), yaw, 0.0);
            entity.bb = painting.bounds();
        }
        entity.fall_distance = nbt.float("FallDistance");
        entity.fire = i32::from(nbt.short("Fire"));
        entity.air = i32::from(nbt.short("Air"));
        entity.on_ground = nbt.bool("OnGround");
        stored.extra = nbt.clone();
        Some(stored)
    }

    pub fn write(&self) -> Option<Compound> {
        if self.entity.dead {
            return None;
        }
        let mut nbt = self.extra.clone();
        match &self.kind {
            Kind::Player { .. } | Kind::Hook(_) => return None,
            Kind::Painting { painting, .. } => {
                nbt.set_string("id", "Painting");
                nbt.set_string("Motive", painting.art().name);
                nbt.set_byte("Dir", painting.direction as i8);
                for (key, coordinate) in
                    ["TileX", "TileY", "TileZ"].into_iter().zip(painting.anchor)
                {
                    nbt.set_int(key, coordinate);
                }
            }
            Kind::Boat(_) => nbt.set_string("id", "Boat"),
            Kind::Cart(cart) => {
                nbt.set_string("id", "Minecart");
                cart.write(&mut nbt);
            }
            Kind::Tnt { fuse } => {
                nbt.set_string("id", "PrimedTnt");
                nbt.set_byte("Fuse", *fuse as i8);
            }
            Kind::Item {
                stack, age, health, ..
            } => {
                nbt.set_string("id", "Item");
                let mut item = Compound::default();
                stack.write_nbt(&mut item);
                nbt.set_compound("Item", item);
                nbt.set_short("Age", *age as i16);
                nbt.set_short("Health", *health as i16);
            }
            Kind::Falling { block, .. } => {
                nbt.set_string("id", "FallingSand");
                nbt.set_byte("Tile", block.0 as i8);
            }
            Kind::Mob(mob) => {
                nbt.set_string("id", mob.kind.name());
                mob.special.write(&mut nbt);
                match mob.kind {
                    MobKind::Sheep => {
                        nbt.set_byte("Color", mob.color as i8);
                        nbt.set_bool("Sheared", mob.sheared);
                    }
                    MobKind::Pig => nbt.set_bool("Saddle", mob.saddled),
                    MobKind::Creeper => nbt.set_bool("powered", mob.powered),
                    _ => {}
                }
                if let Some(living) = &self.entity.living {
                    nbt.set_short("Health", living.health as i16);
                    nbt.set_short("HurtTime", living.hurt_time as i16);
                    nbt.set_short("DeathTime", living.death_time as i16);
                    nbt.set_short("AttackTime", living.attack_time as i16);
                }
            }
            Kind::Thrown(thrown) => {
                if thrown.egg {
                    return None;
                }
                nbt.set_string("id", "Snowball");
                for key in ["xTile", "yTile", "zTile"] {
                    if !nbt.contains(key) {
                        nbt.set_short(key, -1);
                    }
                }
                for key in ["inTile", "shake", "inGround"] {
                    if !nbt.contains(key) {
                        nbt.set_byte(key, 0);
                    }
                }
            }
            Kind::Fireball(fireball) => {
                nbt.set_string("id", "Fireball");
                fireball.stuck.write_tile(&mut nbt);
                nbt.set_byte("shake", fireball.shake as i8);
                nbt.set_bool("inGround", fireball.stuck.grounded);
            }
            Kind::Arrow(arrow) => {
                nbt.set_string("id", "Arrow");
                arrow.stuck.write_tile(&mut nbt);
                nbt.set_byte("inData", arrow.metadata as i8);
                nbt.set_byte("shake", arrow.shake as i8);
                nbt.set_bool("inGround", arrow.stuck.grounded);
                nbt.set_bool("player", arrow.player);
            }
        }
        let entity = &self.entity;
        nbt.set_list(
            "Pos",
            vec![
                Tag::Double(entity.pos.x),
                Tag::Double(entity.pos.y + f64::from(entity.y_size)),
                Tag::Double(entity.pos.z),
            ],
        );
        nbt.set_list("Motion", entity.motion.to_array().map(Tag::Double).to_vec());
        nbt.set_list(
            "Rotation",
            vec![Tag::Float(entity.yaw), Tag::Float(entity.pitch)],
        );
        nbt.set_float("FallDistance", entity.fall_distance);
        nbt.set_short("Fire", entity.fire as i16);
        nbt.set_short("Air", entity.air as i16);
        nbt.set_bool("OnGround", entity.on_ground);
        Some(nbt)
    }
}