azalea_protocol/common/
client_information.rs

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