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