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