1use std::sync::OnceLock;
2
3use rustc_hash::FxHashMap;
4use steel_utils::Identifier;
5
6#[derive(Debug)]
11pub struct StructureData {
12 pub key: Identifier,
14 pub id: OnceLock<usize>,
16 pub structure_type: Identifier,
18 pub allowed_biomes: Vec<Identifier>,
20 pub spawn_overrides: Vec<StructureSpawnOverrideData>,
22 pub step: StructureGenerationStep,
24 pub terrain_adjustment: TerrainAdjustment,
26 pub config: StructureConfigData,
28}
29
30impl StructureData {
31 #[must_use]
34 pub const fn bb_inflate(&self) -> i32 {
35 self.terrain_adjustment.bb_inflate()
36 }
37}
38
39pub type StructureRef = &'static StructureData;
40
41pub struct StructureRegistry {
43 structures_by_id: Vec<StructureRef>,
44 structures_by_key: FxHashMap<Identifier, usize>,
45 tags: FxHashMap<Identifier, Vec<Identifier>>,
46 allows_registering: bool,
47}
48
49impl StructureRegistry {
50 #[must_use]
51 pub fn new() -> Self {
52 Self {
53 structures_by_id: Vec::new(),
54 structures_by_key: FxHashMap::default(),
55 tags: FxHashMap::default(),
56 allows_registering: true,
57 }
58 }
59
60 pub fn register(&mut self, entry: StructureRef) -> usize {
61 assert!(
62 self.allows_registering,
63 "Cannot register StructureData after registry has been frozen"
64 );
65 let id = self.structures_by_id.len();
66 let cached = entry.id.get_or_init(|| id);
67 assert_eq!(*cached, id, "structure registered with conflicting id");
68 self.structures_by_id.push(entry);
69 self.structures_by_key.insert(entry.key.clone(), id);
70 id
71 }
72
73 pub fn iter(&self) -> impl Iterator<Item = (usize, StructureRef)> + '_ {
74 self.structures_by_id
75 .iter()
76 .enumerate()
77 .map(|(id, &entry)| (id, entry))
78 }
79}
80
81impl Default for StructureRegistry {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87crate::impl_registry_ext!(
88 StructureRegistry,
89 StructureData,
90 structures_by_id,
91 structures_by_key
92);
93crate::impl_tagged_registry!(StructureRegistry, structures_by_key, "structure");
94
95impl crate::RegistryEntry for StructureData {
96 fn key(&self) -> &Identifier {
97 &self.key
98 }
99
100 fn try_id(&self) -> Option<usize> {
101 self.id.get().copied()
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum StructureGenerationStep {
108 SurfaceStructures,
110 UndergroundStructures,
112 UndergroundDecoration,
114}
115
116impl StructureGenerationStep {
117 #[must_use]
123 pub const fn decoration_ordinal(self) -> usize {
124 match self {
125 Self::UndergroundStructures => 3,
126 Self::SurfaceStructures => 4,
127 Self::UndergroundDecoration => 7,
128 }
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum TerrainAdjustment {
137 None,
139 Bury,
141 BeardThin,
143 BeardBox,
145 Encapsulate,
147}
148
149impl TerrainAdjustment {
150 #[must_use]
152 pub const fn bb_inflate(self) -> i32 {
153 match self {
154 Self::None => 0,
155 Self::Bury | Self::BeardThin | Self::BeardBox | Self::Encapsulate => 12,
156 }
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub enum StructureSpawnBoundingBox {
163 Full,
165 Piece,
167}
168
169#[cfg(test)]
170mod tests {
171 use crate::{Registry, TaggedRegistryExt};
172
173 use super::*;
174
175 #[test]
176 fn vanilla_structure_tags_are_registered() {
177 let registry = Registry::new_vanilla();
178 let village_tag = Identifier::vanilla_static("village");
179 let villages = registry.structures.get_tag(&village_tag);
180 assert!(villages.as_ref().is_some_and(|entries| {
181 entries
182 .iter()
183 .any(|structure| structure.key == Identifier::vanilla_static("village_plains"))
184 }));
185 }
186
187 #[test]
188 fn structure_generation_steps_use_vanilla_decoration_ordinals() {
189 assert_eq!(
190 StructureGenerationStep::UndergroundStructures.decoration_ordinal(),
191 3
192 );
193 assert_eq!(
194 StructureGenerationStep::SurfaceStructures.decoration_ordinal(),
195 4
196 );
197 assert_eq!(
198 StructureGenerationStep::UndergroundDecoration.decoration_ordinal(),
199 7
200 );
201 }
202}
203
204#[derive(Debug, Clone)]
206pub struct StructureSpawnOverrideData {
207 pub category: String,
209 pub bounding_box: StructureSpawnBoundingBox,
211 pub spawns: Vec<StructureSpawnerData>,
213}
214
215#[derive(Debug, Clone)]
217pub struct StructureSpawnerData {
218 pub entity_type: Identifier,
220 pub weight: i32,
222 pub min_count: i32,
224 pub max_count: i32,
226}
227
228#[derive(Debug, Clone)]
230pub enum StructureConfigData {
231 Jigsaw(JigsawConfig),
233 Mineshaft { mineshaft_type: MineshaftTypeData },
235 Shipwreck { is_beached: bool },
237 OceanRuin {
239 biome_temp: OceanRuinBiomeTempData,
240 large_probability: f32,
241 cluster_probability: f32,
242 },
243 RuinedPortal { setups: Vec<RuinedPortalSetupData> },
245 NetherFossil { height: HeightProviderData },
247 Empty,
249}
250
251impl StructureConfigData {
252 #[must_use]
253 pub fn as_jigsaw(&self) -> Option<&JigsawConfig> {
254 match self {
255 Self::Jigsaw(config) => Some(config),
256 _ => None,
257 }
258 }
259}
260
261#[derive(Debug, Clone)]
263pub struct JigsawConfig {
264 pub start_pool: Identifier,
266 pub max_depth: i32,
268 pub use_expansion_hack: bool,
270 pub project_start_to_heightmap: Option<String>,
272 pub start_height: StartHeight,
274 pub max_distance_from_center: i32,
276 pub start_jigsaw_name: Option<Identifier>,
278 pub dimension_padding: DimensionPadding,
280 pub pool_aliases: Vec<PoolAlias>,
282 pub liquid_settings: LiquidSettingsData,
284}
285
286#[derive(Debug, Clone)]
288pub enum StartHeight {
289 Constant(i32),
291 Uniform { min: i32, max: i32 },
293}
294
295#[derive(Debug, Clone, Copy)]
297pub struct DimensionPadding {
298 pub bottom: i32,
300 pub top: i32,
302}
303
304#[derive(Debug, Clone)]
306pub enum PoolAlias {
307 Direct {
309 alias: Identifier,
310 target: Identifier,
311 },
312 Random {
314 alias: Identifier,
315 targets: Vec<(Identifier, i32)>,
316 },
317 RandomGroup {
319 groups: Vec<(Vec<(Identifier, Identifier)>, i32)>,
320 },
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub enum LiquidSettingsData {
326 ApplyWaterlogging,
328 IgnoreWaterlogging,
330}
331
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
334pub enum MineshaftTypeData {
335 Normal,
337 Mesa,
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub enum OceanRuinBiomeTempData {
344 Warm,
346 Cold,
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum RuinedPortalPlacementData {
353 OnLandSurface,
354 PartlyBuried,
355 Underground,
356 InMountain,
357 OnOceanFloor,
358 InNether,
359}
360
361#[derive(Debug, Clone)]
363pub struct RuinedPortalSetupData {
364 pub placement: RuinedPortalPlacementData,
365 pub weight: f32,
366 pub air_pocket_probability: f32,
367 pub can_be_cold: bool,
368 pub mossiness: f32,
369 pub overgrown: bool,
370 pub replace_with_blackstone: bool,
371 pub vines: bool,
372}
373
374#[derive(Debug, Clone)]
376pub enum VerticalAnchorData {
377 Absolute(i32),
378 AboveBottom(i32),
379 BelowTop(i32),
380}
381
382#[derive(Debug, Clone)]
384pub enum HeightProviderData {
385 Constant(VerticalAnchorData),
386 Uniform {
387 min_inclusive: VerticalAnchorData,
388 max_inclusive: VerticalAnchorData,
389 },
390}