use std::{ sync::atomic::{AtomicU64, Ordering}, time, }; use legacy_rand::{LegacyRand, LegacySplitter}; use worldgen_random::WorldgenRandom; use xoroshiro128::{Xoroshiro, XoroshiroSplitter}; mod gaussian; pub mod legacy_rand; pub mod worldgen_random; pub mod xoroshiro128; /// Global seed uniquifier used to generate unique seeds based on time. static SEED_UNIQUIFIER: AtomicU64 = AtomicU64::new(8682522807148012u64); pub fn get_seed() -> u64 { let seed = SEED_UNIQUIFIER .try_update(Ordering::Relaxed, Ordering::Relaxed, |val| { Some(val.wrapping_mul(1181783497276652981u64)) }) .unwrap_or(0); let nanos = time::SystemTime::now() .duration_since(time::SystemTime::UNIX_EPOCH) .map_or(0, |d| d.as_nanos()); let nano_upper = (nanos >> 8) as u64; let nano_lower = nanos as u64; seed ^ nano_upper ^ nano_lower } pub enum RandomGenerator { /// Xoroshiro128+ random number generator (modern, fast implementation). Xoroshiro(Xoroshiro), /// Xoroshiro wrapped with Java `WorldgenRandom` bit-source semantics. Worldgen(WorldgenRandom), /// Legacy random number generator (compatible with older Minecraft versions). Legacy(LegacyRand), } /// Unified random number deriver enum for creating child RNGs. pub enum RandomDeriver { /// Xoroshiro splitter implementation. Xoroshiro(XoroshiroSplitter), /// Legacy splitter implementation. Legacy(LegacySplitter), } // TODO: Write unit test for this #[macro_export] macro_rules! population_seed_fn { () => { /// Generates a population seed for structure placement. /// /// Population seeds are used to determine the placement of structures /// like villages, temples, and other features. They combine the world seed /// with block coordinates to create a unique seed for each chunk. /// /// # Arguments /// - `world_seed` – The base world seed. /// - `block_x` – The X block coordinate. /// - `block_z` – The Z block coordinate. /// /// # Returns /// A population seed for the given location. pub fn get_population_seed(world_seed: u64, block_x: i32, block_z: i32) -> u64 { let mut rand = Self::from_seed(world_seed); let l = rand.next_i64() | 1; let m = rand.next_i64() | 1; let base = (block_x as i64) .wrapping_mul(l) .wrapping_add((block_z as i64).wrapping_mul(m)); (base as u64) ^ world_seed } }; } /// Generates a decorator seed for feature placement. /// /// Decorator seeds are used for placing individual features like trees, /// flowers, and ores within a chunk. /// /// # Arguments /// - `population_seed` – The base population seed for the area. /// - `index` – The index of the decorator. /// - `step` – The decoration step number. /// /// # Returns /// A decorator seed for the given parameters. // TODO: Write unit test for this #[inline] #[must_use] pub const fn get_decorator_seed(population_seed: u64, index: u64, step: u64) -> u64 { population_seed .wrapping_add(index) .wrapping_add(10_000u64.wrapping_mul(step)) } /// Generates a region seed for large-scale feature placement. /// /// Region seeds are used for features that span multiple chunks, such as /// slime chunks or specific biome placements. /// /// # Arguments /// - `world_seed` – The base world seed. /// - `region_x` – The X region coordinate. /// - `region_z` – The Z region coordinate. /// - `salt` – A salt value to make the seed unique for specific features. /// /// # Returns /// A region seed for the given location. #[inline] #[must_use] pub fn get_region_seed(world_seed: u64, region_x: i32, region_z: i32, salt: u32) -> u64 { let x_part = i64::from(region_x).wrapping_mul(341873128712) as u64; let z_part = i64::from(region_z).wrapping_mul(132897987541) as u64; world_seed .wrapping_add(x_part) .wrapping_add(z_part) .wrapping_add(i64::from(salt) as u64) } /// Generates a seed for slime chunk determination. /// /// This follows Minecraft's specific formula for identifying if a chunk /// is a "slime chunk" where slimes can spawn regardless of light levels. /// /// # Arguments /// - `x` – The X chunk coordinate. /// - `z` – The Z chunk coordinate. /// - `seed` – The world seed. /// - `salt` – A salt value (default is 987234911). /// /// # Returns /// A seed value to be used with a legacy random generator. #[inline] #[must_use] pub const fn seed_slime_chunk(x: i32, z: i32, seed: u64, salt: u64) -> u64 { (seed .wrapping_add((x.wrapping_mul(x).wrapping_mul(4_987_142)) as i64 as u64) .wrapping_add((x.wrapping_mul(5_947_611)) as i64 as u64) .wrapping_add((z.wrapping_mul(z) as i64).wrapping_mul(4_392_871) as u64) .wrapping_add((z.wrapping_mul(389_711)) as i64 as u64)) ^ salt } /// Generates a carver seed for cave and ravine generation. /// /// Carver seeds are used for terrain carving features like caves and ravines. /// /// # Arguments /// - `world_seed` – The base world seed (plus carver index). /// - `chunk_x` – The X chunk coordinate. /// - `chunk_z` – The Z chunk coordinate. /// /// # Returns /// A carver seed for the given chunk. #[inline] #[must_use] pub fn get_carver_seed(world_seed: u64, chunk_x: i32, chunk_z: i32) -> u64 { let mut random = LegacyRand::from_seed(world_seed); let l = random.next_i64() | 1; let m = random.next_i64() | 1; ((chunk_x as i64) .wrapping_mul(l) .wrapping_add((chunk_z as i64).wrapping_mul(m)) as u64) ^ world_seed } /// Generates a large feature seed for structure selection and placement. /// /// Matches vanilla Minecraft's `WorldgenRandom.setLargeFeatureSeed`. /// /// # Arguments /// - `world_seed` – The base world seed. /// - `chunk_x` – The X chunk coordinate. /// - `chunk_z` – The Z chunk coordinate. /// /// # Returns /// A large feature seed for the given chunk. #[inline] #[must_use] pub fn get_large_feature_seed(world_seed: u64, chunk_x: i32, chunk_z: i32) -> u64 { let mut random = LegacyRand::from_seed(world_seed); let x_scale = random.next_i64(); let z_scale = random.next_i64(); ((chunk_x as i64).wrapping_mul(x_scale) ^ (chunk_z as i64).wrapping_mul(z_scale) ^ (world_seed as i64)) as u64 } #[expect(clippy::return_self_not_must_use)] pub trait RandomImpl { fn split(&mut self) -> Self; fn next_splitter(&mut self) -> RandomDeriver; fn next_i32(&mut self) -> i32; fn next_bounded_i32(&mut self, bound: i32) -> i32; fn next_inbetween_i32(&mut self, min: i32, max: i32) -> i32 { if min >= max { return min; } self.next_bounded_i32(max - min + 1) + min } fn next_inbetween_f32(&mut self, min: f32, max: f32) -> f32 { self.next_f32() * (max - min) + min } fn next_i64(&mut self) -> i64; fn next_bool(&mut self) -> bool; fn next_f32(&mut self) -> f32; fn next_f64(&mut self) -> f64; fn next_gaussian(&mut self) -> f64; #[allow(clippy::suboptimal_flops)] fn next_triangular(&mut self, mode: f64, deviation: f64) -> f64 { mode + deviation * (self.next_f64() - self.next_f64()) } fn skip(&mut self, count: i32) { for _ in 0..count { self.next_i64(); } } fn next_inbetween_i32_exclusive(&mut self, min: i32, max: i32) -> i32 { if min >= max { return min; } min + self.next_bounded_i32(max - min) } } impl RandomImpl for RandomGenerator { #[inline] fn split(&mut self) -> Self { match self { Self::Xoroshiro(x) => Self::Xoroshiro(x.split()), Self::Worldgen(x) => Self::Worldgen(x.split()), Self::Legacy(l) => Self::Legacy(l.split()), } } #[inline] fn next_splitter(&mut self) -> RandomDeriver { match self { Self::Xoroshiro(x) => RandomDeriver::Xoroshiro(x.next_splitter()), Self::Worldgen(x) => x.next_splitter(), Self::Legacy(l) => l.next_splitter(), } } #[inline] fn next_i32(&mut self) -> i32 { match self { Self::Xoroshiro(x) => x.next_i32(), Self::Worldgen(x) => x.next_i32(), Self::Legacy(l) => l.next_i32(), } } #[inline] fn next_bounded_i32(&mut self, bound: i32) -> i32 { match self { Self::Xoroshiro(x) => x.next_bounded_i32(bound), Self::Worldgen(x) => x.next_bounded_i32(bound), Self::Legacy(l) => l.next_bounded_i32(bound), } } #[inline] fn next_i64(&mut self) -> i64 { match self { Self::Xoroshiro(x) => x.next_i64(), Self::Worldgen(x) => x.next_i64(), Self::Legacy(l) => l.next_i64(), } } #[inline] fn next_bool(&mut self) -> bool { match self { Self::Xoroshiro(x) => x.next_bool(), Self::Worldgen(x) => x.next_bool(), Self::Legacy(l) => l.next_bool(), } } #[inline] fn next_f32(&mut self) -> f32 { match self { Self::Xoroshiro(x) => x.next_f32(), Self::Worldgen(x) => x.next_f32(), Self::Legacy(l) => l.next_f32(), } } #[inline] fn next_f64(&mut self) -> f64 { match self { Self::Xoroshiro(x) => x.next_f64(), Self::Worldgen(x) => x.next_f64(), Self::Legacy(l) => l.next_f64(), } } #[inline] fn next_gaussian(&mut self) -> f64 { match self { Self::Xoroshiro(x) => x.next_gaussian(), Self::Worldgen(x) => x.next_gaussian(), Self::Legacy(l) => l.next_gaussian(), } } #[inline] fn skip(&mut self, count: i32) { match self { Self::Xoroshiro(x) => x.skip(count), Self::Worldgen(x) => x.skip(count), Self::Legacy(l) => l.skip(count), } } } pub trait RandomDeriverImpl { fn split_string(&self, seed: &str) -> RandomGenerator; fn split_u64(&self, seed: u64) -> RandomGenerator; fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator; } impl RandomDeriverImpl for RandomDeriver { #[inline] fn split_string(&self, seed: &str) -> RandomGenerator { match self { Self::Xoroshiro(x) => RandomGenerator::Xoroshiro(x.split_string(seed)), Self::Legacy(l) => l.split_string(seed), } } #[inline] fn split_u64(&self, seed: u64) -> RandomGenerator { match self { Self::Xoroshiro(x) => x.split_u64(seed), Self::Legacy(l) => l.split_u64(seed), } } #[inline] fn split_pos(&self, x: i32, y: i32, z: i32) -> RandomGenerator { match self { Self::Xoroshiro(s) => RandomGenerator::Xoroshiro(s.split_pos(x, y, z)), Self::Legacy(s) => s.split_pos(x, y, z), } } } /// Hashes a block position into a 64-bit value for use in RNG seeding. /// /// This hash function is designed to produce well-distributed values for /// use as seeds for position-dependent random generation. /// /// # Arguments /// - `x` – The X coordinate. /// - `y` – The Y coordinate. /// - `z` – The Z coordinate. /// /// # Returns /// A 64-bit hash value. #[must_use] pub const fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 { let l = ((x.wrapping_mul(3129871)) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64); let l = l .wrapping_mul(l) .wrapping_mul(42317861i64) .wrapping_add(l.wrapping_mul(11i64)); l >> 16 } #[cfg(test)] mod tests { use crate::random::get_region_seed; use super::hash_block_pos; #[test] fn region_seed() { let seed = get_region_seed(12345612, 1, 1, 14357620); assert_eq!(seed, 474797819485); } #[test] fn block_position_hash() { let values: [((i32, i32, i32), i64); 8] = [ ((0, 0, 0), 0), ((1, 1, 1), 60311958971344), ((4, 4, 4), 120566413180880), ((25, 25, 25), 111753446486209), ((676, 676, 676), 75210837988243), ((458329, 458329, 458329), -43764888250), ((-387008604, -387008604, -387008604), 8437923733503), ((176771161, 176771161, 176771161), 18421337580760), ]; for ((x, y, z), value) in values { assert_eq!(hash_block_pos(x, y, z), value); } } }