azalea_protocol/common/
client_information.rs

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