valence_registry/
dimension_type.rs

1//! Contains dimension types and the dimension type registry. Minecraft's
2//! default dimensions are added to the registry by default.
3//!
4//! ### **NOTE:**
5//! - Modifying the dimension type registry after the server has started can
6//!   break invariants within instances and clients! Make sure there are no
7//!   instances or clients spawned before mutating.
8
9use std::ops::{Deref, DerefMut};
10
11use bevy_app::prelude::*;
12use bevy_ecs::prelude::*;
13use serde::{Deserialize, Serialize};
14use tracing::error;
15use valence_ident::{ident, Ident};
16use valence_nbt::serde::CompoundSerializer;
17
18use crate::codec::{RegistryCodec, RegistryValue};
19use crate::{Registry, RegistryIdx, RegistrySet};
20pub struct DimensionTypePlugin;
21
22impl Plugin for DimensionTypePlugin {
23    fn build(&self, app: &mut App) {
24        app.init_resource::<DimensionTypeRegistry>()
25            .add_systems(PreStartup, load_default_dimension_types)
26            .add_systems(
27                PostUpdate,
28                update_dimension_type_registry.before(RegistrySet),
29            );
30    }
31}
32
33/// Loads the default dimension types from the registry codec.
34fn load_default_dimension_types(mut reg: ResMut<DimensionTypeRegistry>, codec: Res<RegistryCodec>) {
35    let mut helper = move || -> anyhow::Result<()> {
36        for value in codec.registry(DimensionTypeRegistry::KEY) {
37            let mut dimension_type = DimensionType::deserialize(value.element.clone())?;
38
39            // HACK: We don't have a lighting engine implemented. To avoid shrouding the
40            // world in darkness, give all dimensions the max ambient light.
41            dimension_type.ambient_light = 1.0;
42
43            reg.insert(value.name.clone(), dimension_type);
44        }
45
46        Ok(())
47    };
48
49    if let Err(e) = helper() {
50        error!("failed to load default dimension types from registry codec: {e:#}");
51    }
52}
53
54/// Updates the registry codec as the dimension type registry is modified by
55/// users.
56fn update_dimension_type_registry(
57    reg: Res<DimensionTypeRegistry>,
58    mut codec: ResMut<RegistryCodec>,
59) {
60    if reg.is_changed() {
61        let dimension_types = codec.registry_mut(DimensionTypeRegistry::KEY);
62
63        dimension_types.clear();
64
65        dimension_types.extend(reg.iter().map(|(_, name, dim)| {
66            RegistryValue {
67                name: name.into(),
68                element: dim
69                    .serialize(CompoundSerializer)
70                    .expect("failed to serialize dimension type"),
71            }
72        }));
73    }
74}
75
76#[derive(Resource, Default, Debug)]
77pub struct DimensionTypeRegistry {
78    reg: Registry<DimensionTypeId, DimensionType>,
79}
80
81impl DimensionTypeRegistry {
82    pub const KEY: Ident<&'static str> = ident!("dimension_type");
83}
84
85#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
86pub struct DimensionTypeId(u16);
87
88impl RegistryIdx for DimensionTypeId {
89    const MAX: usize = u16::MAX as usize;
90
91    fn to_index(self) -> usize {
92        self.0 as usize
93    }
94
95    fn from_index(idx: usize) -> Self {
96        Self(idx as u16)
97    }
98}
99
100impl Deref for DimensionTypeRegistry {
101    type Target = Registry<DimensionTypeId, DimensionType>;
102
103    fn deref(&self) -> &Self::Target {
104        &self.reg
105    }
106}
107
108impl DerefMut for DimensionTypeRegistry {
109    fn deref_mut(&mut self) -> &mut Self::Target {
110        &mut self.reg
111    }
112}
113
114#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
115#[serde(deny_unknown_fields)]
116pub struct DimensionType {
117    pub ambient_light: f32,
118    pub bed_works: bool,
119    pub coordinate_scale: f64,
120    pub effects: DimensionEffects,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub fixed_time: Option<i32>,
123    pub has_ceiling: bool,
124    pub has_raids: bool,
125    pub has_skylight: bool,
126    pub height: i32,
127    pub infiniburn: String,
128    pub logical_height: i32,
129    pub min_y: i32,
130    pub monster_spawn_block_light_limit: i32,
131    pub monster_spawn_light_level: MonsterSpawnLightLevel,
132    pub natural: bool,
133    pub piglin_safe: bool,
134    pub respawn_anchor_works: bool,
135    pub ultrawarm: bool,
136}
137
138impl Default for DimensionType {
139    fn default() -> Self {
140        Self {
141            ambient_light: 0.0,
142            bed_works: true,
143            coordinate_scale: 1.0,
144            effects: DimensionEffects::default(),
145            fixed_time: None,
146            has_ceiling: false,
147            has_raids: true,
148            has_skylight: true,
149            height: 384,
150            infiniburn: "#minecraft:infiniburn_overworld".into(),
151            logical_height: 384,
152            min_y: -64,
153            monster_spawn_block_light_limit: 0,
154            monster_spawn_light_level: MonsterSpawnLightLevel::Int(7),
155            natural: true,
156            piglin_safe: false,
157            respawn_anchor_works: false,
158            ultrawarm: false,
159        }
160    }
161}
162
163/// Determines what skybox/fog effects to use in dimensions.
164#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)]
165pub enum DimensionEffects {
166    #[serde(rename = "minecraft:overworld")]
167    #[default]
168    Overworld,
169    #[serde(rename = "minecraft:the_nether")]
170    TheNether,
171    #[serde(rename = "minecraft:the_end")]
172    TheEnd,
173}
174
175#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
176#[serde(untagged)]
177pub enum MonsterSpawnLightLevel {
178    Int(i32),
179    Tagged(MonsterSpawnLightLevelTagged),
180}
181
182#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
183#[serde(tag = "type", content = "value")]
184pub enum MonsterSpawnLightLevelTagged {
185    #[serde(rename = "minecraft:uniform")]
186    Uniform {
187        min_inclusive: i32,
188        max_inclusive: i32,
189    },
190}
191
192impl From<i32> for MonsterSpawnLightLevel {
193    fn from(value: i32) -> Self {
194        Self::Int(value)
195    }
196}