diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index 9c555b39..35c561a2 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -13,6 +13,7 @@ use crate::{ }; pub mod anvil; +pub mod pumpkin; const CHUNK_AREA: usize = 16 * 16; const SUBCHUNK_VOLUME: usize = CHUNK_AREA * 16; diff --git a/pumpkin-world/src/chunk/pumpkin.rs b/pumpkin-world/src/chunk/pumpkin.rs new file mode 100644 index 00000000..153dfa29 --- /dev/null +++ b/pumpkin-world/src/chunk/pumpkin.rs @@ -0,0 +1,180 @@ +use std::{ + collections::HashMap, + fs::OpenOptions, + io::{Read, Write}, +}; + +use fastnbt::LongArray; +use pumpkin_core::math::ceil_log2; + +use crate::{ + block::block_registry::BLOCK_ID_TO_REGISTRY_ID, chunk::ChunkWritingError, level::LevelFolder, +}; + +use super::{ + ChunkData, ChunkNbt, ChunkReader, ChunkReadingError, ChunkSection, ChunkSectionBlockStates, + ChunkSerializingError, ChunkWriter, PaletteEntry, +}; + +// 1.21.4 +const WORLD_DATA_VERSION: i32 = 4189; + +#[derive(Clone, Default)] +pub struct PumpkinChunkFormat; + +impl ChunkReader for PumpkinChunkFormat { + fn read_chunk( + &self, + save_file: &LevelFolder, + at: &pumpkin_core::math::vector2::Vector2, + ) -> Result { + let mut file = OpenOptions::new() + .read(true) + .open( + save_file + .region_folder + .join(format!("c.{}.{}.mcp", at.x, at.z)), + ) + .map_err(|err| match err.kind() { + std::io::ErrorKind::NotFound => ChunkReadingError::ChunkNotExist, + kind => ChunkReadingError::IoError(kind), + })?; + + let mut data = Vec::new(); + file.read_to_end(&mut data).unwrap(); + + ChunkData::from_bytes(&data, *at).map_err(ChunkReadingError::ParsingError) + } +} + +impl ChunkWriter for PumpkinChunkFormat { + fn write_chunk( + &self, + chunk_data: &ChunkData, + level_folder: &LevelFolder, + at: &pumpkin_core::math::vector2::Vector2, + ) -> Result<(), super::ChunkWritingError> { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(false) + .open( + level_folder + .region_folder + .join(format!("c.{}.{}.mcp", at.x, at.z)), + ) + .map_err(|err| ChunkWritingError::IoError(err.kind()))?; + + let raw_bytes = self + .to_bytes(chunk_data) + .map_err(|err| ChunkWritingError::ChunkSerializingError(err.to_string()))?; + + file.write_all(&raw_bytes).unwrap(); + + Ok(()) + } +} + +impl PumpkinChunkFormat { + pub fn to_bytes(&self, chunk_data: &ChunkData) -> Result, ChunkSerializingError> { + let mut sections = Vec::new(); + + for (i, blocks) in chunk_data.blocks.blocks.chunks(16 * 16 * 16).enumerate() { + // get unique blocks + let palette = HashMap::::from_iter(blocks.iter().map(|v| { + ( + *v, + BLOCK_ID_TO_REGISTRY_ID + .get(v) + .expect("Tried saving a block which does not exist."), + ) + })); + let palette = HashMap::::from_iter( + palette + .into_iter() + .enumerate() + .map(|(index, (block_id, registry_str))| (block_id, (registry_str, index))), + ); + + let block_bit_size = if palette.len() < 16 { + 4 + } else { + ceil_log2(palette.len() as u32).max(4) + }; + let _blocks_in_pack = 64 / block_bit_size; + + let mut section_longs = Vec::new(); + let mut current_pack_long: i64 = 0; + let mut bits_used_in_pack: u32 = 0; + + for block in blocks { + let index = palette.get(block).expect("Just added all unique").1; + current_pack_long |= (index as i64) << bits_used_in_pack; + bits_used_in_pack += block_bit_size as u32; + + if bits_used_in_pack >= 64 { + section_longs.push(current_pack_long); + current_pack_long = 0; + bits_used_in_pack = 0; + } + } + + if bits_used_in_pack > 0 { + section_longs.push(current_pack_long); + } + + sections.push(ChunkSection { + y: i as i8, + block_states: Some(ChunkSectionBlockStates { + data: Some(LongArray::new(section_longs)), + palette: palette + .into_iter() + .map(|entry| PaletteEntry { + name: entry.1 .0.clone(), + properties: None, + }) + .collect(), + }), + }); + } + + let nbt = ChunkNbt { + data_version: WORLD_DATA_VERSION, + x_pos: chunk_data.position.x, + z_pos: chunk_data.position.z, + status: super::ChunkStatus::Full, + heightmaps: chunk_data.blocks.heightmap.clone(), + sections, + }; + + let bytes = fastnbt::to_bytes(&nbt); + + bytes.map_err(ChunkSerializingError::ErrorSerializingChunk) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use pumpkin_core::math::vector2::Vector2; + + use crate::{ + chunk::{anvil::AnvilChunkFormat, ChunkReader, ChunkReadingError}, + level::LevelFolder, + }; + + #[test] + fn not_existing() { + let region_path = PathBuf::from("not_existing"); + let result = AnvilChunkFormat.read_chunk( + &LevelFolder { + root_folder: PathBuf::from(""), + region_folder: region_path, + }, + &Vector2::new(0, 0), + ); + assert!(matches!(result, Err(ChunkReadingError::ChunkNotExist))); + } +} diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 80f8a7f7..56ed197b 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -11,8 +11,8 @@ use tokio::{ use crate::{ chunk::{ - anvil::AnvilChunkFormat, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError, - ChunkWriter, + anvil::AnvilChunkFormat, pumpkin::PumpkinChunkFormat, ChunkData, ChunkParsingError, + ChunkReader, ChunkReadingError, ChunkWriter, }, generation::{get_world_gen, Seed, WorldGenerator}, lock::{anvil::AnvilLevelLocker, LevelLocker}, @@ -78,7 +78,7 @@ impl Level { world_info_writer: Arc::new(AnvilLevelInfo), level_folder, chunk_reader: Arc::new(AnvilChunkFormat), - chunk_writer: Arc::new(AnvilChunkFormat), + chunk_writer: Arc::new(PumpkinChunkFormat), loaded_chunks: Arc::new(DashMap::new()), chunk_watchers: Arc::new(DashMap::new()), level_info,