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#[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#[derive(Debug, Clone)]
214pub struct StructureSpawnOverrideData {
215 pub category: String,
217 pub bounding_box: StructureSpawnBoundingBox,
219 pub spawns: Vec<StructureSpawnerData>,
221}
222
223#[derive(Debug, Clone)]
225pub struct StructureSpawnerData {
226 pub entity_type: Identifier,
228 pub weight: i32,
230 pub min_count: i32,
232 pub max_count: i32,
234}
235
236#[derive(Debug, Clone)]
238pub enum StructureConfigData {
239 Jigsaw(JigsawConfig),
241 Mineshaft { mineshaft_type: MineshaftTypeData },
243 Shipwreck { is_beached: bool },
245 OceanRuin {
247 biome_temp: OceanRuinBiomeTempData,
248 large_probability: f32,
249 cluster_probability: f32,
250 },
251 RuinedPortal { setups: Vec<RuinedPortalSetupData> },
253 NetherFossil { height: HeightProviderData },
255 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#[derive(Debug, Clone)]
271pub struct JigsawConfig {
272 pub start_pool: Identifier,
274 pub max_depth: i32,
276 pub use_expansion_hack: bool,
278 pub project_start_to_heightmap: Option<String>,
280 pub start_height: StartHeight,
282 pub max_distance_from_center: i32,
284 pub start_jigsaw_name: Option<Identifier>,
286 pub dimension_padding: DimensionPadding,
288 pub pool_aliases: Vec<PoolAlias>,
290 pub liquid_settings: LiquidSettingsData,
292}
293
294#[derive(Debug, Clone)]
296pub enum StartHeight {
297 Constant(i32),
299 Uniform { min: i32, max: i32 },
301}
302
303#[derive(Debug, Clone, Copy)]
305pub struct DimensionPadding {
306 pub bottom: i32,
308 pub top: i32,
310}
311
312#[derive(Debug, Clone)]
314pub enum PoolAlias {
315 Direct {
317 alias: Identifier,
318 target: Identifier,
319 },
320 Random {
322 alias: Identifier,
323 targets: Vec<(Identifier, i32)>,
324 },
325 RandomGroup {
327 groups: Vec<(Vec<(Identifier, Identifier)>, i32)>,
328 },
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333pub enum LiquidSettingsData {
334 ApplyWaterlogging,
336 IgnoreWaterlogging,
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub enum MineshaftTypeData {
343 Normal,
345 Mesa,
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351pub enum OceanRuinBiomeTempData {
352 Warm,
354 Cold,
356}
357
358#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360pub enum RuinedPortalPlacementData {
361 OnLandSurface,
362 PartlyBuried,
363 Underground,
364 InMountain,
365 OnOceanFloor,
366 InNether,
367}
368
369#[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#[derive(Debug, Clone)]
384pub enum VerticalAnchorData {
385 Absolute(i32),
386 AboveBottom(i32),
387 BelowTop(i32),
388}
389
390#[derive(Debug, Clone)]
392pub enum HeightProviderData {
393 Constant(VerticalAnchorData),
394 Uniform {
395 min_inclusive: VerticalAnchorData,
396 max_inclusive: VerticalAnchorData,
397 },
398}