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