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: &(impl Fn(BiomeRef) -> bool + ?Sized),
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 {
370 seed: u64,
371 end_islands: EndIslands,
372}
373
374impl EndBiomeSource {
375 #[must_use]
381 pub fn new(seed: u64) -> Self {
382 Self {
383 seed,
384 end_islands: EndIslands::new(seed),
385 }
386 }
387
388 #[must_use]
390 pub const fn seed(&self) -> u64 {
391 self.seed
392 }
393
394 fn chunk_sampler(&self) -> ChunkBiomeSampler<'_> {
395 ChunkBiomeSampler::End(Box::new(EndChunkBiomeSampler {
396 source: self,
397 cached_erosion: None,
398 }))
399 }
400}
401
402pub struct EndChunkBiomeSampler<'a> {
403 source: &'a EndBiomeSource,
404 cached_erosion: Option<(i32, i32, f64)>,
410}
411
412impl EndChunkBiomeSampler<'_> {
413 fn get_erosion(&mut self, chunk_x: i32, chunk_z: i32) -> f64 {
414 if let Some((cx, cz, erosion)) = self.cached_erosion
415 && cx == chunk_x
416 && cz == chunk_z
417 {
418 return erosion;
419 }
420 let weird_block_x = f64::from((chunk_x * 2 + 1) * 8);
421 let weird_block_z = f64::from((chunk_z * 2 + 1) * 8);
422 let erosion = self
423 .source
424 .end_islands
425 .sample(weird_block_x, 0.0, weird_block_z);
426 self.cached_erosion = Some((chunk_x, chunk_z, erosion));
427 erosion
428 }
429
430 fn sample(&mut self, quart_x: i32, _quart_y: i32, quart_z: i32) -> BiomeRef {
431 let block_x = quart_x << 2;
432 let block_z = quart_z << 2;
433 let chunk_x = block_x >> 4;
434 let chunk_z = block_z >> 4;
435
436 if i64::from(chunk_x) * i64::from(chunk_x) + i64::from(chunk_z) * i64::from(chunk_z) <= 4096
438 {
439 return &vanilla_biomes::THE_END;
440 }
441
442 let erosion = self.get_erosion(chunk_x, chunk_z);
443
444 if erosion > 0.25 {
445 &vanilla_biomes::END_HIGHLANDS
446 } else if erosion >= -0.0625 {
447 &vanilla_biomes::END_MIDLANDS
448 } else if erosion < -0.21875 {
449 &vanilla_biomes::SMALL_END_ISLANDS
450 } else {
451 &vanilla_biomes::END_BARRENS
452 }
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn end_possible_biomes_follow_vanilla_order() {
462 let source = BiomeSourceKind::end(0);
463 let keys = source
464 .possible_biome_refs()
465 .into_iter()
466 .map(|biome| &biome.key)
467 .collect::<Vec<_>>();
468
469 assert_eq!(
470 keys,
471 vec![
472 &vanilla_biomes::THE_END.key,
473 &vanilla_biomes::END_HIGHLANDS.key,
474 &vanilla_biomes::END_MIDLANDS.key,
475 &vanilla_biomes::SMALL_END_ISLANDS.key,
476 &vanilla_biomes::END_BARRENS.key,
477 ]
478 );
479 }
480}