1use rustc_hash::FxHashMap;
2use simdnbt::ToNbtTag;
3use simdnbt::owned::NbtTag;
4use steel_utils::Identifier;
5
6use crate::sound_event::SoundEventRef;
7use crate::world_clock::WorldClockRef;
8
9#[derive(Debug)]
10pub struct BedRule {
11 pub can_set_spawn: BedRuleValue,
12 pub can_sleep: BedRuleValue,
13 pub explodes: bool,
14 pub error_message_key: Option<&'static str>,
15}
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub enum BedRuleValue {
19 Always,
20 WhenDark,
21 Never,
22}
23
24impl BedRuleValue {
25 #[must_use]
26 pub const fn as_str(self) -> &'static str {
27 match self {
28 Self::Always => "always",
29 Self::WhenDark => "when_dark",
30 Self::Never => "never",
31 }
32 }
33}
34
35impl ToNbtTag for BedRuleValue {
36 fn to_nbt_tag(self) -> NbtTag {
37 self.as_str().to_nbt_tag()
38 }
39}
40
41#[derive(Debug)]
42pub struct MoodSound {
43 pub sound: SoundEventRef,
44 pub tick_delay: i32,
45 pub block_search_extent: i32,
46 pub offset: f64,
47}
48
49#[derive(Debug)]
50pub struct MusicEntry {
51 pub sound: SoundEventRef,
52 pub min_delay: i32,
53 pub max_delay: i32,
54 pub replace_current_music: bool,
55}
56
57#[derive(Debug)]
58pub struct BackgroundMusic {
59 pub default: MusicEntry,
60 pub creative: Option<MusicEntry>,
61}
62
63#[derive(Debug)]
65pub struct DimensionType {
66 pub key: Identifier,
67 pub fixed_time: Option<i64>,
68 pub has_skylight: bool,
69 pub has_ceiling: bool,
70 pub coordinate_scale: f64,
71 pub min_y: i32,
72 pub height: i32,
73 pub logical_height: i32,
74 pub infiniburn: &'static str,
75 pub ambient_light: f32,
76 pub default_clock: Option<WorldClockRef>,
77 pub timelines: Option<&'static str>,
78 pub has_ender_dragon_fight: bool,
79 pub monster_spawn_light_level: MonsterSpawnLightLevel,
80 pub monster_spawn_block_light_limit: i32,
81
82 pub skybox: Option<&'static str>,
84 pub cardinal_light: Option<&'static str>,
85
86 pub sky_color: Option<&'static str>,
88 pub fog_color: Option<&'static str>,
89 pub cloud_color: Option<&'static str>,
90 pub cloud_height: Option<f32>,
91 pub ambient_light_color: Option<&'static str>,
92 pub sky_light_color: Option<&'static str>,
93 pub sky_light_factor: Option<f32>,
94 pub fog_start_distance: Option<f32>,
95 pub fog_end_distance: Option<f32>,
96 pub default_dripstone_particle: Option<&'static str>,
97
98 pub respawn_anchor_works: bool,
100 pub can_start_raid: bool,
101 pub fast_lava: bool,
102 pub piglins_zombify: bool,
103 pub sky_light_level: Option<f32>,
104 pub snow_golem_melts: bool,
105 pub water_evaporates: bool,
106 pub nether_portal_spawns_piglin: bool,
107 pub bed_rule: BedRule,
108
109 pub mood_sound: Option<MoodSound>,
111 pub background_music: Option<BackgroundMusic>,
112}
113
114impl DimensionType {
115 #[must_use]
117 pub fn get_teleportation_scale(last_dimension_type: &Self, new_dimension_type: &Self) -> f64 {
118 last_dimension_type.coordinate_scale / new_dimension_type.coordinate_scale
119 }
120}
121
122#[derive(Debug)]
124pub enum MonsterSpawnLightLevel {
125 Simple(i32),
126 Complex {
127 distribution_type: &'static str,
128 min_inclusive: i32,
129 max_inclusive: i32,
130 },
131}
132
133impl ToNbtTag for &DimensionType {
134 fn to_nbt_tag(self) -> NbtTag {
135 use simdnbt::owned::{NbtCompound, NbtTag};
136 let mut compound = NbtCompound::new();
137
138 if let Some(fixed_time) = self.fixed_time {
140 compound.insert("fixed_time", fixed_time);
141 }
142 compound.insert("has_skylight", self.has_skylight);
143 compound.insert("has_ceiling", self.has_ceiling);
144 compound.insert("coordinate_scale", self.coordinate_scale);
145 compound.insert("min_y", self.min_y);
146 compound.insert("height", self.height);
147 compound.insert("logical_height", self.logical_height);
148 compound.insert("infiniburn", self.infiniburn);
149 compound.insert("ambient_light", self.ambient_light);
150 compound.insert("has_ender_dragon_fight", self.has_ender_dragon_fight);
151 if let Some(clock) = self.default_clock {
152 compound.insert("default_clock", clock.key.to_string().as_str());
153 }
154 if let Some(timelines) = self.timelines {
155 compound.insert("timelines", timelines);
156 }
157 if let Some(skybox) = self.skybox {
158 compound.insert("skybox", skybox);
159 }
160 if let Some(cardinal_light) = self.cardinal_light {
161 compound.insert("cardinal_light", cardinal_light);
162 }
163 compound.insert(
164 "monster_spawn_light_level",
165 match &self.monster_spawn_light_level {
166 MonsterSpawnLightLevel::Simple(v) => NbtTag::Int(*v),
167 MonsterSpawnLightLevel::Complex {
168 distribution_type,
169 min_inclusive,
170 max_inclusive,
171 } => {
172 let mut inner = NbtCompound::new();
173 inner.insert("type", *distribution_type);
174 inner.insert("min_inclusive", *min_inclusive);
175 inner.insert("max_inclusive", *max_inclusive);
176 NbtTag::Compound(inner)
177 }
178 },
179 );
180 compound.insert(
181 "monster_spawn_block_light_limit",
182 self.monster_spawn_block_light_limit,
183 );
184
185 let mut attributes = NbtCompound::new();
187
188 if let Some(sky_color) = self.sky_color {
190 attributes.insert("minecraft:visual/sky_color", sky_color);
191 }
192 if let Some(fog_color) = self.fog_color {
193 attributes.insert("minecraft:visual/fog_color", fog_color);
194 }
195 if let Some(cloud_color) = self.cloud_color {
196 attributes.insert("minecraft:visual/cloud_color", cloud_color);
197 }
198 if let Some(cloud_height) = self.cloud_height {
199 attributes.insert("minecraft:visual/cloud_height", cloud_height);
200 }
201 if let Some(ambient_light_color) = self.ambient_light_color {
202 attributes.insert("minecraft:visual/ambient_light_color", ambient_light_color);
203 }
204 if let Some(sky_light_color) = self.sky_light_color {
205 attributes.insert("minecraft:visual/sky_light_color", sky_light_color);
206 }
207 if let Some(sky_light_factor) = self.sky_light_factor {
208 attributes.insert("minecraft:visual/sky_light_factor", sky_light_factor);
209 }
210 if let Some(fog_start_distance) = self.fog_start_distance {
211 attributes.insert("minecraft:visual/fog_start_distance", fog_start_distance);
212 }
213 if let Some(fog_end_distance) = self.fog_end_distance {
214 attributes.insert("minecraft:visual/fog_end_distance", fog_end_distance);
215 }
216 if let Some(particle_type) = self.default_dripstone_particle {
217 let mut particle = NbtCompound::new();
218 particle.insert("type", particle_type);
219 attributes.insert(
220 "minecraft:visual/default_dripstone_particle",
221 NbtTag::Compound(particle),
222 );
223 }
224
225 attributes.insert(
227 "minecraft:gameplay/respawn_anchor_works",
228 self.respawn_anchor_works,
229 );
230 attributes.insert("minecraft:gameplay/can_start_raid", self.can_start_raid);
231 if self.fast_lava {
232 attributes.insert("minecraft:gameplay/fast_lava", self.fast_lava);
233 }
234 if !self.piglins_zombify {
235 attributes.insert("minecraft:gameplay/piglins_zombify", self.piglins_zombify);
236 }
237 if let Some(sky_light_level) = self.sky_light_level {
238 attributes.insert("minecraft:gameplay/sky_light_level", sky_light_level);
239 }
240 if self.snow_golem_melts {
241 attributes.insert("minecraft:gameplay/snow_golem_melts", self.snow_golem_melts);
242 }
243 if self.water_evaporates {
244 attributes.insert("minecraft:gameplay/water_evaporates", self.water_evaporates);
245 }
246 if self.nether_portal_spawns_piglin {
247 attributes.insert(
248 "minecraft:gameplay/nether_portal_spawns_piglin",
249 self.nether_portal_spawns_piglin,
250 );
251 }
252
253 {
255 let mut bed_rule = NbtCompound::new();
256 bed_rule.insert("can_set_spawn", self.bed_rule.can_set_spawn);
257 bed_rule.insert("can_sleep", self.bed_rule.can_sleep);
258 if self.bed_rule.explodes {
259 bed_rule.insert("explodes", self.bed_rule.explodes);
260 }
261 if let Some(key) = self.bed_rule.error_message_key {
262 let mut msg = NbtCompound::new();
263 msg.insert("translate", key);
264 bed_rule.insert("error_message", NbtTag::Compound(msg));
265 }
266 attributes.insert("minecraft:gameplay/bed_rule", NbtTag::Compound(bed_rule));
267 }
268
269 if let Some(mood) = &self.mood_sound {
271 let mut mood_compound = NbtCompound::new();
272 let sound = mood.sound.key.to_string();
273 mood_compound.insert("sound", sound.as_str());
274 mood_compound.insert("tick_delay", mood.tick_delay);
275 mood_compound.insert("block_search_extent", mood.block_search_extent);
276 mood_compound.insert("offset", mood.offset);
277 let mut ambient_sounds = NbtCompound::new();
278 ambient_sounds.insert("mood", NbtTag::Compound(mood_compound));
279 attributes.insert(
280 "minecraft:audio/ambient_sounds",
281 NbtTag::Compound(ambient_sounds),
282 );
283 }
284 if let Some(bg_music) = &self.background_music {
285 let mut music_compound = NbtCompound::new();
286 let mut default_entry = NbtCompound::new();
287 let sound = bg_music.default.sound.key.to_string();
288 default_entry.insert("sound", sound.as_str());
289 default_entry.insert("min_delay", bg_music.default.min_delay);
290 default_entry.insert("max_delay", bg_music.default.max_delay);
291 if bg_music.default.replace_current_music {
292 default_entry.insert(
293 "replace_current_music",
294 bg_music.default.replace_current_music,
295 );
296 }
297 music_compound.insert("default", NbtTag::Compound(default_entry));
298 if let Some(creative) = &bg_music.creative {
299 let mut creative_entry = NbtCompound::new();
300 let sound = creative.sound.key.to_string();
301 creative_entry.insert("sound", sound.as_str());
302 creative_entry.insert("min_delay", creative.min_delay);
303 creative_entry.insert("max_delay", creative.max_delay);
304 if creative.replace_current_music {
305 creative_entry.insert("replace_current_music", creative.replace_current_music);
306 }
307 music_compound.insert("creative", NbtTag::Compound(creative_entry));
308 }
309 attributes.insert(
310 "minecraft:audio/background_music",
311 NbtTag::Compound(music_compound),
312 );
313 }
314
315 if !attributes.is_empty() {
316 compound.insert("attributes", NbtTag::Compound(attributes));
317 }
318
319 NbtTag::Compound(compound)
320 }
321}
322
323pub type DimensionTypeRef = &'static DimensionType;
324
325pub struct DimensionTypeRegistry {
326 dimension_types_by_id: Vec<DimensionTypeRef>,
327 dimension_types_by_key: FxHashMap<Identifier, usize>,
328 allows_registering: bool,
329}
330
331impl DimensionTypeRegistry {
332 #[must_use]
333 pub fn new() -> Self {
334 Self {
335 dimension_types_by_id: Vec::new(),
336 dimension_types_by_key: FxHashMap::default(),
337 allows_registering: true,
338 }
339 }
340
341 #[must_use]
342 pub fn get_ids(&self) -> Vec<Identifier> {
343 self.dimension_types_by_key.keys().cloned().collect()
344 }
345}
346
347crate::impl_standard_methods!(
348 DimensionTypeRegistry,
349 DimensionTypeRef,
350 dimension_types_by_id,
351 dimension_types_by_key,
352 allows_registering
353);
354
355crate::impl_registry!(
356 DimensionTypeRegistry,
357 DimensionType,
358 dimension_types_by_id,
359 dimension_types_by_key,
360 dimension_types
361);
362
363#[cfg(test)]
364mod tests {
365 use crate::dimension_type::DimensionType;
366 use crate::vanilla_dimension_types::{OVERWORLD, THE_END, THE_NETHER};
367
368 #[test]
369 fn teleportation_scale_matches_vanilla_coordinate_ratio() {
370 assert_eq!(
371 DimensionType::get_teleportation_scale(&OVERWORLD, &THE_NETHER),
372 0.125
373 );
374 assert_eq!(
375 DimensionType::get_teleportation_scale(&THE_NETHER, &OVERWORLD),
376 8.0
377 );
378 assert_eq!(
379 DimensionType::get_teleportation_scale(&THE_END, &THE_NETHER),
380 0.125
381 );
382 }
383}