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