diff options
Diffstat (limited to 'azalea-protocol/src/packets/configuration')
16 files changed, 801 insertions, 0 deletions
diff --git a/azalea-protocol/src/packets/configuration/clientbound_custom_payload_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_custom_payload_packet.rs new file mode 100644 index 00000000..0b0ea902 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/clientbound_custom_payload_packet.rs @@ -0,0 +1,10 @@ +use azalea_buf::McBuf; +use azalea_buf::UnsizedByteArray; +use azalea_core::ResourceLocation; +use azalea_protocol_macros::ClientboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] +pub struct ClientboundCustomPayloadPacket { + pub identifier: ResourceLocation, + pub data: UnsizedByteArray, +} diff --git a/azalea-protocol/src/packets/configuration/clientbound_disconnect_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_disconnect_packet.rs new file mode 100644 index 00000000..cd0ed42a --- /dev/null +++ b/azalea-protocol/src/packets/configuration/clientbound_disconnect_packet.rs @@ -0,0 +1,8 @@ +use azalea_buf::McBuf; +use azalea_chat::FormattedText; +use azalea_protocol_macros::ClientboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] +pub struct ClientboundDisconnectPacket { + pub reason: FormattedText, +} diff --git a/azalea-protocol/src/packets/configuration/clientbound_finish_configuration_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_finish_configuration_packet.rs new file mode 100644 index 00000000..81251108 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/clientbound_finish_configuration_packet.rs @@ -0,0 +1,5 @@ +use azalea_buf::McBuf; +use azalea_protocol_macros::ClientboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] +pub struct ClientboundFinishConfigurationPacket {} diff --git a/azalea-protocol/src/packets/configuration/clientbound_keep_alive_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_keep_alive_packet.rs new file mode 100644 index 00000000..83c25610 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/clientbound_keep_alive_packet.rs @@ -0,0 +1,7 @@ +use azalea_buf::McBuf; +use azalea_protocol_macros::ClientboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] +pub struct ClientboundKeepAlivePacket { + pub id: u64, +} diff --git a/azalea-protocol/src/packets/configuration/clientbound_ping_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_ping_packet.rs new file mode 100644 index 00000000..968dcaed --- /dev/null +++ b/azalea-protocol/src/packets/configuration/clientbound_ping_packet.rs @@ -0,0 +1,7 @@ +use azalea_buf::McBuf; +use azalea_protocol_macros::ClientboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] +pub struct ClientboundPingPacket { + pub id: u32, +} diff --git a/azalea-protocol/src/packets/configuration/clientbound_registry_data_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_registry_data_packet.rs new file mode 100644 index 00000000..85345a1e --- /dev/null +++ b/azalea-protocol/src/packets/configuration/clientbound_registry_data_packet.rs @@ -0,0 +1,408 @@ +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::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<ResourceLocation, Nbt>, + } + + impl TryFrom<Nbt> for RegistryHolder { + type Error = serde_json::Error; + + fn try_from(value: Nbt) -> Result<Self, Self::Error> { + Ok(RegistryHolder { + registries: serde_json::from_value(serde_json::to_value(value)?)?, + }) + } + } + + impl TryInto<Nbt> for RegistryHolder { + type Error = serde_json::Error; + + fn try_into(self) -> Result<Nbt, Self::Error> { + serde_json::from_value(serde_json::to_value(self.registries)?) + } + } + + impl McBufReadable for RegistryHolder { + fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { + 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::<Nbt>::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<T> { + #[serde(rename = "type")] + pub kind: ResourceLocation, + pub value: Vec<TypeValue<T>>, + } + + /// 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<T> { + 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<String, String>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option<String>, + } + + /// 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<String>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub style: Option<ChatTypeStyle>, + } + + /// 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<String>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub bold: Option<bool>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub italic: Option<bool>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub underlined: Option<bool>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub strikethrough: Option<bool>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "Convert")] + pub obfuscated: Option<bool>, + } + + /// 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<u32>, + #[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<String, Nbt>, + } + + /// 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<String>, + 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<u32>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub grass_color: Option<u32>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub grass_color_modifier: Option<String>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub music: Option<BiomeMusic>, + pub mood_sound: BiomeMoodSound, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub additions_sound: Option<AdditionsSound>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub ambient_sound: Option<ResourceLocation>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub particle: Option<BiomeParticle>, + } + + /// 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<String, String>, + } + + #[derive(Debug, Clone, Serialize, Deserialize)] + #[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))] + pub struct TrimPatternElement { + #[serde(flatten)] + pub pattern: HashMap<String, String>, + } + + #[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<String>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub death_message_type: Option<String>, + } + + // Using a trait because you can't implement methods for + // types you don't own, in this case Option<bool> and bool. + trait Convert: Sized { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer; + + fn deserialize<'de, D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>; + } + + // Convert between bool and u8 + impl Convert for bool { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_u8(if *self { 1 } else { 0 }) + } + + fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error> + where + D: Deserializer<'de>, + { + convert::<D>(u8::deserialize(deserializer)?) + } + } + + // Convert between Option<bool> and u8 + impl Convert for Option<bool> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + if let Some(value) = self { + Convert::serialize(value, serializer) + } else { + serializer.serialize_none() + } + } + + fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error> + where + D: Deserializer<'de>, + { + if let Some(value) = Option::<u8>::deserialize(deserializer)? { + Ok(Some(convert::<D>(value)?)) + } else { + Ok(None) + } + } + } + + // Deserializing logic here to deduplicate code + fn convert<'de, D>(value: u8) -> Result<bool, D::Error> + 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", + )), + } + } +} diff --git a/azalea-protocol/src/packets/configuration/clientbound_resource_pack_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_resource_pack_packet.rs new file mode 100644 index 00000000..b05210b5 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/clientbound_resource_pack_packet.rs @@ -0,0 +1,11 @@ +use azalea_buf::McBuf; +use azalea_chat::FormattedText; +use azalea_protocol_macros::ClientboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] +pub struct ClientboundResourcePackPacket { + pub url: String, + pub hash: String, + pub required: bool, + pub prompt: Option<FormattedText>, +} diff --git a/azalea-protocol/src/packets/configuration/clientbound_update_enabled_features_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_update_enabled_features_packet.rs new file mode 100644 index 00000000..5eedabc1 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/clientbound_update_enabled_features_packet.rs @@ -0,0 +1,8 @@ +use azalea_buf::McBuf; +use azalea_core::ResourceLocation; +use azalea_protocol_macros::ClientboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] +pub struct ClientboundUpdateEnabledFeaturesPacket { + pub features: Vec<ResourceLocation>, +} diff --git a/azalea-protocol/src/packets/configuration/clientbound_update_tags_packet.rs b/azalea-protocol/src/packets/configuration/clientbound_update_tags_packet.rs new file mode 100644 index 00000000..215c9439 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/clientbound_update_tags_packet.rs @@ -0,0 +1,73 @@ +use azalea_buf::{BufReadError, McBuf, McBufVarReadable, McBufVarWritable}; +use azalea_buf::{McBufReadable, McBufWritable}; +use azalea_core::ResourceLocation; +use azalea_protocol_macros::ClientboundConfigurationPacket; +use std::io::Cursor; +use std::ops::Deref; +use std::{collections::HashMap, io::Write}; + +#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)] +pub struct ClientboundUpdateTagsPacket { + pub tags: TagMap, +} + +#[derive(Clone, Debug)] +pub struct Tags { + pub name: ResourceLocation, + pub elements: Vec<i32>, +} + +#[derive(Clone, Debug)] +pub struct TagMap(pub HashMap<ResourceLocation, Vec<Tags>>); + +impl McBufReadable for TagMap { + fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { + let length = u32::var_read_from(buf)? as usize; + let mut data = HashMap::with_capacity(length); + for _ in 0..length { + let tag_type = ResourceLocation::read_from(buf)?; + let tags_count = i32::var_read_from(buf)? as usize; + let mut tags_vec = Vec::with_capacity(tags_count); + for _ in 0..tags_count { + let tags = Tags::read_from(buf)?; + tags_vec.push(tags); + } + data.insert(tag_type, tags_vec); + } + Ok(TagMap(data)) + } +} + +impl McBufWritable for TagMap { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + (self.len() as u32).var_write_into(buf)?; + for (k, v) in &self.0 { + k.write_into(buf)?; + v.write_into(buf)?; + } + Ok(()) + } +} +impl McBufReadable for Tags { + fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { + let name = ResourceLocation::read_from(buf)?; + let elements = Vec::<i32>::var_read_from(buf)?; + Ok(Tags { name, elements }) + } +} + +impl McBufWritable for Tags { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + self.name.write_into(buf)?; + self.elements.var_write_into(buf)?; + Ok(()) + } +} + +impl Deref for TagMap { + type Target = HashMap<ResourceLocation, Vec<Tags>>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/azalea-protocol/src/packets/configuration/mod.rs b/azalea-protocol/src/packets/configuration/mod.rs new file mode 100755 index 00000000..8244e90a --- /dev/null +++ b/azalea-protocol/src/packets/configuration/mod.rs @@ -0,0 +1,39 @@ +pub mod clientbound_custom_payload_packet; +pub mod clientbound_disconnect_packet; +pub mod clientbound_finish_configuration_packet; +pub mod clientbound_keep_alive_packet; +pub mod clientbound_ping_packet; +pub mod clientbound_registry_data_packet; +pub mod clientbound_resource_pack_packet; +pub mod clientbound_update_enabled_features_packet; +pub mod clientbound_update_tags_packet; +pub mod serverbound_client_information_packet; +pub mod serverbound_custom_payload_packet; +pub mod serverbound_finish_configuration_packet; +pub mod serverbound_keep_alive_packet; +pub mod serverbound_pong_packet; +pub mod serverbound_resource_pack_packet; +use azalea_protocol_macros::declare_state_packets; + +declare_state_packets!( + ConfigurationPacket, + Serverbound => { + 0x00: serverbound_client_information_packet::ServerboundClientInformationPacket, + 0x01: serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, + 0x02: serverbound_finish_configuration_packet::ServerboundFinishConfigurationPacket, + 0x03: serverbound_keep_alive_packet::ServerboundKeepAlivePacket, + 0x04: serverbound_pong_packet::ServerboundPongPacket, + 0x05: serverbound_resource_pack_packet::ServerboundResourcePackPacket, + }, + Clientbound => { + 0x00: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket, + 0x01: clientbound_disconnect_packet::ClientboundDisconnectPacket, + 0x02: clientbound_finish_configuration_packet::ClientboundFinishConfigurationPacket, + 0x03: clientbound_keep_alive_packet::ClientboundKeepAlivePacket, + 0x04: clientbound_ping_packet::ClientboundPingPacket, + 0x05: clientbound_registry_data_packet::ClientboundRegistryDataPacket, + 0x06: clientbound_resource_pack_packet::ClientboundResourcePackPacket, + 0x07: clientbound_update_enabled_features_packet::ClientboundUpdateEnabledFeaturesPacket, + 0x08: clientbound_update_tags_packet::ClientboundUpdateTagsPacket, + } +); diff --git a/azalea-protocol/src/packets/configuration/serverbound_client_information_packet.rs b/azalea-protocol/src/packets/configuration/serverbound_client_information_packet.rs new file mode 100644 index 00000000..2af70b83 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/serverbound_client_information_packet.rs @@ -0,0 +1,181 @@ +use azalea_buf::{McBuf, McBufReadable, McBufWritable}; +use azalea_core::FixedBitSet; +use azalea_protocol_macros::ServerboundConfigurationPacket; +use bevy_ecs::component::Component; + +#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket, PartialEq, Eq)] +pub struct ServerboundClientInformationPacket { + pub information: ClientInformation, +} + +/// A component that contains some of the "settings" for this client that are +/// sent to the server, such as render distance. This is only present on local +/// players. +#[derive(Clone, Debug, McBuf, PartialEq, Eq, Component)] +pub struct ClientInformation { + /// The locale of the client. + pub language: String, + /// The view distance of the client in chunks, same as the render distance + /// in-game. + pub view_distance: u8, + /// The types of chat messages the client wants to receive. Note that many + /// servers ignore this. + pub chat_visibility: ChatVisibility, + /// Whether the messages sent from the server should have colors. Note that + /// many servers ignore this and always send colored messages. + pub chat_colors: bool, + pub model_customization: ModelCustomization, + pub main_hand: HumanoidArm, + pub text_filtering_enabled: bool, + /// Whether the client should show up as "Anonymous Player" in the server + /// list. + pub allows_listing: bool, +} + +impl Default for ClientInformation { + fn default() -> Self { + Self { + language: "en_us".to_string(), + view_distance: 8, + chat_visibility: ChatVisibility::default(), + chat_colors: true, + model_customization: ModelCustomization::default(), + main_hand: HumanoidArm::Right, + text_filtering_enabled: false, + allows_listing: false, + } + } +} + +#[derive(McBuf, Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum ChatVisibility { + /// All chat messages should be sent to the client. + #[default] + Full = 0, + /// Chat messages from other players should be not sent to the client, only + /// messages from the server like "Player joined the game" should be sent. + System = 1, + /// No chat messages should be sent to the client. + Hidden = 2, +} + +#[derive(McBuf, Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum HumanoidArm { + Left = 0, + #[default] + Right = 1, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ModelCustomization { + pub cape: bool, + pub jacket: bool, + pub left_sleeve: bool, + pub right_sleeve: bool, + pub left_pants: bool, + pub right_pants: bool, + pub hat: bool, +} + +impl Default for ModelCustomization { + fn default() -> Self { + Self { + cape: true, + jacket: true, + left_sleeve: true, + right_sleeve: true, + left_pants: true, + right_pants: true, + hat: true, + } + } +} + +impl McBufReadable for ModelCustomization { + fn read_from(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> { + let set = FixedBitSet::<7>::read_from(buf)?; + Ok(Self { + cape: set.index(0), + jacket: set.index(1), + left_sleeve: set.index(2), + right_sleeve: set.index(3), + left_pants: set.index(4), + right_pants: set.index(5), + hat: set.index(6), + }) + } +} + +impl McBufWritable for ModelCustomization { + fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + let mut set = FixedBitSet::<7>::new(); + if self.cape { + set.set(0); + } + if self.jacket { + set.set(1); + } + if self.left_sleeve { + set.set(2); + } + if self.right_sleeve { + set.set(3); + } + if self.left_pants { + set.set(4); + } + if self.right_pants { + set.set(5); + } + if self.hat { + set.set(6); + } + set.write_into(buf) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + #[test] + fn test_client_information_packet() { + { + let data = ClientInformation::default(); + let mut buf = Vec::new(); + data.write_into(&mut buf).unwrap(); + let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf); + + let read_data = ClientInformation::read_from(&mut data_cursor).unwrap(); + assert_eq!(read_data, data); + } + + { + let data = ClientInformation { + language: "en_gb".to_string(), + view_distance: 24, + chat_visibility: ChatVisibility::Hidden, + chat_colors: false, + model_customization: ModelCustomization { + cape: false, + jacket: false, + left_sleeve: true, + right_sleeve: false, + left_pants: true, + right_pants: false, + hat: true, + }, + main_hand: HumanoidArm::Left, + text_filtering_enabled: true, + allows_listing: true, + }; + let mut buf = Vec::new(); + data.write_into(&mut buf).unwrap(); + let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf); + + let read_data = ClientInformation::read_from(&mut data_cursor).unwrap(); + assert_eq!(read_data, data); + } + } +} diff --git a/azalea-protocol/src/packets/configuration/serverbound_custom_payload_packet.rs b/azalea-protocol/src/packets/configuration/serverbound_custom_payload_packet.rs new file mode 100644 index 00000000..589256bd --- /dev/null +++ b/azalea-protocol/src/packets/configuration/serverbound_custom_payload_packet.rs @@ -0,0 +1,10 @@ +use azalea_buf::McBuf; +use azalea_buf::UnsizedByteArray; +use azalea_core::ResourceLocation; +use azalea_protocol_macros::ServerboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)] +pub struct ServerboundCustomPayloadPacket { + pub identifier: ResourceLocation, + pub data: UnsizedByteArray, +} diff --git a/azalea-protocol/src/packets/configuration/serverbound_finish_configuration_packet.rs b/azalea-protocol/src/packets/configuration/serverbound_finish_configuration_packet.rs new file mode 100644 index 00000000..53e04182 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/serverbound_finish_configuration_packet.rs @@ -0,0 +1,5 @@ +use azalea_buf::McBuf; +use azalea_protocol_macros::ServerboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)] +pub struct ServerboundFinishConfigurationPacket {} diff --git a/azalea-protocol/src/packets/configuration/serverbound_keep_alive_packet.rs b/azalea-protocol/src/packets/configuration/serverbound_keep_alive_packet.rs new file mode 100644 index 00000000..4604df46 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/serverbound_keep_alive_packet.rs @@ -0,0 +1,7 @@ +use azalea_buf::McBuf; +use azalea_protocol_macros::ServerboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)] +pub struct ServerboundKeepAlivePacket { + pub id: u64, +} diff --git a/azalea-protocol/src/packets/configuration/serverbound_pong_packet.rs b/azalea-protocol/src/packets/configuration/serverbound_pong_packet.rs new file mode 100644 index 00000000..153e3fea --- /dev/null +++ b/azalea-protocol/src/packets/configuration/serverbound_pong_packet.rs @@ -0,0 +1,7 @@ +use azalea_buf::McBuf; +use azalea_protocol_macros::ServerboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)] +pub struct ServerboundPongPacket { + pub id: u32, +} diff --git a/azalea-protocol/src/packets/configuration/serverbound_resource_pack_packet.rs b/azalea-protocol/src/packets/configuration/serverbound_resource_pack_packet.rs new file mode 100644 index 00000000..11149115 --- /dev/null +++ b/azalea-protocol/src/packets/configuration/serverbound_resource_pack_packet.rs @@ -0,0 +1,15 @@ +use azalea_buf::McBuf; +use azalea_protocol_macros::ServerboundConfigurationPacket; + +#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)] +pub struct ServerboundResourcePackPacket { + pub action: Action, +} + +#[derive(McBuf, Clone, Copy, Debug)] +pub enum Action { + SuccessfullyLoaded = 0, + Declined = 1, + FailedDownload = 2, + Accepted = 3, +} |
