From 2d782f25b1082f65a6ae147b0829f5fac6b879ad Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sun, 12 Feb 2023 01:01:53 +0100 Subject: [PATCH] world: add types to wrap common parts of section data --- src/world/chunk.rs | 111 +++++++++++++++++++++++++++++++++++++++++++ src/world/mod.rs | 2 + src/world/section.rs | 95 ++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 src/world/chunk.rs create mode 100644 src/world/section.rs diff --git a/src/world/chunk.rs b/src/world/chunk.rs new file mode 100644 index 0000000..d5b30b6 --- /dev/null +++ b/src/world/chunk.rs @@ -0,0 +1,111 @@ +use std::collections::BTreeMap; + +use anyhow::{bail, Context, Result}; + +use super::{ + de, + section::{OldSection, PaletteSection, PaletteSectionBiomes}, +}; +use crate::types::*; + +pub enum Chunk<'a> { + V1_18 { + section_map: BTreeMap, PaletteSectionBiomes<'a>)>, + }, + V1_13 { + section_map: BTreeMap>, + biomes: &'a de::BiomesOld, + }, + Old { + section_map: BTreeMap>, + biomes: &'a de::BiomesOld, + }, + Empty, +} + +impl<'a> Chunk<'a> { + pub fn new(data: &'a de::Chunk) -> Result { + let data_version = data.data_version.unwrap_or_default(); + + match &data.chunk { + de::ChunkVariants::V1_18 { sections } => Self::new_v1_18(data_version, sections), + de::ChunkVariants::Old { level } => Self::new_old(data_version, level), + } + } + + fn new_v1_18(data_version: u32, sections: &'a Vec) -> Result { + let mut section_map = BTreeMap::new(); + + for section in sections { + section_map.insert( + SectionY(section.y), + ( + PaletteSection::new( + data_version, + section.block_states.data.as_ref(), + §ion.block_states.palette, + ) + .with_context(|| format!("Failed to load section at Y={}", section.y))?, + PaletteSectionBiomes::new( + section.biomes.data.as_ref(), + §ion.biomes.palette, + ) + .with_context(|| format!("Failed to load section biomes at Y={}", section.y))?, + ), + ); + } + + Ok(Chunk::V1_18 { section_map }) + } + + fn new_old(data_version: u32, level: &'a de::LevelOld) -> Result { + let mut section_map_v1_13 = BTreeMap::new(); + let mut section_map_old = BTreeMap::new(); + + for section in &level.sections { + match §ion.section { + de::SectionOldVariants::V1_13 { + block_states, + palette, + } => { + section_map_v1_13.insert( + SectionY(section.y.into()), + PaletteSection::new(data_version, Some(block_states), palette) + .with_context(|| { + format!("Failed to load section at Y={}", section.y) + })?, + ); + } + de::SectionOldVariants::Old { blocks, data } => { + section_map_old.insert( + SectionY(section.y.into()), + OldSection::new(blocks, data).with_context(|| { + format!("Failed to load section at Y={}", section.y) + })?, + ); + } + de::SectionOldVariants::Empty {} => {} + } + } + + // TODO Check biomes length + let biomes = level.biomes.as_ref().context("Invalid biome data"); + + Ok( + match (section_map_v1_13.is_empty(), section_map_old.is_empty()) { + (true, true) => Chunk::Empty, + (false, true) => Chunk::V1_13 { + section_map: section_map_v1_13, + biomes: biomes?, + }, + (true, false) => Chunk::Old { + section_map: section_map_old, + biomes: biomes?, + }, + (false, false) => { + bail!("Mixed section versions"); + } + }, + ) + } +} diff --git a/src/world/mod.rs b/src/world/mod.rs index 7bbc60f..ae071e0 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -1 +1,3 @@ +pub mod chunk; pub mod de; +pub mod section; diff --git a/src/world/section.rs b/src/world/section.rs new file mode 100644 index 0000000..2ac3945 --- /dev/null +++ b/src/world/section.rs @@ -0,0 +1,95 @@ +use anyhow::{bail, Context, Result}; + +use super::de; + +fn palette_bits(len: usize, min: u8, max: u8) -> Option { + let mut bits = min; + while (1 << bits) < len { + bits += 1; + + if bits > max { + return None; + } + } + + Some(bits) +} + +#[derive(Debug)] +pub struct PaletteSectionBiomes<'a> { + biomes: Option<&'a fastnbt::LongArray>, + palette: &'a Vec, + bits: u8, +} + +impl<'a> PaletteSectionBiomes<'a> { + pub fn new(biomes: Option<&'a fastnbt::LongArray>, palette: &'a Vec) -> Result { + let bits = palette_bits(palette.len(), 1, 6).context("Unsupported block palette size")?; + + if let Some(biomes) = biomes { + let biomes_per_word = 64 / bits as usize; + let expected_length = (64 + biomes_per_word - 1) / biomes_per_word; + if biomes.len() != expected_length { + bail!("Invalid section biome data"); + } + } + + Ok(PaletteSectionBiomes { + biomes, + palette, + bits, + }) + } +} + +#[derive(Debug)] +pub struct PaletteSection<'a> { + block_states: Option<&'a fastnbt::LongArray>, + palette: &'a Vec, + bits: u8, + aligned_blocks: bool, +} + +impl<'a> PaletteSection<'a> { + pub fn new( + data_version: u32, + block_states: Option<&'a fastnbt::LongArray>, + palette: &'a Vec, + ) -> Result { + let aligned_blocks = data_version >= 2529; + + let bits = palette_bits(palette.len(), 4, 12).context("Unsupported block palette size")?; + + if let Some(block_states) = block_states { + let expected_length = if aligned_blocks { + let blocks_per_word = 64 / bits as usize; + (4096 + blocks_per_word - 1) / blocks_per_word + } else { + 64 * bits as usize + }; + if block_states.len() != expected_length { + bail!("Invalid section block data"); + } + } + + Ok(Self { + block_states, + palette, + bits, + aligned_blocks, + }) + } +} + +#[derive(Debug)] +pub struct OldSection<'a> { + blocks: &'a fastnbt::ByteArray, + data: &'a fastnbt::ByteArray, +} + +impl<'a> OldSection<'a> { + pub fn new(blocks: &'a fastnbt::ByteArray, data: &'a fastnbt::ByteArray) -> Result { + // TODO: Check lengths + Ok(Self { blocks, data }) + } +}