Skip to content

Commit

Permalink
Seperate world info loading from chunk reading
Browse files Browse the repository at this point in the history
  • Loading branch information
neeleshpoli committed Dec 22, 2024
1 parent fa7ecde commit 07d34ee
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 81 deletions.
73 changes: 3 additions & 70 deletions pumpkin-world/src/chunk/anvil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down Expand Up @@ -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<WorldInfo, ChunkReadingError> {
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::<LevelDat>(&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)]
Expand All @@ -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,
};

Expand All @@ -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);
}
}
7 changes: 0 additions & 7 deletions pumpkin-world/src/chunk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ pub trait ChunkReader: Sync + Send {
save_file: &SaveFile,
at: &Vector2<i32>,
) -> Result<ChunkData, ChunkReadingError>;
fn read_world_info(&self, save_file: &SaveFile) -> Result<WorldInfo, ChunkReadingError>;
}

#[derive(Error, Debug)]
Expand Down Expand Up @@ -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
}
9 changes: 5 additions & 4 deletions pumpkin-world/src/level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down
1 change: 1 addition & 0 deletions pumpkin-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
94 changes: 94 additions & 0 deletions pumpkin-world/src/world_info/anvil.rs
Original file line number Diff line number Diff line change
@@ -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<WorldInfo, WorldInfoError> {
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::<LevelDat>(&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);
}
}
34 changes: 34 additions & 0 deletions pumpkin-world/src/world_info/mod.rs
Original file line number Diff line number Diff line change
@@ -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<WorldInfo, WorldInfoError>;
}

#[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<std::io::Error> for WorldInfoError {
fn from(value: std::io::Error) -> Self {
match value.kind() {
std::io::ErrorKind::NotFound => Self::InfoNotFound,
value => Self::IoError(value),
}
}
}

0 comments on commit 07d34ee

Please sign in to comment.