Skip to main content

steel_registry/structure/
mod.rs

1use std::sync::OnceLock;
2
3pub mod processor;
4pub mod set;
5pub mod template_pool;
6
7use rustc_hash::FxHashMap;
8use steel_utils::Identifier;
9
10use crate::RegistryTags;
11
12/// A registered worldgen structure definition.
13///
14/// Mirrors vanilla's `Structure`: common settings are stored here, while
15/// [`StructureConfigData`] carries the type-specific codec payload.
16#[derive(Debug)]
17pub struct StructureData {
18    /// Registry key, e.g. `minecraft:village_plains`.
19    pub key: Identifier,
20    /// Cached registry ID, set during registration for O(1) lookup on hot paths.
21    pub id: OnceLock<usize>,
22    /// Structure type, e.g. `minecraft:jigsaw` or `minecraft:mineshaft`.
23    pub structure_type: Identifier,
24    /// Biomes this structure can generate in. Tags are resolved at build time.
25    pub allowed_biomes: Vec<Identifier>,
26    /// Structure-specific mob spawn overrides.
27    pub spawn_overrides: Vec<StructureSpawnOverrideData>,
28    /// Generation decoration step from the structure JSON.
29    pub step: StructureGenerationStep,
30    /// Terrain adaptation used by reference inflation and Beardifier.
31    pub terrain_adjustment: TerrainAdjustment,
32    /// Type-specific structure config.
33    pub config: StructureConfigData,
34}
35
36impl StructureData {
37    /// Vanilla inflates the structure start bounding box by 12 for every terrain
38    /// adaptation mode except `none`.
39    #[must_use]
40    pub const fn bb_inflate(&self) -> i32 {
41        self.terrain_adjustment.bb_inflate()
42    }
43}
44
45pub type StructureRef = &'static StructureData;
46
47/// Registry of worldgen structure definitions.
48pub struct StructureRegistry {
49    structures_by_id: Vec<StructureRef>,
50    structures_by_key: FxHashMap<Identifier, usize>,
51    tags: RegistryTags,
52    allows_registering: bool,
53}
54
55impl StructureRegistry {
56    #[must_use]
57    pub fn new() -> Self {
58        Self {
59            structures_by_id: Vec::new(),
60            structures_by_key: FxHashMap::default(),
61            tags: RegistryTags::default(),
62            allows_registering: true,
63        }
64    }
65
66    pub fn register(&mut self, entry: StructureRef) -> usize {
67        assert!(
68            self.allows_registering,
69            "Cannot register StructureData after registry has been frozen"
70        );
71        let id = self.structures_by_id.len();
72        let cached = entry.id.get_or_init(|| id);
73        assert_eq!(*cached, id, "structure registered with conflicting id");
74        self.structures_by_id.push(entry);
75        self.structures_by_key.insert(entry.key.clone(), id);
76        id
77    }
78
79    pub fn iter(&self) -> impl Iterator<Item = (usize, StructureRef)> + '_ {
80        self.structures_by_id
81            .iter()
82            .enumerate()
83            .map(|(id, &entry)| (id, entry))
84    }
85}
86
87impl Default for StructureRegistry {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93crate::impl_registry_ext!(
94    StructureRegistry,
95    StructureData,
96    structures_by_id,
97    structures_by_key
98);
99crate::impl_tagged_registry!(StructureRegistry, structures_by_key, "structure");
100
101crate::impl_registry_entry_eq!(StructureData);
102
103impl crate::RegistryEntry for StructureData {
104    fn key(&self) -> &Identifier {
105        &self.key
106    }
107
108    fn try_id(&self) -> Option<usize> {
109        self.id.get().copied()
110    }
111}
112
113/// Structure generation step.
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum StructureGenerationStep {
116    /// `surface_structures`.
117    SurfaceStructures,
118    /// `underground_structures`.
119    UndergroundStructures,
120    /// `underground_decoration`.
121    UndergroundDecoration,
122}
123
124impl StructureGenerationStep {
125    /// Decoration-stage ordinal used by vanilla `GenerationStep.Decoration`.
126    ///
127    /// Structure JSON only names the three structure-capable decoration stages;
128    /// feature generation still runs all eleven decoration stages, so these
129    /// values intentionally leave the vanilla gaps intact.
130    #[must_use]
131    pub const fn decoration_ordinal(self) -> usize {
132        match self {
133            Self::UndergroundStructures => 3,
134            Self::SurfaceStructures => 4,
135            Self::UndergroundDecoration => 7,
136        }
137    }
138}
139
140/// How a structure modifies surrounding terrain.
141///
142/// Corresponds to vanilla's `TerrainAdjustment` enum.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum TerrainAdjustment {
145    /// No terrain adaptation.
146    None,
147    /// Fill in terrain around and above the structure.
148    Bury,
149    /// Carve thin beard below structure.
150    BeardThin,
151    /// Carve box-shaped beard below structure.
152    BeardBox,
153    /// Encapsulate structure in terrain.
154    Encapsulate,
155}
156
157impl TerrainAdjustment {
158    /// Bounding-box inflation used by vanilla's `Structure.adjustBoundingBox`.
159    #[must_use]
160    pub const fn bb_inflate(self) -> i32 {
161        match self {
162            Self::None => 0,
163            Self::Bury | Self::BeardThin | Self::BeardBox | Self::Encapsulate => 12,
164        }
165    }
166}
167
168/// Spawn override bounding-box mode.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum StructureSpawnBoundingBox {
171    /// Applies to the whole structure start bounding box.
172    Full,
173    /// Applies only when inside one of the pieces.
174    Piece,
175}
176
177#[cfg(test)]
178mod tests {
179    use crate::{Registry, TaggedRegistryExt};
180
181    use super::*;
182
183    #[test]
184    fn vanilla_structure_tags_are_registered() {
185        let registry = Registry::new_vanilla();
186        let village_tag = Identifier::vanilla_static("village");
187        let villages = registry.structures.get_tag(&village_tag);
188        assert!(villages.as_ref().is_some_and(|entries| {
189            entries
190                .iter()
191                .any(|structure| structure.key == Identifier::vanilla_static("village_plains"))
192        }));
193    }
194
195    #[test]
196    fn structure_generation_steps_use_vanilla_decoration_ordinals() {
197        assert_eq!(
198            StructureGenerationStep::UndergroundStructures.decoration_ordinal(),
199            3
200        );
201        assert_eq!(
202            StructureGenerationStep::SurfaceStructures.decoration_ordinal(),
203            4
204        );
205        assert_eq!(
206            StructureGenerationStep::UndergroundDecoration.decoration_ordinal(),
207            7
208        );
209    }
210}
211
212/// A structure mob spawn override for one mob category.
213#[derive(Debug, Clone)]
214pub struct StructureSpawnOverrideData {
215    /// Mob category name, e.g. `monster`.
216    pub category: String,
217    /// Bounding box mode.
218    pub bounding_box: StructureSpawnBoundingBox,
219    /// Weighted spawns for this override.
220    pub spawns: Vec<StructureSpawnerData>,
221}
222
223/// Spawn entry inside a structure spawn override.
224#[derive(Debug, Clone)]
225pub struct StructureSpawnerData {
226    /// Entity type id.
227    pub entity_type: Identifier,
228    /// Spawn weight.
229    pub weight: i32,
230    /// Minimum group size.
231    pub min_count: i32,
232    /// Maximum group size.
233    pub max_count: i32,
234}
235
236/// Type-specific structure config.
237#[derive(Debug, Clone)]
238pub enum StructureConfigData {
239    /// `minecraft:jigsaw`.
240    Jigsaw(JigsawConfig),
241    /// `minecraft:mineshaft`.
242    Mineshaft { mineshaft_type: MineshaftTypeData },
243    /// `minecraft:shipwreck`.
244    Shipwreck { is_beached: bool },
245    /// `minecraft:ocean_ruin`.
246    OceanRuin {
247        biome_temp: OceanRuinBiomeTempData,
248        large_probability: f32,
249        cluster_probability: f32,
250    },
251    /// `minecraft:ruined_portal`.
252    RuinedPortal { setups: Vec<RuinedPortalSetupData> },
253    /// `minecraft:nether_fossil`.
254    NetherFossil { height: HeightProviderData },
255    /// Structure types with only common settings, or whose config is still unused.
256    Empty,
257}
258
259impl StructureConfigData {
260    #[must_use]
261    pub const fn as_jigsaw(&self) -> Option<&JigsawConfig> {
262        match self {
263            Self::Jigsaw(config) => Some(config),
264            _ => None,
265        }
266    }
267}
268
269/// Jigsaw-specific configuration parsed from structure JSON.
270#[derive(Debug, Clone)]
271pub struct JigsawConfig {
272    /// Starting template pool.
273    pub start_pool: Identifier,
274    /// Maximum recursion depth (vanilla calls this `size`).
275    pub max_depth: i32,
276    /// Whether the expansion hack is enabled.
277    pub use_expansion_hack: bool,
278    /// If set, project the start piece to this heightmap type.
279    pub project_start_to_heightmap: Option<String>,
280    /// Start height provider type and value.
281    pub start_height: StartHeight,
282    /// Maximum distance from center for piece placement.
283    pub max_distance_from_center: i32,
284    /// Optional named jigsaw to anchor the start piece to.
285    pub start_jigsaw_name: Option<Identifier>,
286    /// Dimension padding (min distance from world height limits).
287    pub dimension_padding: DimensionPadding,
288    /// Pool alias configurations.
289    pub pool_aliases: Vec<PoolAlias>,
290    /// Liquid handling mode.
291    pub liquid_settings: LiquidSettingsData,
292}
293
294/// Start height configuration used by currently-generated jigsaw structures.
295#[derive(Debug, Clone)]
296pub enum StartHeight {
297    /// Fixed absolute Y.
298    Constant(i32),
299    /// Uniform random between min and max (inclusive).
300    Uniform { min: i32, max: i32 },
301}
302
303/// Dimension padding (how close pieces can be to world height limits).
304#[derive(Debug, Clone, Copy)]
305pub struct DimensionPadding {
306    /// Bottom padding.
307    pub bottom: i32,
308    /// Top padding.
309    pub top: i32,
310}
311
312/// A pool alias remapping.
313#[derive(Debug, Clone)]
314pub enum PoolAlias {
315    /// Direct remapping: alias -> target.
316    Direct {
317        alias: Identifier,
318        target: Identifier,
319    },
320    /// Random selection from weighted targets.
321    Random {
322        alias: Identifier,
323        targets: Vec<(Identifier, i32)>,
324    },
325    /// Random group: pick one group, apply all bindings in it.
326    RandomGroup {
327        groups: Vec<(Vec<(Identifier, Identifier)>, i32)>,
328    },
329}
330
331/// Jigsaw liquid handling mode.
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333pub enum LiquidSettingsData {
334    /// Default vanilla behavior.
335    ApplyWaterlogging,
336    /// Do not apply waterlogging from surrounding fluids.
337    IgnoreWaterlogging,
338}
339
340/// Mineshaft variant type.
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub enum MineshaftTypeData {
343    /// Standard mineshaft.
344    Normal,
345    /// Badlands mineshaft.
346    Mesa,
347}
348
349/// Ocean ruin temperature variant.
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351pub enum OceanRuinBiomeTempData {
352    /// Warm ruin pools.
353    Warm,
354    /// Cold ruin pools.
355    Cold,
356}
357
358/// Ruined portal vertical placement type.
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360pub enum RuinedPortalPlacementData {
361    OnLandSurface,
362    PartlyBuried,
363    Underground,
364    InMountain,
365    OnOceanFloor,
366    InNether,
367}
368
369/// One weighted ruined portal setup entry.
370#[derive(Debug, Clone)]
371pub struct RuinedPortalSetupData {
372    pub placement: RuinedPortalPlacementData,
373    pub weight: f32,
374    pub air_pocket_probability: f32,
375    pub can_be_cold: bool,
376    pub mossiness: f32,
377    pub overgrown: bool,
378    pub replace_with_blackstone: bool,
379    pub vines: bool,
380}
381
382/// Generic vertical anchor used by non-jigsaw height providers.
383#[derive(Debug, Clone)]
384pub enum VerticalAnchorData {
385    Absolute(i32),
386    AboveBottom(i32),
387    BelowTop(i32),
388}
389
390/// Height provider subset used by vanilla structures in this version.
391#[derive(Debug, Clone)]
392pub enum HeightProviderData {
393    Constant(VerticalAnchorData),
394    Uniform {
395        min_inclusive: VerticalAnchorData,
396        max_inclusive: VerticalAnchorData,
397    },
398}