use azalea_buf::McBuf; use azalea_protocol_macros::ClientboundConfigurationPacket; use self::registry::RegistryHolder; #[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] pub struct ClientboundRegistryDataPacket { pub registry_holder: RegistryHolder, } pub mod registry { //! [ClientboundRegistryDataPacket](super::ClientboundRegistryDataPacket) //! Registry Structures //! //! This module contains the structures used to represent the registry //! sent to the client upon login. This contains a lot of information about //! the game, including the types of chat messages, dimensions, and //! biomes. use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use azalea_core::resource_location::ResourceLocation; use azalea_nbt::Nbt; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::{collections::HashMap, io::Cursor}; /// The base of the registry. /// /// This is the registry that is sent to the client upon login. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RegistryHolder { pub registries: HashMap, } impl TryFrom for RegistryHolder { type Error = serde_json::Error; fn try_from(value: Nbt) -> Result { Ok(RegistryHolder { registries: serde_json::from_value(serde_json::to_value(value)?)?, }) } } impl TryInto for RegistryHolder { type Error = serde_json::Error; fn try_into(self) -> Result { serde_json::from_value(serde_json::to_value(self.registries)?) } } impl McBufReadable for RegistryHolder { fn read_from(buf: &mut Cursor<&[u8]>) -> Result { RegistryHolder::try_from(Nbt::read_from(buf)?) .map_err(|e| BufReadError::Deserialization { source: e }) } } impl McBufWritable for RegistryHolder { fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { TryInto::::try_into(self.clone())?.write_into(buf) } } /// A collection of values for a certain type of registry data. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct RegistryType { #[serde(rename = "type")] pub kind: ResourceLocation, pub value: Vec>, } /// A value for a certain type of registry data. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct TypeValue { pub id: u32, pub name: ResourceLocation, pub element: T, } #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct TrimMaterialElement { pub asset_name: String, pub ingredient: ResourceLocation, pub item_model_index: f32, pub override_armor_materials: HashMap, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, } /// Data about a kind of chat message #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct ChatTypeElement { pub chat: ChatTypeData, pub narration: ChatTypeData, } /// Data about a chat message. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct ChatTypeData { pub translation_key: String, pub parameters: Vec, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub style: Option, } /// The style of a chat message. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct ChatTypeStyle { #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub color: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] #[serde(with = "Convert")] pub bold: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] #[serde(with = "Convert")] pub italic: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] #[serde(with = "Convert")] pub underlined: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] #[serde(with = "Convert")] pub strikethrough: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] #[serde(with = "Convert")] pub obfuscated: Option, } /// Dimension attributes. #[cfg(feature = "strict_registry")] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct DimensionTypeElement { pub ambient_light: f32, #[serde(with = "Convert")] pub bed_works: bool, pub coordinate_scale: f32, pub effects: ResourceLocation, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub fixed_time: Option, #[serde(with = "Convert")] pub has_ceiling: bool, #[serde(with = "Convert")] pub has_raids: bool, #[serde(with = "Convert")] pub has_skylight: bool, pub height: u32, pub infiniburn: ResourceLocation, pub logical_height: u32, pub min_y: i32, pub monster_spawn_block_light_limit: u32, pub monster_spawn_light_level: MonsterSpawnLightLevel, #[serde(with = "Convert")] pub natural: bool, #[serde(with = "Convert")] pub piglin_safe: bool, #[serde(with = "Convert")] pub respawn_anchor_works: bool, #[serde(with = "Convert")] pub ultrawarm: bool, } /// Dimension attributes. #[cfg(not(feature = "strict_registry"))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DimensionTypeElement { pub height: u32, pub min_y: i32, #[serde(flatten)] pub _extra: HashMap, } /// The light level at which monsters can spawn. /// /// This can be either a single minimum value, or a formula with a min and /// max. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub enum MonsterSpawnLightLevel { /// A simple minimum value. Simple(u32), /// A complex value with a type, minimum, and maximum. /// Vanilla minecraft only uses one type, "minecraft:uniform". Complex { #[serde(rename = "type")] kind: ResourceLocation, value: MonsterSpawnLightLevelValues, }, } /// The min and max light levels at which monsters can spawn. /// /// Values are inclusive. #[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct MonsterSpawnLightLevelValues { #[serde(rename = "min_inclusive")] pub min: u32, #[serde(rename = "max_inclusive")] pub max: u32, } /// Biome attributes. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct WorldTypeElement { #[serde(with = "Convert")] pub has_precipitation: bool, pub temperature: f32, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub temperature_modifier: Option, pub downfall: f32, pub effects: BiomeEffects, } /// The precipitation of a biome. #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub enum BiomePrecipitation { #[serde(rename = "none")] None, #[serde(rename = "rain")] Rain, #[serde(rename = "snow")] Snow, } /// The effects of a biome. /// /// This includes the sky, fog, water, and grass color, /// as well as music and other sound effects. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct BiomeEffects { pub sky_color: u32, pub fog_color: u32, pub water_color: u32, pub water_fog_color: u32, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub foliage_color: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub grass_color: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub grass_color_modifier: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub music: Option, pub mood_sound: BiomeMoodSound, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub additions_sound: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub ambient_sound: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub particle: Option, } /// The music of the biome. /// /// Some biomes have unique music that only play when inside them. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct BiomeMusic { #[serde(with = "Convert")] pub replace_current_music: bool, pub max_delay: u32, pub min_delay: u32, pub sound: azalea_registry::SoundEvent, } #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct BiomeMoodSound { pub tick_delay: u32, pub block_search_extent: u32, pub offset: f32, pub sound: azalea_registry::SoundEvent, } #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct AdditionsSound { pub tick_chance: f32, pub sound: azalea_registry::SoundEvent, } /// Biome particles. /// /// Some biomes have particles that spawn in the air. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct BiomeParticle { pub probability: f32, pub options: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct TrimPatternElement { #[serde(flatten)] pub pattern: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] pub struct DamageTypeElement { pub message_id: String, pub scaling: String, pub exhaustion: f32, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub effects: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub death_message_type: Option, } // Using a trait because you can't implement methods for // types you don't own, in this case Option and bool. trait Convert: Sized { fn serialize(&self, serializer: S) -> Result where S: Serializer; fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>; } // Convert between bool and u8 impl Convert for bool { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_u8(if *self { 1 } else { 0 }) } fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { convert::(u8::deserialize(deserializer)?) } } // Convert between Option and u8 impl Convert for Option { fn serialize(&self, serializer: S) -> Result where S: Serializer, { if let Some(value) = self { Convert::serialize(value, serializer) } else { serializer.serialize_none() } } fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { if let Some(value) = Option::::deserialize(deserializer)? { Ok(Some(convert::(value)?)) } else { Ok(None) } } } // Deserializing logic here to deduplicate code fn convert<'de, D>(value: u8) -> Result where D: Deserializer<'de>, { match value { 0 => Ok(false), 1 => Ok(true), other => Err(de::Error::invalid_value( de::Unexpected::Unsigned(other as u64), &"zero or one", )), } } }