mirror of
https://github.com/Cactus-minecraft-server/World.git
synced 2025-12-07 10:40:37 +00:00
started the level.dat file creation and completion... I may need to continue the code of level.rs and make an update function to level.dat
This commit is contained in:
@@ -7,6 +7,7 @@ edition = "2024"
|
|||||||
image = "0.25.6"
|
image = "0.25.6"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
rand_chacha = "0.9.0"
|
rand_chacha = "0.9.0"
|
||||||
|
nbt = { git = "https://github.com/Cactus-minecraft-server/nbt.git" }
|
||||||
[lib]
|
[lib]
|
||||||
name = "worldgen"
|
name = "worldgen"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|||||||
69
TODO.md
69
TODO.md
@@ -1,69 +0,0 @@
|
|||||||
# TODO List for a Minecraft-Like Terrain Generator in Rust
|
|
||||||
|
|
||||||
## 1. Define Objectives and Requirements
|
|
||||||
- **Features:**
|
|
||||||
- Global terrain generation (overall elevation)
|
|
||||||
- Local detail (surface variations)
|
|
||||||
- Biome transitions and optionally cave systems
|
|
||||||
- **Technical Constraints:**
|
|
||||||
- Chunk dimensions (e.g., 16×16 blocks horizontally with a fixed vertical height)
|
|
||||||
- Memory management (chunk caching, on-demand generation)
|
|
||||||
- Server integration (protocol, networking, etc.)
|
|
||||||
- **Output Format:**
|
|
||||||
- How to represent the world (e.g., a 3D array of block types)
|
|
||||||
- Block types (Air, Grass, Dirt, Stone, Water, etc.)
|
|
||||||
|
|
||||||
## 2. Research and Select Noise Algorithms
|
|
||||||
- Study noise algorithms such as Perlin and Simplex.
|
|
||||||
- Understand Fractal Brownian Motion (fBm) to combine multiple octaves.
|
|
||||||
- Define parameters like frequency, amplitude, persistence, number of octaves, and scaling factors.
|
|
||||||
|
|
||||||
## 3. Set Up Your Rust Project
|
|
||||||
- Create a new project with Cargo.
|
|
||||||
- Add necessary dependencies in `Cargo.toml` (e.g., the `noise` crate).
|
|
||||||
- Set up version control (Git).
|
|
||||||
|
|
||||||
## 4. Implement Basic Noise Generation
|
|
||||||
- Write a simple prototype to generate noise values.
|
|
||||||
- Use a scaling factor to avoid sampling only on integer coordinates.
|
|
||||||
- Test with a fixed seed for reproducibility.
|
|
||||||
|
|
||||||
## 5. Implement Fractal Brownian Motion (fBm)
|
|
||||||
- Create a function to combine multiple noise octaves.
|
|
||||||
- Adjust parameters (octaves, persistence, etc.) and test the results.
|
|
||||||
|
|
||||||
## 6. Map Noise to Terrain Height
|
|
||||||
- Convert normalized noise values (e.g., from -1 to 1) to block heights.
|
|
||||||
- Define a mapping strategy (for example, scaling to a maximum height).
|
|
||||||
|
|
||||||
## 7. Design the Chunk Data Structure
|
|
||||||
- Decide on chunk dimensions (e.g., 16×16×128).
|
|
||||||
- Create a simple structure to represent blocks (using enums or similar).
|
|
||||||
|
|
||||||
## 8. Generate Chunks Based on Noise
|
|
||||||
- For each (x, z) coordinate in a chunk:
|
|
||||||
- Calculate the noise value.
|
|
||||||
- Map it to a terrain height.
|
|
||||||
- Fill in blocks based on the height (e.g., surface, sub-surface, stone).
|
|
||||||
- Keep the code modular and avoid overcomplicating early on.
|
|
||||||
|
|
||||||
## 9. Test and Visualize the Generated Terrain
|
|
||||||
- Write unit tests for noise functions and terrain mapping.
|
|
||||||
- Create a simple visualization (e.g., a 2D height map printed to the console or exporting data for external tools).
|
|
||||||
- Verify that parameter adjustments produce the expected variations.
|
|
||||||
|
|
||||||
## 10. Integrate the Generator into Your Server Architecture
|
|
||||||
- Implement on-demand chunk generation as the player moves.
|
|
||||||
- Cache generated chunks (in memory or on disk) to avoid re-computation.
|
|
||||||
- Consider multithreading or asynchronous processing for parallel generation.
|
|
||||||
|
|
||||||
## 11. Optimize and Refine
|
|
||||||
- Profile the terrain generation for performance bottlenecks.
|
|
||||||
- Fine-tune noise parameters and mapping logic.
|
|
||||||
- Plan future enhancements (biomes, caves, advanced block types).
|
|
||||||
|
|
||||||
## 12. Document and Maintain the Codebase
|
|
||||||
- Document your functions, parameters, and overall architecture.
|
|
||||||
- Use version control to track changes and manage iterative improvements.
|
|
||||||
- Keep your code modular for easy future enhancements.
|
|
||||||
|
|
||||||
80
src/level.rs
Normal file
80
src/level.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
use nbt::{Tag, write_nbt};
|
||||||
|
|
||||||
|
pub fn create_nbt(
|
||||||
|
name: String,
|
||||||
|
seed: i64,
|
||||||
|
is_hardcore: bool,
|
||||||
|
structures: bool,
|
||||||
|
raining: bool,
|
||||||
|
thundering: bool,
|
||||||
|
game_type: i32,
|
||||||
|
generator_version: i32,
|
||||||
|
raintime: i32,
|
||||||
|
spawnx: i32,
|
||||||
|
spawny: i32,
|
||||||
|
spawnz: i32,
|
||||||
|
thundertime: i32,
|
||||||
|
version: i32,
|
||||||
|
last_played: i64,
|
||||||
|
disk_size: i64,
|
||||||
|
time: i64,
|
||||||
|
generator_name: String,
|
||||||
|
level_name: String,
|
||||||
|
path: String,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let mut root = Tag::new_compound(name);
|
||||||
|
root.insert("RandomSeed".to_string(), Tag::new_long("RandomSeed", seed));
|
||||||
|
root.insert(
|
||||||
|
"hardcore".to_string(),
|
||||||
|
Tag::new_byte("hardcore", i8::from(is_hardcore)),
|
||||||
|
);
|
||||||
|
root.insert(
|
||||||
|
"MapFeatures".to_string(),
|
||||||
|
Tag::new_byte("MapFeatures", i8::from(structures)),
|
||||||
|
);
|
||||||
|
root.insert(
|
||||||
|
"raining".to_string(),
|
||||||
|
Tag::new_byte("raining", i8::from(raining)),
|
||||||
|
);
|
||||||
|
root.insert(
|
||||||
|
"thundering".to_string(),
|
||||||
|
Tag::new_byte("thundering", i8::from(thundering)),
|
||||||
|
);
|
||||||
|
root.insert("GameType".to_string(), Tag::new_int("GameType", game_type));
|
||||||
|
root.insert(
|
||||||
|
"GeneratorVersion".to_string(),
|
||||||
|
Tag::new_int("GeneratorVersion", generator_version),
|
||||||
|
);
|
||||||
|
root.insert("RainTime".to_string(), Tag::new_int("RainTime", raintime));
|
||||||
|
root.insert("SpawnX".to_string(), Tag::new_int("SpawnX", spawnx));
|
||||||
|
root.insert("SpawnY".to_string(), Tag::new_int("SpawnY", spawny));
|
||||||
|
root.insert("SpawnZ".to_string(), Tag::new_int("SpawnZ", spawnz));
|
||||||
|
root.insert(
|
||||||
|
"ThunderTime".to_string(),
|
||||||
|
Tag::new_int("ThunderTime", thundertime),
|
||||||
|
);
|
||||||
|
root.insert("Version".to_string(), Tag::new_int("Version", version));
|
||||||
|
root.insert(
|
||||||
|
"LastPlayed".to_string(),
|
||||||
|
Tag::new_long("LastPlayed", last_played),
|
||||||
|
); // Not sure if I should keep LastPlayed field because of the fact that this isn't for a
|
||||||
|
// client world but for a server world
|
||||||
|
root.insert(
|
||||||
|
"SizeOnDisk".to_string(),
|
||||||
|
Tag::new_long("SizeOnDisk", disk_size),
|
||||||
|
);
|
||||||
|
root.insert("Time".to_string(), Tag::new_long("Time", time));
|
||||||
|
root.insert(
|
||||||
|
"GeneratorName".to_string(),
|
||||||
|
Tag::new_string("GeneratorName", generator_name),
|
||||||
|
);
|
||||||
|
root.insert(
|
||||||
|
"LevelName".to_string(),
|
||||||
|
Tag::new_string("LevelName", level_name),
|
||||||
|
);
|
||||||
|
let file = File::create(path)?;
|
||||||
|
write_nbt(&root, file)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
|
mod level;
|
||||||
mod perlin;
|
mod perlin;
|
||||||
mod superflat;
|
mod superflat;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use rand::{
|
|||||||
};
|
};
|
||||||
use rand_chacha::ChaCha8Rng;
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 16;
|
pub const CHUNK_SIZE: usize = 16;
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Noise {
|
pub struct Noise {
|
||||||
scale: f32,
|
scale: f32,
|
||||||
@@ -18,9 +18,9 @@ pub struct Vector {
|
|||||||
y: f32,
|
y: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIN_Y: i32 = -64;
|
pub const MIN_Y: i32 = -64;
|
||||||
const MAX_Y: i32 = 320;
|
pub const MAX_Y: i32 = 320;
|
||||||
const SEA_LEVEL: i32 = 63;
|
pub const SEA_LEVEL: i32 = 63;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fbm_seeded(
|
fn fbm_seeded(
|
||||||
@@ -211,13 +211,15 @@ fn linear_interpolation(a: f32, b: f32, t: f32) -> f32 {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::perlin::{
|
||||||
|
Noise, Vector, calculate_norm, dot_product, fade, linear_interpolation, normalize,
|
||||||
|
};
|
||||||
|
|
||||||
fn deriv(f: fn(f32) -> f32, x: f32) -> f32 {
|
fn deriv(f: fn(f32) -> f32, x: f32) -> f32 {
|
||||||
let h = 1e-3;
|
let h = 1e-3;
|
||||||
(f(x + h) - f(x - h)) / (2.0 * h)
|
(f(x + h) - f(x - h)) / (2.0 * h)
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn approx_eq(a: f32, b: f32, eps: f32) -> bool {
|
fn approx_eq(a: f32, b: f32, eps: f32) -> bool {
|
||||||
(a - b).abs() <= eps
|
(a - b).abs() <= eps
|
||||||
}
|
}
|
||||||
@@ -436,49 +438,3 @@ mod perlin_tests {
|
|||||||
assert!((n.get(x, y + h) - c0).abs() < 0.1);
|
assert!((n.get(x, y + h) - c0).abs() < 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod viz_chunk2 {
|
|
||||||
use super::*;
|
|
||||||
use image::{ImageBuffer, Luma};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn dump_chunk_png() {
|
|
||||||
let seed: u64 = 42;
|
|
||||||
let chunks_x: usize = 32;
|
|
||||||
let chunks_z: usize = 32;
|
|
||||||
|
|
||||||
let w: u32 = (chunks_x * CHUNK_SIZE) as u32;
|
|
||||||
let h: u32 = (chunks_z * CHUNK_SIZE) as u32;
|
|
||||||
|
|
||||||
let mut field = vec![0i32; (w as usize) * (h as usize)];
|
|
||||||
|
|
||||||
for cz in 0..chunks_z {
|
|
||||||
for cx in 0..chunks_x {
|
|
||||||
let tile = generate_height_chunk(seed, cx as i32, cz as i32);
|
|
||||||
for lz in 0..CHUNK_SIZE {
|
|
||||||
for lx in 0..CHUNK_SIZE {
|
|
||||||
let x = cx * CHUNK_SIZE + lx;
|
|
||||||
let z = cz * CHUNK_SIZE + lz;
|
|
||||||
field[z * (w as usize) + x] = tile[lx][lz];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut img: ImageBuffer<Luma<u16>, Vec<u16>> = ImageBuffer::new(w, h);
|
|
||||||
let denom = (MAX_Y - MIN_Y) as f32;
|
|
||||||
for z in 0..h {
|
|
||||||
for x in 0..w {
|
|
||||||
let v = field[(z as usize) * (w as usize) + (x as usize)];
|
|
||||||
let n01 = ((v - MIN_Y) as f32 / denom).clamp(0.0, 1.0);
|
|
||||||
let p = (n01 * u16::MAX as f32).round() as u16;
|
|
||||||
img.put_pixel(x, z, Luma([p]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fs::create_dir_all("target").ok();
|
|
||||||
img.save("target/heightmap16.png").unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
0
src/player.rs
Normal file
0
src/player.rs
Normal file
0
src/region.rs
Normal file
0
src/region.rs
Normal file
78
src/test.rs
Normal file
78
src/test.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/// Test for perlin.rs
|
||||||
|
#[cfg(test)]
|
||||||
|
mod perlin_test {
|
||||||
|
use crate::perlin::{CHUNK_SIZE, MAX_Y, MIN_Y, generate_height_chunk};
|
||||||
|
|
||||||
|
use image::{ImageBuffer, Luma};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn dump_chunk_png() {
|
||||||
|
let seed: u64 = 42;
|
||||||
|
let chunks_x: usize = 32;
|
||||||
|
let chunks_z: usize = 32;
|
||||||
|
|
||||||
|
let w: u32 = (chunks_x * CHUNK_SIZE) as u32;
|
||||||
|
let h: u32 = (chunks_z * CHUNK_SIZE) as u32;
|
||||||
|
|
||||||
|
let mut field = vec![0i32; (w as usize) * (h as usize)];
|
||||||
|
|
||||||
|
for cz in 0..chunks_z {
|
||||||
|
for cx in 0..chunks_x {
|
||||||
|
let tile = generate_height_chunk(seed, cx as i32, cz as i32);
|
||||||
|
for lz in 0..CHUNK_SIZE {
|
||||||
|
for lx in 0..CHUNK_SIZE {
|
||||||
|
let x = cx * CHUNK_SIZE + lx;
|
||||||
|
let z = cz * CHUNK_SIZE + lz;
|
||||||
|
field[z * (w as usize) + x] = tile[lx][lz];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut img: ImageBuffer<Luma<u16>, Vec<u16>> = ImageBuffer::new(w, h);
|
||||||
|
let denom = (MAX_Y - MIN_Y) as f32;
|
||||||
|
for z in 0..h {
|
||||||
|
for x in 0..w {
|
||||||
|
let v = field[(z as usize) * (w as usize) + (x as usize)];
|
||||||
|
let n01 = ((v - MIN_Y) as f32 / denom).clamp(0.0, 1.0);
|
||||||
|
let p = (n01 * u16::MAX as f32).round() as u16;
|
||||||
|
img.put_pixel(x, z, Luma([p]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::create_dir_all("target").ok();
|
||||||
|
img.save("target/heightmap16.png").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Test for level.rs
|
||||||
|
#[cfg(test)]
|
||||||
|
mod level_file_test {
|
||||||
|
use crate::level::create_nbt;
|
||||||
|
#[test]
|
||||||
|
fn test_creation_of_file() -> () {
|
||||||
|
let result = create_nbt(
|
||||||
|
"test".to_string(),
|
||||||
|
1234,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
213,
|
||||||
|
1235,
|
||||||
|
55555,
|
||||||
|
90000,
|
||||||
|
900,
|
||||||
|
"test".to_string(),
|
||||||
|
"test".to_string(),
|
||||||
|
"target/level.dat".to_string(),
|
||||||
|
);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user