Skip to main content

steel_registry/
dimension_type.rs

1use rustc_hash::FxHashMap;
2use simdnbt::ToNbtTag;
3use simdnbt::owned::NbtTag;
4use steel_utils::Identifier;
5
6use crate::sound_event::SoundEventRef;
7
8#[derive(Debug)]
9pub struct BedRule {
10    pub can_set_spawn: &'static str,
11    pub can_sleep: &'static str,
12    pub explodes: bool,
13    pub error_message_key: Option<&'static str>,
14}
15
16#[derive(Debug)]
17pub struct MoodSound {
18    pub sound: SoundEventRef,
19    pub tick_delay: i32,
20    pub block_search_extent: i32,
21    pub offset: f64,
22}
23
24#[derive(Debug)]
25pub struct MusicEntry {
26    pub sound: SoundEventRef,
27    pub min_delay: i32,
28    pub max_delay: i32,
29    pub replace_current_music: bool,
30}
31
32#[derive(Debug)]
33pub struct BackgroundMusic {
34    pub default: MusicEntry,
35    pub creative: Option<MusicEntry>,
36}
37
38/// Represents a full dimension type definition from a data pack JSON file.
39#[derive(Debug)]
40pub struct DimensionType {
41    pub key: Identifier,
42    pub fixed_time: Option<i64>,
43    pub has_skylight: bool,
44    pub has_ceiling: bool,
45    pub coordinate_scale: f64,
46    pub min_y: i32,
47    pub height: i32,
48    pub logical_height: i32,
49    pub infiniburn: &'static str,
50    pub ambient_light: f32,
51    pub default_clock: Option<&'static str>,
52    pub timelines: Option<&'static str>,
53    pub has_ender_dragon_fight: bool,
54    pub monster_spawn_light_level: MonsterSpawnLightLevel,
55    pub monster_spawn_block_light_limit: i32,
56
57    // Top-level
58    pub skybox: Option<&'static str>,
59    pub cardinal_light: Option<&'static str>,
60
61    // Attributes: visual
62    pub sky_color: Option<&'static str>,
63    pub fog_color: Option<&'static str>,
64    pub cloud_color: Option<&'static str>,
65    pub cloud_height: Option<f32>,
66    pub ambient_light_color: Option<&'static str>,
67    pub sky_light_color: Option<&'static str>,
68    pub sky_light_factor: Option<f32>,
69    pub fog_start_distance: Option<f32>,
70    pub fog_end_distance: Option<f32>,
71    pub default_dripstone_particle: Option<&'static str>,
72
73    // Attributes: gameplay
74    pub respawn_anchor_works: bool,
75    pub can_start_raid: bool,
76    pub fast_lava: bool,
77    pub piglins_zombify: bool,
78    pub sky_light_level: Option<f32>,
79    pub snow_golem_melts: bool,
80    pub water_evaporates: bool,
81    pub nether_portal_spawns_piglin: bool,
82    pub bed_rule: BedRule,
83
84    // Attributes: audio
85    pub mood_sound: Option<MoodSound>,
86    pub background_music: Option<BackgroundMusic>,
87}
88
89/// Represents the complex structure for monster spawn light level.
90#[derive(Debug)]
91pub enum MonsterSpawnLightLevel {
92    Simple(i32),
93    Complex {
94        distribution_type: &'static str,
95        min_inclusive: i32,
96        max_inclusive: i32,
97    },
98}
99
100impl ToNbtTag for &DimensionType {
101    fn to_nbt_tag(self) -> NbtTag {
102        use simdnbt::owned::{NbtCompound, NbtTag};
103        let mut compound = NbtCompound::new();
104
105        // Top-level fields
106        if let Some(fixed_time) = self.fixed_time {
107            compound.insert("fixed_time", fixed_time);
108        }
109        compound.insert("has_skylight", self.has_skylight);
110        compound.insert("has_ceiling", self.has_ceiling);
111        compound.insert("coordinate_scale", self.coordinate_scale);
112        compound.insert("min_y", self.min_y);
113        compound.insert("height", self.height);
114        compound.insert("logical_height", self.logical_height);
115        compound.insert("infiniburn", self.infiniburn);
116        compound.insert("ambient_light", self.ambient_light);
117        compound.insert("has_ender_dragon_fight", self.has_ender_dragon_fight);
118        if let Some(clock) = self.default_clock {
119            compound.insert("default_clock", clock);
120        }
121        if let Some(timelines) = self.timelines {
122            compound.insert("timelines", timelines);
123        }
124        if let Some(skybox) = self.skybox {
125            compound.insert("skybox", skybox);
126        }
127        if let Some(cardinal_light) = self.cardinal_light {
128            compound.insert("cardinal_light", cardinal_light);
129        }
130        compound.insert(
131            "monster_spawn_light_level",
132            match &self.monster_spawn_light_level {
133                MonsterSpawnLightLevel::Simple(v) => NbtTag::Int(*v),
134                MonsterSpawnLightLevel::Complex {
135                    distribution_type,
136                    min_inclusive,
137                    max_inclusive,
138                } => {
139                    let mut inner = NbtCompound::new();
140                    inner.insert("type", *distribution_type);
141                    inner.insert("min_inclusive", *min_inclusive);
142                    inner.insert("max_inclusive", *max_inclusive);
143                    NbtTag::Compound(inner)
144                }
145            },
146        );
147        compound.insert(
148            "monster_spawn_block_light_limit",
149            self.monster_spawn_block_light_limit,
150        );
151
152        // Attributes compound
153        let mut attributes = NbtCompound::new();
154
155        // Visual attributes
156        if let Some(sky_color) = self.sky_color {
157            attributes.insert("minecraft:visual/sky_color", sky_color);
158        }
159        if let Some(fog_color) = self.fog_color {
160            attributes.insert("minecraft:visual/fog_color", fog_color);
161        }
162        if let Some(cloud_color) = self.cloud_color {
163            attributes.insert("minecraft:visual/cloud_color", cloud_color);
164        }
165        if let Some(cloud_height) = self.cloud_height {
166            attributes.insert("minecraft:visual/cloud_height", cloud_height);
167        }
168        if let Some(ambient_light_color) = self.ambient_light_color {
169            attributes.insert("minecraft:visual/ambient_light_color", ambient_light_color);
170        }
171        if let Some(sky_light_color) = self.sky_light_color {
172            attributes.insert("minecraft:visual/sky_light_color", sky_light_color);
173        }
174        if let Some(sky_light_factor) = self.sky_light_factor {
175            attributes.insert("minecraft:visual/sky_light_factor", sky_light_factor);
176        }
177        if let Some(fog_start_distance) = self.fog_start_distance {
178            attributes.insert("minecraft:visual/fog_start_distance", fog_start_distance);
179        }
180        if let Some(fog_end_distance) = self.fog_end_distance {
181            attributes.insert("minecraft:visual/fog_end_distance", fog_end_distance);
182        }
183        if let Some(particle_type) = self.default_dripstone_particle {
184            let mut particle = NbtCompound::new();
185            particle.insert("type", particle_type);
186            attributes.insert(
187                "minecraft:visual/default_dripstone_particle",
188                NbtTag::Compound(particle),
189            );
190        }
191
192        // Gameplay attributes
193        attributes.insert(
194            "minecraft:gameplay/respawn_anchor_works",
195            self.respawn_anchor_works,
196        );
197        attributes.insert("minecraft:gameplay/can_start_raid", self.can_start_raid);
198        if self.fast_lava {
199            attributes.insert("minecraft:gameplay/fast_lava", self.fast_lava);
200        }
201        if !self.piglins_zombify {
202            attributes.insert("minecraft:gameplay/piglins_zombify", self.piglins_zombify);
203        }
204        if let Some(sky_light_level) = self.sky_light_level {
205            attributes.insert("minecraft:gameplay/sky_light_level", sky_light_level);
206        }
207        if self.snow_golem_melts {
208            attributes.insert("minecraft:gameplay/snow_golem_melts", self.snow_golem_melts);
209        }
210        if self.water_evaporates {
211            attributes.insert("minecraft:gameplay/water_evaporates", self.water_evaporates);
212        }
213        if self.nether_portal_spawns_piglin {
214            attributes.insert(
215                "minecraft:gameplay/nether_portal_spawns_piglin",
216                self.nether_portal_spawns_piglin,
217            );
218        }
219
220        // Bed rule
221        {
222            let mut bed_rule = NbtCompound::new();
223            bed_rule.insert("can_set_spawn", self.bed_rule.can_set_spawn);
224            bed_rule.insert("can_sleep", self.bed_rule.can_sleep);
225            if self.bed_rule.explodes {
226                bed_rule.insert("explodes", self.bed_rule.explodes);
227            }
228            if let Some(key) = self.bed_rule.error_message_key {
229                let mut msg = NbtCompound::new();
230                msg.insert("translate", key);
231                bed_rule.insert("error_message", NbtTag::Compound(msg));
232            }
233            attributes.insert("minecraft:gameplay/bed_rule", NbtTag::Compound(bed_rule));
234        }
235
236        // Audio attributes
237        if let Some(mood) = &self.mood_sound {
238            let mut mood_compound = NbtCompound::new();
239            let sound = mood.sound.key.to_string();
240            mood_compound.insert("sound", sound.as_str());
241            mood_compound.insert("tick_delay", mood.tick_delay);
242            mood_compound.insert("block_search_extent", mood.block_search_extent);
243            mood_compound.insert("offset", mood.offset);
244            let mut ambient_sounds = NbtCompound::new();
245            ambient_sounds.insert("mood", NbtTag::Compound(mood_compound));
246            attributes.insert(
247                "minecraft:audio/ambient_sounds",
248                NbtTag::Compound(ambient_sounds),
249            );
250        }
251        if let Some(bg_music) = &self.background_music {
252            let mut music_compound = NbtCompound::new();
253            let mut default_entry = NbtCompound::new();
254            let sound = bg_music.default.sound.key.to_string();
255            default_entry.insert("sound", sound.as_str());
256            default_entry.insert("min_delay", bg_music.default.min_delay);
257            default_entry.insert("max_delay", bg_music.default.max_delay);
258            if bg_music.default.replace_current_music {
259                default_entry.insert(
260                    "replace_current_music",
261                    bg_music.default.replace_current_music,
262                );
263            }
264            music_compound.insert("default", NbtTag::Compound(default_entry));
265            if let Some(creative) = &bg_music.creative {
266                let mut creative_entry = NbtCompound::new();
267                let sound = creative.sound.key.to_string();
268                creative_entry.insert("sound", sound.as_str());
269                creative_entry.insert("min_delay", creative.min_delay);
270                creative_entry.insert("max_delay", creative.max_delay);
271                if creative.replace_current_music {
272                    creative_entry.insert("replace_current_music", creative.replace_current_music);
273                }
274                music_compound.insert("creative", NbtTag::Compound(creative_entry));
275            }
276            attributes.insert(
277                "minecraft:audio/background_music",
278                NbtTag::Compound(music_compound),
279            );
280        }
281
282        if !attributes.is_empty() {
283            compound.insert("attributes", NbtTag::Compound(attributes));
284        }
285
286        NbtTag::Compound(compound)
287    }
288}
289
290pub type DimensionTypeRef = &'static DimensionType;
291
292impl PartialEq for DimensionTypeRef {
293    #[expect(clippy::disallowed_methods)] // This IS the PartialEq impl; ptr::eq is correct here
294    fn eq(&self, other: &Self) -> bool {
295        std::ptr::eq(*self, *other)
296    }
297}
298
299impl Eq for DimensionTypeRef {}
300
301pub struct DimensionTypeRegistry {
302    dimension_types_by_id: Vec<DimensionTypeRef>,
303    dimension_types_by_key: FxHashMap<Identifier, usize>,
304    allows_registering: bool,
305}
306
307impl DimensionTypeRegistry {
308    #[must_use]
309    pub fn new() -> Self {
310        Self {
311            dimension_types_by_id: Vec::new(),
312            dimension_types_by_key: FxHashMap::default(),
313            allows_registering: true,
314        }
315    }
316
317    #[must_use]
318    pub fn get_ids(&self) -> Vec<Identifier> {
319        self.dimension_types_by_key.keys().cloned().collect()
320    }
321}
322
323crate::impl_standard_methods!(
324    DimensionTypeRegistry,
325    DimensionTypeRef,
326    dimension_types_by_id,
327    dimension_types_by_key,
328    allows_registering
329);
330
331crate::impl_registry!(
332    DimensionTypeRegistry,
333    DimensionType,
334    dimension_types_by_id,
335    dimension_types_by_key,
336    dimension_types
337);