azalea_core/
registry_holder.rs

1//! The data sent to the client in the `ClientboundRegistryDataPacket`.
2//!
3//! This module contains the structures used to represent the registry
4//! sent to the client upon login. This contains a lot of information about
5//! the game, including the types of chat messages, dimensions, and
6//! biomes.
7
8use std::{collections::HashMap, io::Cursor};
9
10use indexmap::IndexMap;
11use simdnbt::{
12    owned::{NbtCompound, NbtTag},
13    Deserialize, FromNbtTag, Serialize, ToNbtTag,
14};
15use tracing::error;
16
17use crate::resource_location::ResourceLocation;
18
19/// The base of the registry.
20///
21/// This is the registry that is sent to the client upon login.
22#[derive(Default, Debug, Clone)]
23pub struct RegistryHolder {
24    pub map: HashMap<ResourceLocation, IndexMap<ResourceLocation, NbtCompound>>,
25}
26
27impl RegistryHolder {
28    pub fn append(
29        &mut self,
30        id: ResourceLocation,
31        entries: Vec<(ResourceLocation, Option<NbtCompound>)>,
32    ) {
33        let map = self.map.entry(id).or_default();
34        for (key, value) in entries {
35            if let Some(value) = value {
36                map.insert(key, value);
37            } else {
38                map.shift_remove(&key);
39            }
40        }
41    }
42
43    /// Get the dimension type registry, or `None` if it doesn't exist. You
44    /// should do some type of error handling if this returns `None`.
45    pub fn dimension_type(&self) -> Option<RegistryType<DimensionTypeElement>> {
46        let name = ResourceLocation::new("minecraft:dimension_type");
47        match self.get(&name) {
48            Some(Ok(registry)) => Some(registry),
49            Some(Err(err)) => {
50                error!(
51                    "Error deserializing dimension type registry: {err:?}\n{:?}",
52                    self.map.get(&name)
53                );
54                None
55            }
56            None => None,
57        }
58    }
59
60    fn get<T: Deserialize>(
61        &self,
62        name: &ResourceLocation,
63    ) -> Option<Result<RegistryType<T>, simdnbt::DeserializeError>> {
64        // this is suboptimal, ideally simdnbt should just have a way to get the
65        // owned::NbtCompound as a borrow::NbtCompound
66
67        let mut map = HashMap::new();
68
69        for (key, value) in self.map.get(name)? {
70            // convert the value to T
71            let mut nbt_bytes = Vec::new();
72            value.write(&mut nbt_bytes);
73            let nbt_borrow_compound =
74                simdnbt::borrow::read_compound(&mut Cursor::new(&nbt_bytes)).ok()?;
75            let value = match T::from_compound((&nbt_borrow_compound).into()) {
76                Ok(value) => value,
77                Err(err) => {
78                    return Some(Err(err));
79                }
80            };
81
82            map.insert(key.clone(), value);
83        }
84
85        Some(Ok(RegistryType { map }))
86    }
87}
88
89/// A collection of values for a certain type of registry data.
90#[derive(Debug, Clone)]
91pub struct RegistryType<T> {
92    pub map: HashMap<ResourceLocation, T>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
97pub struct TrimMaterialElement {
98    pub asset_name: String,
99    pub ingredient: ResourceLocation,
100    pub item_model_index: f32,
101    pub override_armor_materials: HashMap<String, String>,
102    pub description: Option<String>,
103}
104
105/// Data about a kind of chat message
106#[derive(Debug, Clone, Serialize, Deserialize)]
107#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
108pub struct ChatTypeElement {
109    pub chat: ChatTypeData,
110    pub narration: ChatTypeData,
111}
112
113/// Data about a chat message.
114#[derive(Debug, Clone, Serialize, Deserialize)]
115#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
116pub struct ChatTypeData {
117    pub translation_key: String,
118    pub parameters: Vec<String>,
119    pub style: Option<ChatTypeStyle>,
120}
121
122/// The style of a chat message.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
125pub struct ChatTypeStyle {
126    pub color: Option<String>,
127    pub bold: Option<bool>,
128    pub italic: Option<bool>,
129    pub underlined: Option<bool>,
130    pub strikethrough: Option<bool>,
131    pub obfuscated: Option<bool>,
132}
133
134/// Dimension attributes.
135#[cfg(feature = "strict_registry")]
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[simdnbt(deny_unknown_fields)]
138pub struct DimensionTypeElement {
139    pub ambient_light: f32,
140    pub bed_works: bool,
141    pub coordinate_scale: f32,
142    pub effects: ResourceLocation,
143    pub fixed_time: Option<u32>,
144    pub has_ceiling: bool,
145    pub has_raids: bool,
146    pub has_skylight: bool,
147    pub height: u32,
148    pub infiniburn: ResourceLocation,
149    pub logical_height: u32,
150    pub min_y: i32,
151    pub monster_spawn_block_light_limit: u32,
152    pub monster_spawn_light_level: MonsterSpawnLightLevel,
153    pub natural: bool,
154    pub piglin_safe: bool,
155    pub respawn_anchor_works: bool,
156    pub ultrawarm: Option<bool>,
157}
158
159/// Dimension attributes.
160#[cfg(not(feature = "strict_registry"))]
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct DimensionTypeElement {
163    pub height: u32,
164    pub min_y: i32,
165    pub ultrawarm: Option<bool>,
166    #[simdnbt(flatten)]
167    pub _extra: HashMap<String, NbtTag>,
168}
169
170/// The light level at which monsters can spawn.
171///
172/// This can be either a single minimum value, or a formula with a min and
173/// max.
174#[derive(Debug, Clone)]
175// #[serde(untagged)]
176pub enum MonsterSpawnLightLevel {
177    /// A simple minimum value.
178    Simple(u32),
179    /// A complex value with a type, minimum, and maximum.
180    /// Vanilla minecraft only uses one type, "minecraft:uniform".
181    Complex {
182        kind: ResourceLocation,
183        value: MonsterSpawnLightLevelValues,
184    },
185}
186
187impl FromNbtTag for MonsterSpawnLightLevel {
188    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
189        if let Some(value) = tag.int() {
190            Some(Self::Simple(value as u32))
191        } else if let Some(value) = tag.compound() {
192            let kind = ResourceLocation::from_nbt_tag(value.get("type")?)?;
193            let value = MonsterSpawnLightLevelValues::from_nbt_tag(value.get("value")?)?;
194            Some(Self::Complex { kind, value })
195        } else {
196            None
197        }
198    }
199}
200
201impl ToNbtTag for MonsterSpawnLightLevel {
202    fn to_nbt_tag(self) -> simdnbt::owned::NbtTag {
203        match self {
204            Self::Simple(value) => value.to_nbt_tag(),
205            Self::Complex { kind, value } => {
206                let mut compound = NbtCompound::new();
207                compound.insert("type", kind.to_nbt_tag());
208                compound.insert("value", value.to_nbt_tag());
209                simdnbt::owned::NbtTag::Compound(compound)
210            }
211        }
212    }
213}
214
215/// The min and max light levels at which monsters can spawn.
216///
217/// Values are inclusive.
218#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
219#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
220pub struct MonsterSpawnLightLevelValues {
221    #[simdnbt(rename = "min_inclusive")]
222    pub min: u32,
223    #[simdnbt(rename = "max_inclusive")]
224    pub max: u32,
225}
226
227/// Biome attributes.
228#[derive(Debug, Clone, Serialize, Deserialize)]
229#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
230pub struct WorldTypeElement {
231    pub has_precipitation: bool,
232    pub temperature: f32,
233    pub temperature_modifier: Option<String>,
234    pub downfall: f32,
235    pub effects: BiomeEffects,
236}
237
238/// The precipitation of a biome.
239#[derive(Debug, PartialEq, Eq, Copy, Clone)]
240pub enum BiomePrecipitation {
241    None,
242    Rain,
243    Snow,
244}
245impl FromNbtTag for BiomePrecipitation {
246    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
247        match tag.string()?.to_str().as_ref() {
248            "none" => Some(Self::None),
249            "rain" => Some(Self::Rain),
250            "snow" => Some(Self::Snow),
251            _ => None,
252        }
253    }
254}
255impl ToNbtTag for BiomePrecipitation {
256    fn to_nbt_tag(self) -> NbtTag {
257        match self {
258            Self::None => NbtTag::String("none".into()),
259            Self::Rain => NbtTag::String("rain".into()),
260            Self::Snow => NbtTag::String("snow".into()),
261        }
262    }
263}
264
265/// The effects of a biome.
266///
267/// This includes the sky, fog, water, and grass color,
268/// as well as music and other sound effects.
269#[derive(Debug, Clone, Serialize, Deserialize)]
270#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
271pub struct BiomeEffects {
272    pub sky_color: u32,
273    pub fog_color: u32,
274    pub water_color: u32,
275    pub water_fog_color: u32,
276    pub foliage_color: Option<u32>,
277    pub grass_color: Option<u32>,
278    pub grass_color_modifier: Option<String>,
279    pub music: Option<BiomeMusic>,
280    pub mood_sound: BiomeMoodSound,
281    pub additions_sound: Option<AdditionsSound>,
282    pub ambient_sound: Option<ResourceLocation>,
283    pub particle: Option<BiomeParticle>,
284}
285
286/// The music of the biome.
287///
288/// Some biomes have unique music that only play when inside them.
289#[derive(Debug, Clone, Serialize, Deserialize)]
290#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
291pub struct BiomeMusic {
292    pub replace_current_music: bool,
293    pub max_delay: u32,
294    pub min_delay: u32,
295    pub sound: azalea_registry::SoundEvent,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
299#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
300pub struct BiomeMoodSound {
301    pub tick_delay: u32,
302    pub block_search_extent: u32,
303    pub offset: f32,
304    pub sound: azalea_registry::SoundEvent,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
308#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
309pub struct AdditionsSound {
310    pub tick_chance: f32,
311    pub sound: azalea_registry::SoundEvent,
312}
313
314/// Biome particles.
315///
316/// Some biomes have particles that spawn in the air.
317#[derive(Debug, Clone, Serialize, Deserialize)]
318#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
319pub struct BiomeParticle {
320    pub probability: f32,
321    pub options: HashMap<String, String>,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
325#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
326pub struct TrimPatternElement {
327    #[simdnbt(flatten)]
328    pub pattern: HashMap<String, String>,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
332#[cfg_attr(feature = "strict_registry", simdnbt(deny_unknown_fields))]
333pub struct DamageTypeElement {
334    pub message_id: String,
335    pub scaling: String,
336    pub exhaustion: f32,
337    pub effects: Option<String>,
338    pub death_message_type: Option<String>,
339}