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