1use rustc_hash::FxHashSet;
20use steel_registry::biome::BiomeRef;
21use steel_registry::vanilla_biomes;
22use steel_utils::random::Random as _;
23use steel_utils::random::legacy_random::LegacyRandom;
24use steel_utils::{BlockPos, Identifier};
25use steel_worldgen::density_functions::nether::NetherColumnCache;
26use steel_worldgen::density_functions::overworld::OverworldColumnCache;
27use steel_worldgen::multi_noise::{
28 NETHER_BIOME_PARAMETERS, OVERWORLD_BIOME_PARAMETERS, get_nether_biome_cached,
29 get_overworld_biome_cached,
30};
31
32use super::{NetherClimateSampler, OverworldClimateSampler};
33use steel_worldgen::noise::EndIslands;
34
35#[non_exhaustive]
40pub enum BiomeSourceKind {
41 Overworld(OverworldBiomeSource),
43 Nether(NetherBiomeSource),
45 End(Box<EndBiomeSource>),
50}
51
52impl BiomeSourceKind {
53 #[must_use]
55 pub fn overworld(seed: u64) -> Self {
56 Self::Overworld(OverworldBiomeSource::new(seed))
57 }
58
59 #[must_use]
61 pub fn nether(seed: u64) -> Self {
62 Self::Nether(NetherBiomeSource::new(seed))
63 }
64
65 #[must_use]
67 pub fn end(seed: u64) -> Self {
68 Self::End(Box::new(EndBiomeSource::new(seed)))
69 }
70
71 #[must_use]
78 pub fn possible_biome_refs(&self) -> Vec<BiomeRef> {
79 let biomes: Vec<BiomeRef> = match self {
80 Self::Overworld(_) => OVERWORLD_BIOME_PARAMETERS
81 .values()
82 .iter()
83 .map(|(_, biome)| *biome)
84 .collect(),
85 Self::Nether(_) => NETHER_BIOME_PARAMETERS
86 .values()
87 .iter()
88 .map(|(_, biome)| *biome)
89 .collect(),
90 Self::End(_) => vec![
91 &vanilla_biomes::THE_END,
92 &vanilla_biomes::END_HIGHLANDS,
93 &vanilla_biomes::END_MIDLANDS,
94 &vanilla_biomes::SMALL_END_ISLANDS,
95 &vanilla_biomes::END_BARRENS,
96 ],
97 };
98
99 distinct_biome_refs(biomes)
100 }
101
102 #[must_use]
105 pub fn possible_biomes(&self) -> FxHashSet<Identifier> {
106 self.possible_biome_refs()
107 .into_iter()
108 .map(|biome| biome.key.clone())
109 .collect()
110 }
111
112 #[must_use]
117 pub fn chunk_sampler(&self) -> ChunkBiomeSampler<'_> {
118 match self {
119 Self::Overworld(source) => source.chunk_sampler(),
120 Self::Nether(source) => source.chunk_sampler(),
121 Self::End(source) => source.chunk_sampler(),
122 }
123 }
124
125 #[must_use]
128 pub fn find_biome_horizontal(
129 &self,
130 origin_x: i32,
131 origin_z: i32,
132 search_radius: i32,
133 allowed: &dyn Fn(BiomeRef) -> bool,
134 rng: &mut LegacyRandom,
135 ) -> Option<(i32, i32)> {
136 let mut sampler = self.chunk_sampler();
137 let noise_center_x = origin_x >> 2;
139 let noise_center_z = origin_z >> 2;
140 let noise_radius = search_radius >> 2;
141
142 let mut result: Option<(i32, i32)> = None;
143 let mut found = 0;
144 for z in -noise_radius..=noise_radius {
145 for x in -noise_radius..=noise_radius {
146 let nx = noise_center_x + x;
147 let nz = noise_center_z + z;
148 if allowed(sampler.sample(nx, 0, nz)) {
149 if result.is_none() || rng.next_i32_bounded(found + 1) == 0 {
151 result = Some((nx << 2, nz << 2));
152 }
153 found += 1;
154 }
155 }
156 }
157 result
158 }
159
160 #[must_use]
162 pub fn initial_spawn_search_origin(&self) -> BlockPos {
163 match self {
164 Self::Overworld(source) => source.climate_sampler().find_spawn_position(),
165 Self::Nether(_) | Self::End(_) => BlockPos::new(0, 0, 0),
166 }
167 }
168}
169
170fn distinct_biome_refs(biomes: Vec<BiomeRef>) -> Vec<BiomeRef> {
171 let mut seen = FxHashSet::default();
172 let mut distinct = Vec::with_capacity(biomes.len());
173
174 for biome in biomes {
175 if seen.insert(&biome.key) {
176 distinct.push(biome);
177 }
178 }
179
180 distinct
181}
182
183pub enum ChunkBiomeSampler<'a> {
192 Overworld(Box<OverworldChunkBiomeSampler<'a>>),
194 Nether(Box<NetherChunkBiomeSampler<'a>>),
196 End(Box<EndChunkBiomeSampler<'a>>),
198}
199
200impl ChunkBiomeSampler<'_> {
201 #[inline]
203 pub fn sample(&mut self, quart_x: i32, quart_y: i32, quart_z: i32) -> BiomeRef {
204 match self {
205 Self::Overworld(s) => s.sample(quart_x, quart_y, quart_z),
206 Self::Nether(s) => s.sample(quart_x, quart_y, quart_z),
207 Self::End(s) => s.sample(quart_x, quart_y, quart_z),
208 }
209 }
210
211 pub fn init_grid(&mut self, chunk_block_x: i32, chunk_block_z: i32) {
220 match self {
221 Self::Overworld(s) => s.init_grid(chunk_block_x, chunk_block_z),
222 Self::Nether(s) => s.init_grid(chunk_block_x, chunk_block_z),
223 Self::End(_) => {}
224 }
225 }
226}
227
228pub struct OverworldBiomeSource {
235 seed: u64,
236 climate_sampler: OverworldClimateSampler,
237}
238
239impl OverworldBiomeSource {
240 #[must_use]
242 pub fn new(seed: u64) -> Self {
243 Self {
244 seed,
245 climate_sampler: OverworldClimateSampler::new(seed),
246 }
247 }
248
249 #[must_use]
251 pub const fn seed(&self) -> u64 {
252 self.seed
253 }
254
255 #[must_use]
257 pub const fn climate_sampler(&self) -> &OverworldClimateSampler {
258 &self.climate_sampler
259 }
260
261 fn chunk_sampler(&self) -> ChunkBiomeSampler<'_> {
262 ChunkBiomeSampler::Overworld(Box::new(OverworldChunkBiomeSampler {
263 source: self,
264 column_cache: OverworldColumnCache::new(),
265 biome_cache: None,
266 }))
267 }
268}
269
270pub struct OverworldChunkBiomeSampler<'a> {
271 source: &'a OverworldBiomeSource,
272 column_cache: OverworldColumnCache,
273 biome_cache: Option<usize>,
274}
275
276impl OverworldChunkBiomeSampler<'_> {
277 fn sample(&mut self, quart_x: i32, quart_y: i32, quart_z: i32) -> BiomeRef {
278 let target =
279 self.source
280 .climate_sampler
281 .sample(quart_x, quart_y, quart_z, &mut self.column_cache);
282 get_overworld_biome_cached(&target, &mut self.biome_cache)
283 }
284
285 fn init_grid(&mut self, chunk_block_x: i32, chunk_block_z: i32) {
286 self.source.climate_sampler.init_column_grid(
287 &mut self.column_cache,
288 chunk_block_x,
289 chunk_block_z,
290 );
291 }
292}
293
294pub struct NetherBiomeSource {
303 seed: u64,
304 climate_sampler: NetherClimateSampler,
305}
306
307impl NetherBiomeSource {
308 #[must_use]
310 pub fn new(seed: u64) -> Self {
311 Self {
312 seed,
313 climate_sampler: NetherClimateSampler::new(seed),
314 }
315 }
316
317 #[must_use]
319 pub const fn seed(&self) -> u64 {
320 self.seed
321 }
322
323 fn chunk_sampler(&self) -> ChunkBiomeSampler<'_> {
324 ChunkBiomeSampler::Nether(Box::new(NetherChunkBiomeSampler {
325 source: self,
326 column_cache: NetherColumnCache::new(),
327 biome_cache: None,
328 }))
329 }
330}
331
332pub struct NetherChunkBiomeSampler<'a> {
333 source: &'a NetherBiomeSource,
334 column_cache: NetherColumnCache,
335 biome_cache: Option<usize>,
336}
337
338impl NetherChunkBiomeSampler<'_> {
339 fn sample(&mut self, quart_x: i32, quart_y: i32, quart_z: i32) -> BiomeRef {
340 let target =
341 self.source
342 .climate_sampler
343 .sample(quart_x, quart_y, quart_z, &mut self.column_cache);
344 get_nether_biome_cached(&target, &mut self.biome_cache)
345 }
346
347 fn init_grid(&mut self, chunk_block_x: i32, chunk_block_z: i32) {
348 self.source.climate_sampler.init_column_grid(
349 &mut self.column_cache,
350 chunk_block_x,
351 chunk_block_z,
352 );
353 }
354}
355
356pub struct EndBiomeSource {
372 seed: u64,
373 end_islands: EndIslands,
374}
375
376impl EndBiomeSource {
377 #[must_use]
383 pub fn new(seed: u64) -> Self {
384 Self {
385 seed,
386 end_islands: EndIslands::new(seed),
387 }
388 }
389
390 #[must_use]
392 pub const fn seed(&self) -> u64 {
393 self.seed
394 }
395
396 fn chunk_sampler(&self) -> ChunkBiomeSampler<'_> {
397 ChunkBiomeSampler::End(Box::new(EndChunkBiomeSampler {
398 source: self,
399 cached_erosion: None,
400 }))
401 }
402}
403
404pub struct EndChunkBiomeSampler<'a> {
405 source: &'a EndBiomeSource,
406 cached_erosion: Option<(i32, i32, f64)>,
412}
413
414impl EndChunkBiomeSampler<'_> {
415 fn get_erosion(&mut self, chunk_x: i32, chunk_z: i32) -> f64 {
416 if let Some((cx, cz, erosion)) = self.cached_erosion
417 && cx == chunk_x
418 && cz == chunk_z
419 {
420 return erosion;
421 }
422 let weird_block_x = f64::from((chunk_x * 2 + 1) * 8);
423 let weird_block_z = f64::from((chunk_z * 2 + 1) * 8);
424 let erosion = self
425 .source
426 .end_islands
427 .sample(weird_block_x, 0.0, weird_block_z);
428 self.cached_erosion = Some((chunk_x, chunk_z, erosion));
429 erosion
430 }
431
432 fn sample(&mut self, quart_x: i32, _quart_y: i32, quart_z: i32) -> BiomeRef {
433 let block_x = quart_x << 2;
434 let block_z = quart_z << 2;
435 let chunk_x = block_x >> 4;
436 let chunk_z = block_z >> 4;
437
438 if i64::from(chunk_x) * i64::from(chunk_x) + i64::from(chunk_z) * i64::from(chunk_z) <= 4096
440 {
441 return &vanilla_biomes::THE_END;
442 }
443
444 let erosion = self.get_erosion(chunk_x, chunk_z);
445
446 if erosion > 0.25 {
447 &vanilla_biomes::END_HIGHLANDS
448 } else if erosion >= -0.0625 {
449 &vanilla_biomes::END_MIDLANDS
450 } else if erosion < -0.21875 {
451 &vanilla_biomes::SMALL_END_ISLANDS
452 } else {
453 &vanilla_biomes::END_BARRENS
454 }
455 }
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn end_possible_biomes_follow_vanilla_order() {
464 let source = BiomeSourceKind::end(0);
465 let keys = source
466 .possible_biome_refs()
467 .into_iter()
468 .map(|biome| &biome.key)
469 .collect::<Vec<_>>();
470
471 assert_eq!(
472 keys,
473 vec![
474 &vanilla_biomes::THE_END.key,
475 &vanilla_biomes::END_HIGHLANDS.key,
476 &vanilla_biomes::END_MIDLANDS.key,
477 &vanilla_biomes::SMALL_END_ISLANDS.key,
478 &vanilla_biomes::END_BARRENS.key,
479 ]
480 );
481 }
482}