From dd36013f5e31f3d24276e6e3c4c2f5859d8e00ec Mon Sep 17 00:00:00 2001 From: "neelesh.poli2006@gmail.com" <72574589+neeleshpoli@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:10:59 -0600 Subject: [PATCH 1/5] Implement level.dat reading and use the seed in the world for chunk generation --- pumpkin-world/src/chunk/anvil.rs | 55 ++++++++++++++++++++++++++++++-- pumpkin-world/src/chunk/mod.rs | 6 ++++ pumpkin-world/src/level.rs | 21 ++++++++---- 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/pumpkin-world/src/chunk/anvil.rs b/pumpkin-world/src/chunk/anvil.rs index 86389be6..af24f70b 100644 --- a/pumpkin-world/src/chunk/anvil.rs +++ b/pumpkin-world/src/chunk/anvil.rs @@ -4,10 +4,11 @@ use std::{ }; use flate2::bufread::{GzDecoder, ZlibDecoder}; +use serde::Deserialize; -use crate::level::SaveFile; +use crate::{chunk::ChunkParsingError, level::SaveFile}; -use super::{ChunkData, ChunkReader, ChunkReadingError, CompressionError}; +use super::{ChunkData, ChunkReader, ChunkReadingError, CompressionError, WorldInfo}; #[derive(Clone)] pub struct AnvilChunkReader {} @@ -158,6 +159,56 @@ 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)] diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index fcbb53f1..5e69e8f6 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -25,6 +25,7 @@ pub trait ChunkReader: Sync + Send { save_file: &SaveFile, at: &Vector2, ) -> Result; + fn read_world_info(&self, save_file: &SaveFile) -> Result; } #[derive(Error, Debug)] @@ -326,3 +327,8 @@ pub enum ChunkParsingError { #[error("Error deserializing chunk: {0}")] ErrorDeserializingChunk(String), } + +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 2a6a9571..eb107b55 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -13,6 +13,7 @@ use tokio::{ use crate::{ chunk::{ anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError, + WorldInfo, }, world_gen::{get_world_gen, Seed, WorldGenerator}, }; @@ -28,6 +29,7 @@ use crate::{ /// For more details on world generation, refer to the `WorldGenerator` module. pub struct Level { pub seed: Seed, + pub world_info: Option, save_file: Option, loaded_chunks: Arc, Arc>>>, chunk_watchers: Arc, usize>>, @@ -55,20 +57,26 @@ impl Level { region_folder.exists(), "World region folder does not exist, despite there being a root folder." ); - // TODO: read seed from level.dat - let seed = get_or_create_seed(); + let save_file = SaveFile { + root_folder, + region_folder, + }; + + let reader = AnvilChunkReader::new(); + let info = reader + .read_world_info(&save_file) + .expect("Unable to get world info!"); + let seed = Seed(info.seed as u64); let world_gen = get_world_gen(seed).into(); // TODO Read Seed from config. Self { seed, world_gen, - save_file: Some(SaveFile { - root_folder, - region_folder, - }), + save_file: Some(save_file), chunk_reader: Arc::new(AnvilChunkReader::new()), loaded_chunks: Arc::new(DashMap::new()), chunk_watchers: Arc::new(DashMap::new()), + world_info: Some(info), } } else { let seed = get_or_create_seed(); @@ -80,6 +88,7 @@ impl Level { chunk_reader: Arc::new(AnvilChunkReader::new()), loaded_chunks: Arc::new(DashMap::new()), chunk_watchers: Arc::new(DashMap::new()), + world_info: None, } } } From fa7ecde1b11e19f235c9a1bb07a956c6e3972699 Mon Sep 17 00:00:00 2001 From: "neelesh.poli2006@gmail.com" <72574589+neeleshpoli@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:37:49 -0600 Subject: [PATCH 2/5] Add tests --- pumpkin-world/src/chunk/anvil.rs | 18 +++++++++++++++++- pumpkin-world/src/chunk/mod.rs | 1 + pumpkin-world/test-files/sample-1/level.dat | Bin 0 -> 1530 bytes 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 pumpkin-world/test-files/sample-1/level.dat diff --git a/pumpkin-world/src/chunk/anvil.rs b/pumpkin-world/src/chunk/anvil.rs index af24f70b..4e83c078 100644 --- a/pumpkin-world/src/chunk/anvil.rs +++ b/pumpkin-world/src/chunk/anvil.rs @@ -218,7 +218,7 @@ mod tests { use pumpkin_core::math::vector2::Vector2; use crate::{ - chunk::{anvil::AnvilChunkReader, ChunkReader, ChunkReadingError}, + chunk::{anvil::AnvilChunkReader, ChunkReader, ChunkReadingError, WorldInfo}, level::SaveFile, }; @@ -234,4 +234,20 @@ 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 5e69e8f6..7d50c4d4 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -328,6 +328,7 @@ pub enum ChunkParsingError { ErrorDeserializingChunk(String), } +#[derive(Debug, PartialEq)] pub struct WorldInfo { pub seed: i64, // TODO: Implement all fields diff --git a/pumpkin-world/test-files/sample-1/level.dat b/pumpkin-world/test-files/sample-1/level.dat new file mode 100644 index 0000000000000000000000000000000000000000..1a527907bf8616dee934fd9e7c1cdce5087c30e7 GIT binary patch literal 1530 zcmVwogCsOVZ<@@Rej*sCGado)?tL*sYI{=QfzRK;?vKAdA^0ni+7Z>J z71ALvLcE_o{PyY(k1WtIlvfQIMN}|NX2?SNv;o3^`j%urkIZqw4W}V~l6f;^Ej7EK zN^sHJ3uSPrU##cGl|EH0kP65&nH_-ATna+I{=G`(eNcF?r>wh*GcLDi3~HLp_%Xc_ z&gC?z9MA~PhB4TGXUJlf5beSnDIoV$CN=kHSM)+>Ss~RU7(jS}uSxkzs*sgP$pPbj zJETgM{Gbmk34!eoFiTBRkIO*_R9yfy{R~Ryc|G;qq!H~w3nRCwe)axm`}<{@+`ga| zdc@#NQ5&HRrwNF18qVTga(T(k=6MFfGnedx`sQ|h^U?kKsOEG^@g-uzOYCifbhW>JkrRQjueS*Bh=cU@k$Q_ zC~BbHMT8Rt+N+To!w(9Ji>{INhzkf5?V9IUfwll!hYBefOM;1)HB!yKDhtDoG&b8MQ6!PxJ3-sMu!p^?zoq` zn{@X-MnB2jSOOCbFo%_Km_Za7e0=jC;{W(#W6>>)uI-g2Vo3wAzyExH^4EubW97ke zFV(xvI!0n;u|-jXs$_Ow&<-|t#c_LfKm`v&O6Iyega;khjiemgF07JDlk027DXvtR zHjiS3$V5&b))g>qPd`T%iM3Bp-p@=$0Q z2~LZnE-HGeR{7BqlTA7dd9QC8LsYr^{PPL!o6x05snq+_Q*^(`WG0GZkC$~SBZ;`o zQu4Md*yf=`JbZ~lm8olP&t!`lB}1n#RyMMWjflR@<`0Sz7e^p^rccT{j~_qWLHCK^ zc7o6lMIJk?jQLrvZx~_r-^QV|U^kVFn^?JFalMMjL=iBKl5+|!*5a<#%6@k45sK zBN|fV<1vqJMfc@Z(;|KhT4Mxy6r_?rKwyx_x=y3QA&H#%QSv^6_w2vhPKfrE5tUQm*r|&W=G+Q1m*hiQL9+t=~ ziswHgTyqi9cRUF@&_0D9&6A{-0=04Fc!Q2+n{ literal 0 HcmV?d00001 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 3/5] 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), + } + } +} From 3cb7f2b085bdffec19b965a0db1adae38aa35060 Mon Sep 17 00:00:00 2001 From: "neelesh.poli2006@gmail.com" <72574589+neeleshpoli@users.noreply.github.com> Date: Sat, 21 Dec 2024 19:31:57 -0600 Subject: [PATCH 4/5] Fix cargo fmt --- pumpkin-world/src/world_info/anvil.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pumpkin-world/src/world_info/anvil.rs b/pumpkin-world/src/world_info/anvil.rs index cb30d953..1132d072 100644 --- a/pumpkin-world/src/world_info/anvil.rs +++ b/pumpkin-world/src/world_info/anvil.rs @@ -37,7 +37,6 @@ impl WorldInfoReader for AnvilInfoReader { } } - impl Default for AnvilInfoReader { fn default() -> Self { Self::new() From 566136e350a3aaddc6e4a3934be7e049fc8f9107 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Tue, 24 Dec 2024 15:21:28 +0100 Subject: [PATCH 5/5] upsi while fixing conflicts --- pumpkin-world/src/level.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 4820e799..4c06f5a2 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -15,6 +15,7 @@ use crate::{ anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError, }, generation::{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.