steel_registry/structure/
mod.rs1use 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#[derive(Debug)]
17pub struct StructureData {
18 pub key: Identifier,
20 pub id: OnceLock<usize>,
22 pub structure_type: Identifier,
24 pub allowed_biomes: Vec<Identifier>,
26 pub spawn_overrides: Vec<StructureSpawnOverrideData>,
28 pub step: StructureGenerationStep,
30 pub terrain_adjustment: TerrainAdjustment,
32 pub config: StructureConfigData,
34}
35
36impl StructureData {
37 #[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
47pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum StructureGenerationStep {
116 SurfaceStructures,
118 UndergroundStructures,
120 UndergroundDecoration,
122}
123
124impl StructureGenerationStep {
125 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum TerrainAdjustment {
145 None,
147 Bury,
149 BeardThin,
151 BeardBox,
153 Encapsulate,
155}
156
157impl TerrainAdjustment {
158 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum StructureSpawnBoundingBox {
171 Full,
173 Piece,
175}
176
177#[derive(Debug, Clone)]
179pub struct StructureSpawnOverrideData {
180 pub category: String,
182 pub bounding_box: StructureSpawnBoundingBox,
184 pub spawns: Vec<StructureSpawnerData>,
186}
187
188#[derive(Debug, Clone)]
190pub struct StructureSpawnerData {
191 pub entity_type: Identifier,
193 pub weight: i32,
195 pub min_count: i32,
197 pub max_count: i32,
199}
200
201#[derive(Debug, Clone)]
203pub enum StructureConfigData {
204 Jigsaw(JigsawConfig),
206 Mineshaft { mineshaft_type: MineshaftTypeData },
208 Shipwreck { is_beached: bool },
210 OceanRuin {
212 biome_temp: OceanRuinBiomeTempData,
213 large_probability: f32,
214 cluster_probability: f32,
215 },
216 RuinedPortal { setups: Vec<RuinedPortalSetupData> },
218 NetherFossil { height: HeightProviderData },
220 Empty,
222}
223
224impl StructureConfigData {
225 #[must_use]
226 pub const fn as_jigsaw(&self) -> Option<&JigsawConfig> {
227 match self {
228 Self::Jigsaw(config) => Some(config),
229 _ => None,
230 }
231 }
232}
233
234#[derive(Debug, Clone)]
236pub struct JigsawConfig {
237 pub start_pool: Identifier,
239 pub max_depth: i32,
241 pub use_expansion_hack: bool,
243 pub project_start_to_heightmap: Option<String>,
245 pub start_height: StartHeight,
247 pub max_distance_from_center: i32,
249 pub start_jigsaw_name: Option<Identifier>,
251 pub dimension_padding: DimensionPadding,
253 pub pool_aliases: Vec<PoolAlias>,
255 pub liquid_settings: LiquidSettingsData,
257}
258
259#[derive(Debug, Clone)]
261pub enum StartHeight {
262 Constant(i32),
264 Uniform { min: i32, max: i32 },
266}
267
268#[derive(Debug, Clone, Copy)]
270pub struct DimensionPadding {
271 pub bottom: i32,
273 pub top: i32,
275}
276
277#[derive(Debug, Clone)]
279pub enum PoolAlias {
280 Direct {
282 alias: Identifier,
283 target: Identifier,
284 },
285 Random {
287 alias: Identifier,
288 targets: Vec<(Identifier, i32)>,
289 },
290 RandomGroup {
292 groups: Vec<(Vec<(Identifier, Identifier)>, i32)>,
293 },
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub enum LiquidSettingsData {
299 ApplyWaterlogging,
301 IgnoreWaterlogging,
303}
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307pub enum MineshaftTypeData {
308 Normal,
310 Mesa,
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
316pub enum OceanRuinBiomeTempData {
317 Warm,
319 Cold,
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub enum RuinedPortalPlacementData {
326 OnLandSurface,
327 PartlyBuried,
328 Underground,
329 InMountain,
330 OnOceanFloor,
331 InNether,
332}
333
334#[derive(Debug, Clone)]
336pub struct RuinedPortalSetupData {
337 pub placement: RuinedPortalPlacementData,
338 pub weight: f32,
339 pub air_pocket_probability: f32,
340 pub can_be_cold: bool,
341 pub mossiness: f32,
342 pub overgrown: bool,
343 pub replace_with_blackstone: bool,
344 pub vines: bool,
345}
346
347#[derive(Debug, Clone)]
349pub enum VerticalAnchorData {
350 Absolute(i32),
351 AboveBottom(i32),
352 BelowTop(i32),
353}
354
355#[derive(Debug, Clone)]
357pub enum HeightProviderData {
358 Constant(VerticalAnchorData),
359 Uniform {
360 min_inclusive: VerticalAnchorData,
361 max_inclusive: VerticalAnchorData,
362 },
363}
364
365#[cfg(test)]
366mod tests {
367 use crate::{Registry, TaggedRegistryExt};
368
369 use super::*;
370
371 #[test]
372 fn vanilla_structure_tags_are_registered() {
373 let registry = Registry::new_vanilla();
374 let village_tag = Identifier::vanilla_static("village");
375 let villages = registry.structures.get_tag(&village_tag);
376 assert!(villages.as_ref().is_some_and(|entries| {
377 entries
378 .iter()
379 .any(|structure| structure.key == Identifier::vanilla_static("village_plains"))
380 }));
381 }
382
383 #[test]
384 fn structure_generation_steps_use_vanilla_decoration_ordinals() {
385 assert_eq!(
386 StructureGenerationStep::UndergroundStructures.decoration_ordinal(),
387 3
388 );
389 assert_eq!(
390 StructureGenerationStep::SurfaceStructures.decoration_ordinal(),
391 4
392 );
393 assert_eq!(
394 StructureGenerationStep::UndergroundDecoration.decoration_ordinal(),
395 7
396 );
397 }
398}