chunkedge_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 chunkedge_ident::{ident, Ident};
14use chunkedge_nbt::serde::ser::CompoundSerializer;
15use serde::{Deserialize, Serialize};
16use tracing::error;
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 DimensionTypeId {
89    pub fn new(value: u16) -> Self {
90        DimensionTypeId(value)
91    }
92    pub fn get_value(&self) -> u16 {
93        self.0
94    }
95}
96
97impl RegistryIdx for DimensionTypeId {
98    const MAX: usize = u16::MAX as usize;
99
100    fn to_index(self) -> usize {
101        self.0 as usize
102    }
103
104    fn from_index(idx: usize) -> Self {
105        Self(idx as u16)
106    }
107}
108
109impl Deref for DimensionTypeRegistry {
110    type Target = Registry<DimensionTypeId, DimensionType>;
111
112    fn deref(&self) -> &Self::Target {
113        &self.reg
114    }
115}
116
117impl DerefMut for DimensionTypeRegistry {
118    fn deref_mut(&mut self) -> &mut Self::Target {
119        &mut self.reg
120    }
121}
122
123#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
124#[serde(deny_unknown_fields)]
125pub struct DimensionType {
126    pub ambient_light: f32,
127    pub bed_works: bool,
128    pub coordinate_scale: f64,
129    pub effects: DimensionEffects,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub fixed_time: Option<i32>,
132    pub has_ceiling: bool,
133    pub has_raids: bool,
134    pub has_skylight: bool,
135    pub height: i32,
136    pub infiniburn: String,
137    pub logical_height: i32,
138    pub min_y: i32,
139    pub monster_spawn_block_light_limit: i32,
140    pub monster_spawn_light_level: MonsterSpawnLightLevel,
141    pub natural: bool,
142    pub piglin_safe: bool,
143    pub respawn_anchor_works: bool,
144    pub ultrawarm: bool,
145}
146
147impl Default for DimensionType {
148    fn default() -> Self {
149        Self {
150            ambient_light: 0.0,
151            bed_works: true,
152            coordinate_scale: 1.0,
153            effects: DimensionEffects::default(),
154            fixed_time: None,
155            has_ceiling: false,
156            has_raids: true,
157            has_skylight: true,
158            height: 384,
159            infiniburn: "#minecraft:infiniburn_overworld".into(),
160            logical_height: 384,
161            min_y: -64,
162            monster_spawn_block_light_limit: 0,
163            monster_spawn_light_level: MonsterSpawnLightLevel::Int(7),
164            natural: true,
165            piglin_safe: false,
166            respawn_anchor_works: false,
167            ultrawarm: false,
168        }
169    }
170}
171
172/// Determines what skybox/fog effects to use in dimensions.
173#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)]
174pub enum DimensionEffects {
175    #[serde(rename = "minecraft:overworld")]
176    #[default]
177    Overworld,
178    #[serde(rename = "minecraft:the_nether")]
179    TheNether,
180    #[serde(rename = "minecraft:the_end")]
181    TheEnd,
182}
183
184#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
185#[serde(untagged)]
186pub enum MonsterSpawnLightLevel {
187    Int(i32),
188    Tagged(MonsterSpawnLightLevelTagged),
189}
190
191#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
192#[serde(tag = "type")]
193pub enum MonsterSpawnLightLevelTagged {
194    #[serde(rename = "minecraft:uniform")]
195    Uniform {
196        min_inclusive: i32,
197        max_inclusive: i32,
198    },
199}
200
201impl From<i32> for MonsterSpawnLightLevel {
202    fn from(value: i32) -> Self {
203        Self::Int(value)
204    }
205}