azalea_protocol/common/
client_information.rs

1use std::io::{self, Cursor};
2
3use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite};
4use azalea_core::bitset::FixedBitSet;
5use bevy_ecs::component::Component;
6
7/// A component that contains some of the "settings" for this client that are
8/// sent to the server, such as render distance.
9///
10/// This is only present on local players.
11#[derive(Clone, Debug, AzBuf, PartialEq, Eq, Component)]
12pub struct ClientInformation {
13    /// The locale of the client.
14    pub language: String,
15    /// The view distance of the client in chunks, same as the render distance
16    /// in-game.
17    pub view_distance: u8,
18    /// The types of chat messages the client wants to receive.
19    ///
20    /// Note that this is enforced by the server, and many servers ignore this.
21    pub chat_visibility: ChatVisibility,
22    /// Whether the messages sent from the server should have colors.
23    ///
24    /// Note that many servers ignore this and always send colored messages.
25    pub chat_colors: bool,
26    pub model_customization: ModelCustomization,
27    pub main_hand: HumanoidArm,
28    pub text_filtering_enabled: bool,
29    /// Whether the client should show up as "Anonymous Player" in the server
30    /// list.
31    pub allows_listing: bool,
32    pub particle_status: ParticleStatus,
33}
34
35impl Default for ClientInformation {
36    fn default() -> Self {
37        Self {
38            language: "en_us".to_string(),
39            view_distance: 8,
40            chat_visibility: ChatVisibility::default(),
41            chat_colors: true,
42            model_customization: ModelCustomization::default(),
43            main_hand: HumanoidArm::Right,
44            text_filtering_enabled: false,
45            allows_listing: false,
46            particle_status: ParticleStatus::default(),
47        }
48    }
49}
50
51#[derive(AzBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
52pub enum ChatVisibility {
53    /// All chat messages should be sent to the client.
54    #[default]
55    Full = 0,
56    /// Chat messages from other players should be not sent to the client, only
57    /// messages from the server like "Player joined the game" should be sent.
58    System = 1,
59    /// No chat messages should be sent to the client.
60    Hidden = 2,
61}
62
63#[derive(AzBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
64pub enum HumanoidArm {
65    Left = 0,
66    #[default]
67    Right = 1,
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub struct ModelCustomization {
72    pub cape: bool,
73    pub jacket: bool,
74    pub left_sleeve: bool,
75    pub right_sleeve: bool,
76    pub left_pants: bool,
77    pub right_pants: bool,
78    pub hat: bool,
79}
80
81#[derive(AzBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
82pub enum ParticleStatus {
83    #[default]
84    All,
85    Decreased,
86    Minimal,
87}
88
89impl Default for ModelCustomization {
90    fn default() -> Self {
91        Self {
92            cape: true,
93            jacket: true,
94            left_sleeve: true,
95            right_sleeve: true,
96            left_pants: true,
97            right_pants: true,
98            hat: true,
99        }
100    }
101}
102
103impl AzaleaRead for ModelCustomization {
104    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
105        let set = FixedBitSet::<7>::azalea_read(buf)?;
106        Ok(Self {
107            cape: set.index(0),
108            jacket: set.index(1),
109            left_sleeve: set.index(2),
110            right_sleeve: set.index(3),
111            left_pants: set.index(4),
112            right_pants: set.index(5),
113            hat: set.index(6),
114        })
115    }
116}
117
118impl AzaleaWrite for ModelCustomization {
119    fn azalea_write(&self, buf: &mut impl io::Write) -> io::Result<()> {
120        let mut set = FixedBitSet::<7>::new();
121        if self.cape {
122            set.set(0);
123        }
124        if self.jacket {
125            set.set(1);
126        }
127        if self.left_sleeve {
128            set.set(2);
129        }
130        if self.right_sleeve {
131            set.set(3);
132        }
133        if self.left_pants {
134            set.set(4);
135        }
136        if self.right_pants {
137            set.set(5);
138        }
139        if self.hat {
140            set.set(6);
141        }
142        set.azalea_write(buf)
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use std::io::Cursor;
149
150    use azalea_buf::{AzaleaRead, AzaleaWrite};
151
152    use super::*;
153
154    #[test]
155    fn test_client_information() {
156        {
157            let data = ClientInformation::default();
158            let mut buf = Vec::new();
159            data.azalea_write(&mut buf).unwrap();
160            let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
161
162            let read_data = ClientInformation::azalea_read(&mut data_cursor).unwrap();
163            assert_eq!(read_data, data);
164        }
165
166        let data = ClientInformation {
167            language: "en_gb".to_string(),
168            view_distance: 24,
169            chat_visibility: ChatVisibility::Hidden,
170            chat_colors: false,
171            model_customization: ModelCustomization {
172                cape: false,
173                jacket: false,
174                left_sleeve: true,
175                right_sleeve: false,
176                left_pants: true,
177                right_pants: false,
178                hat: true,
179            },
180            main_hand: HumanoidArm::Left,
181            text_filtering_enabled: true,
182            allows_listing: true,
183            particle_status: ParticleStatus::Decreased,
184        };
185        let mut buf = Vec::new();
186        data.azalea_write(&mut buf).unwrap();
187        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
188
189        let read_data = ClientInformation::azalea_read(&mut data_cursor).unwrap();
190        assert_eq!(read_data, data);
191    }
192}