steel_protocol/packets/game/
c_player_info_update.rs1use steel_macros::ClientPacket;
2use steel_registry::packets::play::C_PLAYER_INFO_UPDATE;
3use steel_utils::codec::VarInt;
4use steel_utils::serial::PrefixedWrite;
5use text_components::TextComponent;
6use uuid::Uuid;
7
8use super::ProtocolRemoteChatSessionData;
10use crate::packets::login::GameProfileProperty;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[repr(u8)]
16pub enum PlayerInfoAction {
17 AddPlayer = 0x01,
18 InitializeChat = 0x02,
19 UpdateGameMode = 0x04,
20 UpdateListed = 0x08,
21 UpdateLatency = 0x10,
22 UpdateDisplayName = 0x20,
23 UpdateListOrder = 0x40,
24 UpdateHat = 0x80,
25}
26
27pub const PLAYER_INFO_INIT_ACTIONS: u8 = PlayerInfoAction::AddPlayer as u8
30 | PlayerInfoAction::InitializeChat as u8
31 | PlayerInfoAction::UpdateGameMode as u8
32 | PlayerInfoAction::UpdateListed as u8
33 | PlayerInfoAction::UpdateLatency as u8
34 | PlayerInfoAction::UpdateDisplayName as u8
35 | PlayerInfoAction::UpdateListOrder as u8
36 | PlayerInfoAction::UpdateHat as u8;
37
38#[derive(Debug, Clone)]
40pub enum PlayerDisplayName {
41 Reset,
43 Custom(Box<TextComponent>),
45}
46
47impl From<Option<TextComponent>> for PlayerDisplayName {
48 fn from(opt: Option<TextComponent>) -> Self {
49 match opt {
50 Some(component) => Self::Custom(Box::new(component)),
51 None => Self::Reset,
52 }
53 }
54}
55
56#[derive(Debug, Clone)]
57pub struct PlayerInfoEntry {
58 pub uuid: Uuid,
59 pub name: Option<String>,
60 pub properties: Vec<GameProfileProperty>,
61 pub chat_session: Option<ProtocolRemoteChatSessionData>,
62 pub game_mode: Option<VarInt>,
63 pub listed: Option<bool>,
64 pub latency: Option<VarInt>,
65 pub display_name: Option<PlayerDisplayName>,
66 pub list_order: Option<VarInt>,
67 pub show_hat: Option<bool>,
68}
69
70impl PlayerInfoEntry {
71 #[must_use]
73 pub const fn new(uuid: Uuid) -> Self {
74 Self {
75 uuid,
76 name: None,
77 properties: Vec::new(),
78 chat_session: None,
79 game_mode: None,
80 listed: None,
81 latency: None,
82 display_name: None,
83 list_order: None,
84 show_hat: None,
85 }
86 }
87
88 #[must_use]
90 pub const fn with_latency(mut self, latency: i32) -> Self {
91 self.latency = Some(VarInt(latency));
92 self
93 }
94
95 #[must_use]
97 pub const fn with_game_mode(mut self, game_mode: i32) -> Self {
98 self.game_mode = Some(VarInt(game_mode));
99 self
100 }
101
102 #[must_use]
104 pub const fn with_listed(mut self, listed: bool) -> Self {
105 self.listed = Some(listed);
106 self
107 }
108
109 #[must_use]
111 pub fn with_display_name(mut self, display_name: impl Into<PlayerDisplayName>) -> Self {
112 self.display_name = Some(display_name.into());
113 self
114 }
115
116 #[must_use]
118 pub const fn with_show_hat(mut self, show_hat: bool) -> Self {
119 self.show_hat = Some(show_hat);
120 self
121 }
122
123 #[must_use]
125 pub const fn with_list_order(mut self, list_order: i32) -> Self {
126 self.list_order = Some(VarInt(list_order));
127 self
128 }
129
130 #[must_use]
132 pub fn with_chat_session(mut self, chat_session: ProtocolRemoteChatSessionData) -> Self {
133 self.chat_session = Some(chat_session);
134 self
135 }
136}
137
138#[derive(ClientPacket, Debug, Clone)]
139#[packet_id(Play = C_PLAYER_INFO_UPDATE)]
140pub struct CPlayerInfoUpdate {
141 pub actions: u8, pub entries: Vec<PlayerInfoEntry>,
143}
144
145impl CPlayerInfoUpdate {
146 #[must_use]
150 pub fn create_player_initializing(
151 uuid: Uuid,
152 name: String,
153 properties: Vec<GameProfileProperty>,
154 game_mode: i32,
155 latency: i32,
156 display_name: Option<TextComponent>,
157 show_hat: bool,
158 ) -> Self {
159 Self {
160 actions: PLAYER_INFO_INIT_ACTIONS,
161 entries: vec![PlayerInfoEntry {
162 uuid,
163 name: Some(name),
164 properties,
165 chat_session: None,
166 game_mode: Some(VarInt(game_mode)),
167 listed: Some(true),
168 latency: Some(VarInt(latency)),
169 display_name: Some(display_name.into()),
170 list_order: Some(VarInt(0)),
171 show_hat: Some(show_hat),
172 }],
173 }
174 }
175
176 #[must_use]
178 pub fn update_chat_session(uuid: Uuid, chat_session: ProtocolRemoteChatSessionData) -> Self {
179 Self {
180 actions: PlayerInfoAction::InitializeChat as u8,
181 entries: vec![PlayerInfoEntry::new(uuid).with_chat_session(chat_session)],
182 }
183 }
184
185 #[must_use]
188 pub fn update_latency(entries: Vec<(Uuid, i32)>) -> Self {
189 Self {
190 actions: PlayerInfoAction::UpdateLatency as u8,
191 entries: entries
192 .into_iter()
193 .map(|(uuid, latency)| PlayerInfoEntry::new(uuid).with_latency(latency))
194 .collect(),
195 }
196 }
197
198 #[must_use]
200 pub fn update_game_mode(uuid: Uuid, game_mode: i32) -> Self {
201 Self {
202 actions: PlayerInfoAction::UpdateGameMode as u8,
203 entries: vec![PlayerInfoEntry::new(uuid).with_game_mode(game_mode)],
204 }
205 }
206
207 #[must_use]
209 pub fn update_listed(uuid: Uuid, listed: bool) -> Self {
210 Self {
211 actions: PlayerInfoAction::UpdateListed as u8,
212 entries: vec![PlayerInfoEntry::new(uuid).with_listed(listed)],
213 }
214 }
215
216 #[must_use]
218 pub fn update_display_name(uuid: Uuid, display_name: Option<TextComponent>) -> Self {
219 Self {
220 actions: PlayerInfoAction::UpdateDisplayName as u8,
221 entries: vec![PlayerInfoEntry::new(uuid).with_display_name(display_name)],
222 }
223 }
224
225 #[must_use]
227 pub fn update_hat(uuid: Uuid, show_hat: bool) -> Self {
228 Self {
229 actions: PlayerInfoAction::UpdateHat as u8,
230 entries: vec![PlayerInfoEntry::new(uuid).with_show_hat(show_hat)],
231 }
232 }
233
234 #[must_use]
236 pub fn update_list_order(uuid: Uuid, list_order: i32) -> Self {
237 Self {
238 actions: PlayerInfoAction::UpdateListOrder as u8,
239 entries: vec![PlayerInfoEntry::new(uuid).with_list_order(list_order)],
240 }
241 }
242}
243
244impl steel_utils::serial::WriteTo for CPlayerInfoUpdate {
245 fn write(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
246 self.actions.write(writer)?;
247 VarInt(self.entries.len() as i32).write(writer)?;
248
249 for entry in &self.entries {
250 entry.uuid.write(writer)?;
251
252 if self.actions & (PlayerInfoAction::AddPlayer as u8) != 0
253 && let Some(name) = &entry.name
254 {
255 name.write_prefixed::<VarInt>(writer)?;
256 VarInt(entry.properties.len() as i32).write(writer)?;
258 for prop in &entry.properties {
259 prop.write(writer)?;
260 }
261 }
262
263 if self.actions & (PlayerInfoAction::InitializeChat as u8) != 0 {
264 if let Some(session_data) = &entry.chat_session {
266 true.write(writer)?;
267 session_data.write(writer)?;
268 } else {
269 false.write(writer)?;
270 }
271 }
272
273 if self.actions & (PlayerInfoAction::UpdateGameMode as u8) != 0 {
274 let game_mode = entry.game_mode.unwrap_or(VarInt(0));
275 game_mode.write(writer)?;
276 }
277
278 if self.actions & (PlayerInfoAction::UpdateListed as u8) != 0 {
279 let listed = entry.listed.unwrap_or(true);
280 listed.write(writer)?;
281 }
282
283 if self.actions & (PlayerInfoAction::UpdateLatency as u8) != 0 {
284 let latency = entry.latency.unwrap_or(VarInt(0));
285 latency.write(writer)?;
286 }
287
288 if self.actions & (PlayerInfoAction::UpdateDisplayName as u8) != 0 {
289 match &entry.display_name {
291 Some(PlayerDisplayName::Custom(display_name)) => {
292 true.write(writer)?;
293 display_name.write(writer)?;
294 }
295 Some(PlayerDisplayName::Reset) | None => {
296 false.write(writer)?;
297 }
298 }
299 }
300
301 if self.actions & (PlayerInfoAction::UpdateListOrder as u8) != 0 {
302 let list_order = entry.list_order.unwrap_or(VarInt(0));
303 list_order.write(writer)?;
304 }
305
306 if self.actions & (PlayerInfoAction::UpdateHat as u8) != 0 {
307 let show_hat = entry.show_hat.unwrap_or(true);
308 show_hat.write(writer)?;
309 }
310 }
311
312 Ok(())
313 }
314}