From 07d34ee5392fccb87779324c1fd439d72e697ab2 Mon Sep 17 00:00:00 2001 From: "neelesh.poli2006@gmail.com" <72574589+neeleshpoli@users.noreply.github.com> Date: Sat, 21 Dec 2024 19:24:20 -0600 Subject: [PATCH] Seperate world info loading from chunk reading --- pumpkin-world/src/chunk/anvil.rs | 73 +-------------------- pumpkin-world/src/chunk/mod.rs | 7 -- pumpkin-world/src/level.rs | 9 +-- pumpkin-world/src/lib.rs | 1 + pumpkin-world/src/world_info/anvil.rs | 94 +++++++++++++++++++++++++++ pumpkin-world/src/world_info/mod.rs | 34 ++++++++++ 6 files changed, 137 insertions(+), 81 deletions(-) create mode 100644 pumpkin-world/src/world_info/anvil.rs create mode 100644 pumpkin-world/src/world_info/mod.rs diff --git a/pumpkin-world/src/chunk/anvil.rs b/pumpkin-world/src/chunk/anvil.rs index 4e83c078..86389be6 100644 --- a/pumpkin-world/src/chunk/anvil.rs +++ b/pumpkin-world/src/chunk/anvil.rs @@ -4,11 +4,10 @@ use std::{ }; use flate2::bufread::{GzDecoder, ZlibDecoder}; -use serde::Deserialize; -use crate::{chunk::ChunkParsingError, level::SaveFile}; +use crate::level::SaveFile; -use super::{ChunkData, ChunkReader, ChunkReadingError, CompressionError, WorldInfo}; +use super::{ChunkData, ChunkReader, ChunkReadingError, CompressionError}; #[derive(Clone)] pub struct AnvilChunkReader {} @@ -159,56 +158,6 @@ impl ChunkReader for AnvilChunkReader { ChunkData::from_bytes(&decompressed_chunk, *at).map_err(ChunkReadingError::ParsingError) } - - fn read_world_info(&self, save_file: &SaveFile) -> Result { - let path = save_file.root_folder.join("level.dat"); - - let mut world_info_file = OpenOptions::new() - .read(true) - .open(path) - .map_err(|e| ChunkReadingError::IoError(e.kind()))?; - - let mut buffer = Vec::new(); - world_info_file - .read_to_end(&mut buffer) - .map_err(|e| ChunkReadingError::IoError(e.kind()))?; - - let decompressed_data = Compression::GZip - .decompress_data(buffer) - .map_err(ChunkReadingError::Compression)?; - let info = fastnbt::from_bytes::(&decompressed_data).map_err(|e| { - ChunkReadingError::ParsingError(ChunkParsingError::ErrorDeserializingChunk( - e.to_string(), - )) - })?; - - Ok(WorldInfo { - seed: info.data.world_gen_settings.seed, - }) - } -} - -#[derive(Deserialize)] -pub struct LevelDat { - // No idea why its formatted like this - #[serde(rename = "Data")] - pub data: WorldData, -} - -#[derive(Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct WorldData { - pub world_gen_settings: WorldGenSettings, - // TODO: Implement the rest of the fields - // Fields below this comment are being deserialized, but are not being used - pub spawn_x: i32, - pub spawn_y: i32, - pub spawn_z: i32, -} - -#[derive(Deserialize)] -pub struct WorldGenSettings { - pub seed: i64, } #[cfg(test)] @@ -218,7 +167,7 @@ mod tests { use pumpkin_core::math::vector2::Vector2; use crate::{ - chunk::{anvil::AnvilChunkReader, ChunkReader, ChunkReadingError, WorldInfo}, + chunk::{anvil::AnvilChunkReader, ChunkReader, ChunkReadingError}, level::SaveFile, }; @@ -234,20 +183,4 @@ mod tests { ); assert!(matches!(result, Err(ChunkReadingError::ChunkNotExist))); } - - #[test] - fn test_level_dat_reading() { - let world_loader = AnvilChunkReader::new(); - let root_folder = PathBuf::from("test-files").join("sample-1"); - let save_file = SaveFile { - root_folder: root_folder.clone(), - region_folder: root_folder, - }; - let expected = WorldInfo { - seed: -79717552349559436, - }; - let info = world_loader.read_world_info(&save_file).unwrap(); - - assert_eq!(info, expected); - } } diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index 7d50c4d4..fcbb53f1 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -25,7 +25,6 @@ pub trait ChunkReader: Sync + Send { save_file: &SaveFile, at: &Vector2, ) -> Result; - fn read_world_info(&self, save_file: &SaveFile) -> Result; } #[derive(Error, Debug)] @@ -327,9 +326,3 @@ pub enum ChunkParsingError { #[error("Error deserializing chunk: {0}")] ErrorDeserializingChunk(String), } - -#[derive(Debug, PartialEq)] -pub struct WorldInfo { - pub seed: i64, - // TODO: Implement all fields -} diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index eb107b55..3f19d5fc 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -13,9 +13,9 @@ use tokio::{ use crate::{ chunk::{ anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError, - WorldInfo, }, world_gen::{get_world_gen, Seed, WorldGenerator}, + world_info::{anvil::AnvilInfoReader, WorldInfo, WorldInfoReader}, }; /// The `Level` module provides functionality for working with chunks within or outside a Minecraft world. @@ -62,10 +62,11 @@ impl Level { region_folder, }; - let reader = AnvilChunkReader::new(); - let info = reader + // TODO: Load info correctly based on world format type + let world_info_reader = AnvilInfoReader::new(); + let info = world_info_reader .read_world_info(&save_file) - .expect("Unable to get world info!"); + .expect("Unable to get world info!"); // TODO: Improve error handling let seed = Seed(info.seed as u64); let world_gen = get_world_gen(seed).into(); // TODO Read Seed from config. diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index d1d6284c..7ca3b38f 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -16,6 +16,7 @@ pub mod dimension; pub mod item; pub mod level; mod world_gen; +pub mod world_info; pub const WORLD_HEIGHT: usize = 384; pub const WORLD_LOWEST_Y: i16 = -64; diff --git a/pumpkin-world/src/world_info/anvil.rs b/pumpkin-world/src/world_info/anvil.rs new file mode 100644 index 00000000..cb30d953 --- /dev/null +++ b/pumpkin-world/src/world_info/anvil.rs @@ -0,0 +1,94 @@ +use std::{fs::OpenOptions, io::Read}; + +use flate2::read::GzDecoder; +use serde::Deserialize; + +use crate::level::SaveFile; + +use super::{WorldInfo, WorldInfoError, WorldInfoReader}; + +pub struct AnvilInfoReader {} + +impl AnvilInfoReader { + pub fn new() -> Self { + Self {} + } +} + +impl WorldInfoReader for AnvilInfoReader { + fn read_world_info(&self, save_file: &SaveFile) -> Result { + let path = save_file.root_folder.join("level.dat"); + + let mut world_info_file = OpenOptions::new().read(true).open(path)?; + + let mut buffer = Vec::new(); + world_info_file.read_to_end(&mut buffer)?; + + let mut decoder = GzDecoder::new(&buffer[..]); + let mut decompressed_data = Vec::new(); + decoder.read_to_end(&mut decompressed_data)?; + + let info = fastnbt::from_bytes::(&decompressed_data) + .map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?; + + Ok(WorldInfo { + seed: info.data.world_gen_settings.seed, + }) + } +} + + +impl Default for AnvilInfoReader { + fn default() -> Self { + Self::new() + } +} + +#[derive(Deserialize)] +pub struct LevelDat { + // No idea why its formatted like this + #[serde(rename = "Data")] + pub data: WorldData, +} + +#[derive(Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct WorldData { + pub world_gen_settings: WorldGenSettings, + // TODO: Implement the rest of the fields + // Fields below this comment are being deserialized, but are not being used + pub spawn_x: i32, + pub spawn_y: i32, + pub spawn_z: i32, +} + +#[derive(Deserialize)] +pub struct WorldGenSettings { + pub seed: i64, +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::{ + level::SaveFile, + world_info::{anvil::AnvilInfoReader, WorldInfo, WorldInfoReader}, + }; + + #[test] + fn test_level_dat_reading() { + let world_info = AnvilInfoReader::new(); + let root_folder = PathBuf::from("test-files").join("sample-1"); + let save_file = SaveFile { + root_folder: root_folder.clone(), + region_folder: root_folder, + }; + let expected = WorldInfo { + seed: -79717552349559436, + }; + let info = world_info.read_world_info(&save_file).unwrap(); + + assert_eq!(info, expected); + } +} diff --git a/pumpkin-world/src/world_info/mod.rs b/pumpkin-world/src/world_info/mod.rs new file mode 100644 index 00000000..a1184635 --- /dev/null +++ b/pumpkin-world/src/world_info/mod.rs @@ -0,0 +1,34 @@ +use thiserror::Error; + +use crate::level::SaveFile; + +pub mod anvil; + +pub(crate) trait WorldInfoReader { + fn read_world_info(&self, save_file: &SaveFile) -> Result; +} + +#[derive(Debug, PartialEq)] +pub struct WorldInfo { + pub seed: i64, + // TODO: Implement all fields +} + +#[derive(Error, Debug)] +pub enum WorldInfoError { + #[error("Io error: {0}")] + IoError(std::io::ErrorKind), + #[error("Info not found!")] + InfoNotFound, + #[error("Deserialization error: {0}")] + DeserializationError(String), +} + +impl From for WorldInfoError { + fn from(value: std::io::Error) -> Self { + match value.kind() { + std::io::ErrorKind::NotFound => Self::InfoNotFound, + value => Self::IoError(value), + } + } +}