Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement level.dat reading and load the seed for use in world generation #402

Merged
merged 6 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions pumpkin-world/src/level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<WorldInfo>,
save_file: Option<SaveFile>,
loaded_chunks: Arc<DashMap<Vector2<i32>, Arc<RwLock<ChunkData>>>>,
chunk_watchers: Arc<DashMap<Vector2<i32>, usize>>,
Expand Down Expand Up @@ -55,20 +57,27 @@ 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,
};

// 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!"); // TODO: Improve error handling
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();
Expand All @@ -80,6 +89,7 @@ impl Level {
chunk_reader: Arc::new(AnvilChunkReader::new()),
loaded_chunks: Arc::new(DashMap::new()),
chunk_watchers: Arc::new(DashMap::new()),
world_info: None,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pumpkin-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod dimension;
mod generation;
pub mod item;
pub mod level;

pub mod world_info;
pub const WORLD_HEIGHT: usize = 384;
pub const WORLD_LOWEST_Y: i16 = -64;
pub const WORLD_MAX_Y: i16 = WORLD_HEIGHT as i16 - WORLD_LOWEST_Y.abs();
Expand Down
93 changes: 93 additions & 0 deletions pumpkin-world/src/world_info/anvil.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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),
}
}
}
Binary file added pumpkin-world/test-files/sample-1/level.dat
Binary file not shown.
Loading