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 / save / chunk.rs

//! Chunk NBT of `ChunkLoader.storeChunkInCompound` and `loadChunkIntoWorldFromCompound`.

use common::nbt::{Compound, Tag};
use common::world::chunk::{self, Nibbles, VOLUME};
use common::world::{Block, Chunk, ChunkPos, TileEntity};

/// A chunk from disk with the NBT of its entities, which are spawned separately.
pub struct LoadedChunk {
    pub chunk: Chunk,
    pub entities: Vec<Compound>,
}

pub fn write(chunk: &Chunk, entities: &[Compound], time: i64) -> Compound {
    let mut level = Compound::default();
    level.set_int("xPos", chunk.pos.x);
    level.set_int("zPos", chunk.pos.z);
    level.set_long("LastUpdate", time);
    level.set_byte_array("Blocks", chunk.blocks().ids().collect::<Vec<_>>());
    level.set_byte_array("Data", chunk.metadata.to_bytes());
    level.set_byte_array("SkyLight", chunk.sky_light.to_bytes());
    level.set_byte_array("BlockLight", chunk.block_light.to_bytes());
    level.set_byte_array("HeightMap", chunk.height_map().to_vec());
    level.set_bool("TerrainPopulated", chunk.populated);
    level.set_list(
        "Entities",
        entities.iter().cloned().map(Tag::Compound).collect(),
    );
    let (base_x, base_z) = (chunk.pos.min_block_x(), chunk.pos.min_block_z());
    let tile_entities = chunk
        .tile_entities
        .iter()
        .map(|(&index, tile_entity)| {
            let index = usize::from(index);
            let position = [
                base_x + (index >> 11) as i32,
                (index & 127) as i32,
                base_z + (index >> 7 & 15) as i32,
            ];
            Tag::Compound(tile_entity.write_nbt(position))
        })
        .collect();
    level.set_list("TileEntities", tile_entities);
    let mut root = Compound::default();
    root.set_compound("Level", level);
    root
}

/// Reads a chunk, relocating it if it claims another position. Unknown block ids become air.
pub fn read(root: &Compound, pos: ChunkPos) -> Option<LoadedChunk> {
    let (x, z) = (pos.x, pos.z);
    let Some(level) = root.compound("Level") else {
        tracing::warn!("chunk file at {x},{z} is missing level data, skipping");
        return None;
    };
    let Ok(raw): Result<&[u8; VOLUME], _> = level.byte_array("Blocks").try_into() else {
        tracing::warn!("chunk file at {x},{z} is missing block data, skipping");
        return None;
    };
    let (stored_x, stored_z) = (level.int("xPos"), level.int("zPos"));
    if (stored_x, stored_z) != (x, z) {
        tracing::warn!(
            "chunk file at {x},{z} is in the wrong location; relocating (got {stored_x},{stored_z})"
        );
    }
    let blocks = Box::new(raw.map(|id| {
        Some(Block(id))
            .filter(|block| block.properties().is_some())
            .unwrap_or(Block::AIR)
    }));
    let height_map = level.byte_array("HeightMap").try_into().ok();
    let mut chunk = Chunk::from_saved(
        pos,
        blocks,
        Nibbles::from_bytes(level.byte_array("Data")),
        Nibbles::from_bytes(level.byte_array("SkyLight")),
        Nibbles::from_bytes(level.byte_array("BlockLight")),
        height_map,
    );
    chunk.populated = level.bool("TerrainPopulated");
    for nbt in level.compounds("TileEntities") {
        let Some((tile_entity, [x, y, z])) = TileEntity::read_nbt(nbt) else {
            tracing::warn!(
                "skipping tile entity with unknown id {:?}",
                nbt.string("id")
            );
            continue;
        };
        let (lx, lz) = (x - pos.min_block_x(), z - pos.min_block_z());
        let valid = (0..16).contains(&lx) && (0..16).contains(&lz) && (0..128).contains(&y);
        let index = valid.then(|| chunk::index(lx as usize, y as usize, lz as usize));
        match index {
            Some(index) if chunk.blocks().get(index).is_container() => {
                chunk.tile_entities.insert(index as u16, tile_entity);
            }
            _ => tracing::warn!("attempted to place a tile entity where there was no entity tile"),
        }
    }
    let entities = level.compounds("Entities").cloned().collect();
    Some(LoadedChunk { chunk, entities })
}