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