Skip to main content

steel_core/worldgen/generator/
flat.rs

1use glam::IVec3;
2use rustc_hash::FxHashMap;
3use steel_registry::biome::BiomeRef;
4use steel_registry::blocks::block_state_ext::BlockStateExt;
5use steel_registry::template_pool::{TemplateData, TemplatePoolData};
6use steel_registry::{REGISTRY, RegistryExt, vanilla_biomes};
7use steel_utils::random::RandomSource;
8use steel_utils::{BlockStateId, ChunkPos, Identifier};
9
10use crate::chunk::Chunk;
11use crate::worldgen::generator::{
12    CarversPhase, ChunkGenerator, GenerationChunk, NoisePhase, SurfacePhase,
13    xoroshiro_worldgen_region_random,
14};
15use crate::worldgen::region::WorldGenRegion;
16use crate::worldgen::structure::{StructureGenerator, create_structures};
17use steel_worldgen::noise::Beardifier;
18use steel_worldgen::structure::{ColumnBlock, StructureGenerationContext};
19
20/// A chunk generator that generates a flat world.
21///
22/// Uses a fixed biome (plains) for all positions, matching vanilla's
23/// `FlatLevelSource` with `FixedBiomeSource`.
24pub struct FlatChunkGenerator {
25    /// Block layers from world bottom upwards.
26    pub layers: Vec<BlockStateId>,
27    /// The biome ID for plains (cached at construction).
28    biome_id: u16,
29    /// World seed for structure placement.
30    seed: i64,
31    /// Sea level for this flat generator's dimension type.
32    sea_level: i32,
33    /// Optional structure engine from flat structure overrides.
34    structure_generator: Option<StructureGenerator>,
35}
36
37impl FlatChunkGenerator {
38    /// Creates a new `FlatChunkGenerator`.
39    #[must_use]
40    pub fn new(bedrock: BlockStateId, dirt: BlockStateId, grass: BlockStateId) -> Self {
41        Self::new_layers(vec![bedrock, dirt, dirt, grass])
42    }
43
44    /// Creates a new flat generator with explicit block layers from bottom upwards.
45    #[must_use]
46    pub fn new_layers(layers: Vec<BlockStateId>) -> Self {
47        Self::new_layers_with_structures(layers, 0, 63, None)
48    }
49
50    /// Creates a flat generator with optional structure generation.
51    #[must_use]
52    pub(crate) fn new_layers_with_structures(
53        layers: Vec<BlockStateId>,
54        seed: i64,
55        sea_level: i32,
56        structure_generator: Option<StructureGenerator>,
57    ) -> Self {
58        let biome_id = REGISTRY
59            .biomes
60            .id_from_key(&Identifier::vanilla("plains".to_string()))
61            .unwrap_or(0) as u16;
62
63        Self {
64            layers,
65            biome_id,
66            seed,
67            sea_level,
68            structure_generator,
69        }
70    }
71}
72
73struct FlatGenerationContext<'a> {
74    seed: i64,
75    chunk_x: i32,
76    chunk_z: i32,
77    chunk_min_x: i32,
78    chunk_min_z: i32,
79    center_block_x: i32,
80    center_block_z: i32,
81    sea_level: i32,
82    min_y: i32,
83    height: i32,
84    layers: &'a [BlockStateId],
85    biome: BiomeRef,
86    template_pools: &'a FxHashMap<Identifier, TemplatePoolData>,
87    templates: &'a FxHashMap<Identifier, TemplateData>,
88    surface_y_cache: Option<i32>,
89}
90
91impl FlatGenerationContext<'_> {
92    fn state_at_y(&self, y: i32) -> Option<BlockStateId> {
93        let relative_y = y.checked_sub(self.min_y)? as usize;
94        self.layers.get(relative_y).copied()
95    }
96
97    fn is_opaque_at_y(&self, y: i32, ocean_floor: bool) -> bool {
98        let Some(state) = self.state_at_y(y) else {
99            return false;
100        };
101        if ocean_floor {
102            state.is_solid()
103        } else {
104            state.is_solid() || state.has_fluid()
105        }
106    }
107
108    fn base_height_flat(&self, ocean_floor: bool) -> i32 {
109        for y in (self.min_y..self.min_y + self.height).rev() {
110            if self.is_opaque_at_y(y, ocean_floor) {
111                return y + 1;
112            }
113        }
114        self.min_y
115    }
116}
117
118impl StructureGenerationContext for FlatGenerationContext<'_> {
119    fn seed(&self) -> i64 {
120        self.seed
121    }
122
123    fn chunk_x(&self) -> i32 {
124        self.chunk_x
125    }
126
127    fn chunk_z(&self) -> i32 {
128        self.chunk_z
129    }
130
131    fn chunk_min_x(&self) -> i32 {
132        self.chunk_min_x
133    }
134
135    fn chunk_min_z(&self) -> i32 {
136        self.chunk_min_z
137    }
138
139    fn center_block_x(&self) -> i32 {
140        self.center_block_x
141    }
142
143    fn center_block_z(&self) -> i32 {
144        self.center_block_z
145    }
146
147    fn sea_level(&self) -> i32 {
148        self.sea_level
149    }
150
151    fn min_y(&self) -> i32 {
152        self.min_y
153    }
154
155    fn height(&self) -> i32 {
156        self.height
157    }
158
159    fn template_pools(&self) -> &FxHashMap<Identifier, TemplatePoolData> {
160        self.template_pools
161    }
162
163    fn templates(&self) -> &FxHashMap<Identifier, TemplateData> {
164        self.templates
165    }
166
167    fn base_height(&mut self, _x: i32, _z: i32, ocean_floor: bool) -> i32 {
168        self.base_height_flat(ocean_floor)
169    }
170
171    fn base_height_full(&mut self, _x: i32, _z: i32, ocean_floor: bool) -> i32 {
172        self.base_height_flat(ocean_floor)
173    }
174
175    fn biome_at(&mut self, _block_x: i32, _block_y: i32, _block_z: i32) -> BiomeRef {
176        self.biome
177    }
178
179    fn column_state(&mut self, _x: i32, y: i32, _z: i32) -> ColumnBlock {
180        let Some(state) = self.state_at_y(y) else {
181            return ColumnBlock::Air;
182        };
183        if state.is_solid() {
184            ColumnBlock::Solid
185        } else if state.has_fluid() {
186            ColumnBlock::Fluid
187        } else {
188            ColumnBlock::Air
189        }
190    }
191
192    fn surface_y(&mut self) -> i32 {
193        if let Some(y) = self.surface_y_cache {
194            return y;
195        }
196        let y = self.base_height_flat(false) - 1;
197        self.surface_y_cache = Some(y);
198        y
199    }
200
201    fn terrain_surface_height(&self, _x: i32, _z: i32, ocean_floor: bool) -> i32 {
202        self.base_height_flat(ocean_floor)
203    }
204
205    fn terrain_is_opaque(&self, _x: i32, y: i32, _z: i32, ocean_floor: bool) -> bool {
206        self.is_opaque_at_y(y, ocean_floor)
207    }
208}
209
210impl ChunkGenerator for FlatChunkGenerator {
211    fn min_y(&self) -> i32 {
212        0
213    }
214
215    fn gen_depth(&self) -> i32 {
216        384
217    }
218
219    fn noise_biome(&self, _quart_x: i32, _quart_y: i32, _quart_z: i32) -> BiomeRef {
220        &vanilla_biomes::PLAINS
221    }
222
223    fn spawn_height(&self, min_y: i32, height: i32) -> i32 {
224        min_y + height.min(self.layers.len() as i32)
225    }
226
227    fn structure_generator(&self) -> Option<&StructureGenerator> {
228        self.structure_generator.as_ref()
229    }
230
231    fn create_structures(&self, chunk: &Chunk) {
232        let Some(structure_generator) = &self.structure_generator else {
233            return;
234        };
235
236        let pos = chunk.pos();
237        let chunk_x = pos.0.x;
238        let chunk_z = pos.0.y;
239        let chunk_min_x = chunk_x * 16;
240        let chunk_min_z = chunk_z * 16;
241        let mut ctx = FlatGenerationContext {
242            seed: self.seed,
243            chunk_x,
244            chunk_z,
245            chunk_min_x,
246            chunk_min_z,
247            center_block_x: chunk_min_x + 8,
248            center_block_z: chunk_min_z + 8,
249            sea_level: self.sea_level,
250            min_y: chunk.min_y(),
251            height: (chunk.sections().sections.len() * 16) as i32,
252            layers: &self.layers,
253            biome: &vanilla_biomes::PLAINS,
254            template_pools: structure_generator.template_pools(),
255            templates: structure_generator.templates(),
256            surface_y_cache: None,
257        };
258        create_structures(structure_generator, chunk, &mut ctx);
259    }
260
261    fn create_biomes(&self, chunk: &Chunk) {
262        let section_count = chunk.sections().sections.len();
263
264        for section_index in 0..section_count {
265            let section = &chunk.sections().sections[section_index];
266            let mut section_guard = section.write();
267
268            for local_quart_x in 0..4usize {
269                for local_quart_y in 0..4usize {
270                    for local_quart_z in 0..4usize {
271                        section_guard.biomes.set(
272                            local_quart_x,
273                            local_quart_y,
274                            local_quart_z,
275                            self.biome_id,
276                        );
277                    }
278                }
279            }
280            drop(section_guard);
281        }
282
283        chunk.mark_dirty();
284    }
285
286    fn fill_from_noise(
287        &self,
288        chunk: GenerationChunk<'_, NoisePhase>,
289        _beardifier: Option<&Beardifier>,
290    ) {
291        let max_relative_y = chunk.section_count() * 16;
292
293        for x in 0..16 {
294            for z in 0..16 {
295                for (relative_y, block) in self.layers.iter().enumerate().take(max_relative_y) {
296                    chunk.set_relative_block(x, relative_y, z, *block);
297                }
298            }
299        }
300    }
301
302    fn build_surface(
303        &self,
304        _chunk: GenerationChunk<'_, SurfacePhase>,
305        _neighbor_biomes: &dyn Fn(IVec3) -> u16,
306    ) {
307    }
308
309    fn apply_carvers(&self, _chunk: GenerationChunk<'_, CarversPhase>) {}
310
311    fn create_worldgen_region_random(&self, world_seed: i64, center: ChunkPos) -> RandomSource {
312        xoroshiro_worldgen_region_random(world_seed, center)
313    }
314
315    fn apply_biome_decorations(&self, _region: &mut WorldGenRegion<'_>) {}
316}