1use bitbuffer::{BitError, BitRead, LittleEndian};
2#[cfg(feature = "write")]
3use bitbuffer::{BitWrite, BitWriteStream, Endianness};
4use serde::{Deserialize, Serialize};
5
6use crate::demo::data::MaybeUtf8String;
7use crate::demo::message::packetentities::EntityId;
8use crate::{ReadResult, Stream};
9
10#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
11#[derive(BitRead, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
12#[cfg_attr(feature = "write", derive(BitWrite))]
13#[repr(u8)]
14#[discriminant_bits = 8]
15pub enum UserMessageType {
16 Geiger = 0,
17 Train = 1,
18 HudText = 2,
19 SayText = 3,
20 SayText2 = 4,
21 TextMsg = 5,
22 ResetHUD = 6,
23 GameTitle = 7,
24 ItemPickup = 8,
25 ShowMenu = 9,
26 Shake = 10,
27 Fade = 11,
28 VGuiMenu = 12,
29 Rumble = 13,
30 CloseCaption = 14,
31 SendAudio = 15,
32 VoiceMask = 16,
33 RequestState = 17,
34 Damage = 18,
35 HintText = 19,
36 KeyHintText = 20,
37 HudMsg = 21,
38 AmmoDenied = 22,
39 AchievementEvent = 23,
40 UpdateRadar = 24,
41 VoiceSubtitle = 25,
42 HudNotify = 26,
43 HudNotifyCustom = 27,
44 PlayerStatsUpdate = 28,
45 PlayerIgnited = 29,
46 PlayerIgnitedInv = 30,
47 HudArenaNotify = 31,
48 UpdateAchievement = 32,
49 TrainingMsg = 33,
50 TrainingObjective = 34,
51 DamageDodged = 35,
52 PlayerJarated = 36,
53 PlayerExtinguished = 37,
54 PlayerJaratedFade = 38,
55 PlayerShieldBlocked = 39,
56 BreakModel = 40,
57 CheapBreakModel = 41,
58 BreakModelPumpkin = 42,
59 BreakModelRocketDud = 43,
60 CallVoteFailed = 44,
61 VoteStart = 45,
62 VotePass = 46,
63 VoteFailed = 47,
64 VoteSetup = 48,
65 PlayerBonusPoints = 49,
66 SpawnFlyingBird = 50,
67 PlayerGodRayEffect = 51,
68 SPHapWeapEvent = 52,
69 HapDmg = 53,
70 HapPunch = 54,
71 HapSetDrag = 55,
72 HapSet = 56,
73 HapMeleeContact = 57,
74 Unknown = 255,
75}
76
77impl PartialEq<u8> for UserMessageType {
78 fn eq(&self, other: &u8) -> bool {
79 *self as u8 == *other
80 }
81}
82
83#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
84#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
85#[serde(bound(deserialize = "'a: 'static"))]
86#[serde(tag = "type")]
87pub enum UserMessage<'a> {
88 SayText2(Box<SayText2Message>),
89 Text(Box<TextMessage>),
90 ResetHUD(ResetHudMessage),
91 Train(TrainMessage),
92 VoiceSubtitle(VoiceSubtitleMessage),
93 Shake(ShakeMessage),
94 VGuiMenu(VGuiMenuMessage),
95 Rumble(RumbleMessage),
96 Fade(FadeMessage),
97 HapMeleeContact(HapMeleeContactMessage),
98 Unknown(UnknownUserMessage<'a>),
99}
100
101impl UserMessage<'_> {
102 pub fn message_type(&self) -> u8 {
103 match self {
104 UserMessage::SayText2(_) => UserMessageType::SayText2 as u8,
105 UserMessage::Text(_) => UserMessageType::TextMsg as u8,
106 UserMessage::ResetHUD(_) => UserMessageType::ResetHUD as u8,
107 UserMessage::Train(_) => UserMessageType::Train as u8,
108 UserMessage::VoiceSubtitle(_) => UserMessageType::VoiceSubtitle as u8,
109 UserMessage::Shake(_) => UserMessageType::Shake as u8,
110 UserMessage::VGuiMenu(_) => UserMessageType::VGuiMenu as u8,
111 UserMessage::Rumble(_) => UserMessageType::Rumble as u8,
112 UserMessage::Fade(_) => UserMessageType::Fade as u8,
113 UserMessage::HapMeleeContact(_) => UserMessageType::HapMeleeContact as u8,
114 UserMessage::Unknown(msg) => msg.raw_type,
115 }
116 }
117}
118
119impl<'a> BitRead<'a, LittleEndian> for UserMessage<'a> {
120 fn read(stream: &mut Stream<'a>) -> ReadResult<Self> {
121 let message = match stream.read() {
122 Ok(message_type) => {
123 let length = stream.read_int(11)?;
124 let mut data = stream.read_bits(length)?;
125 match message_type {
126 UserMessageType::SayText2 => UserMessage::SayText2(data.read()?),
127 UserMessageType::TextMsg => UserMessage::Text(data.read()?),
128 UserMessageType::ResetHUD => UserMessage::ResetHUD(data.read()?),
129 UserMessageType::Train => UserMessage::Train(data.read()?),
130 UserMessageType::VoiceSubtitle => UserMessage::VoiceSubtitle(data.read()?),
131 UserMessageType::Shake => UserMessage::Shake(data.read()?),
132 UserMessageType::VGuiMenu => UserMessage::VGuiMenu(data.read()?),
133 UserMessageType::Rumble => UserMessage::Rumble(data.read()?),
134 UserMessageType::Fade => UserMessage::Fade(data.read()?),
135 UserMessageType::HapMeleeContact => UserMessage::HapMeleeContact(data.read()?),
136 _ => UserMessage::Unknown(UnknownUserMessage {
137 raw_type: message_type as u8,
138 data,
139 }),
140 }
141 }
142 Err(BitError::UnmatchedDiscriminant { discriminant, .. }) => {
143 let length = stream.read_int(11)?;
144 let data = stream.read_bits(length)?;
145 UserMessage::Unknown(UnknownUserMessage {
146 raw_type: discriminant as u8,
147 data,
148 })
149 }
150 Err(e) => return Err(e),
151 };
152
153 Ok(message)
154 }
155
156 fn skip(stream: &mut Stream) -> ReadResult<()> {
157 stream.skip_bits(8)?;
158 let length: u32 = stream.read_int(11)?;
159 stream.skip_bits(length as usize)
160 }
161}
162
163#[cfg(feature = "write")]
164impl BitWrite<LittleEndian> for UserMessage<'_> {
165 fn write(&self, stream: &mut BitWriteStream<LittleEndian>) -> ReadResult<()> {
166 self.message_type().write(stream)?;
167 stream.reserve_length(11, |stream| match self {
168 UserMessage::SayText2(body) => stream.write(body),
169 UserMessage::Text(body) => stream.write(body),
170 UserMessage::ResetHUD(body) => stream.write(body),
171 UserMessage::Train(body) => stream.write(body),
172 UserMessage::VoiceSubtitle(body) => stream.write(body),
173 UserMessage::Shake(body) => stream.write(body),
174 UserMessage::VGuiMenu(body) => stream.write(body),
175 UserMessage::Rumble(body) => stream.write(body),
176 UserMessage::Fade(body) => stream.write(body),
177 UserMessage::HapMeleeContact(body) => stream.write(body),
178 UserMessage::Unknown(body) => stream.write(&body.data),
179 })?;
180
181 Ok(())
182 }
183}
184
185#[test]
186#[cfg(feature = "write")]
187fn test_user_message_roundtrip() {
188 crate::test_roundtrip_write(UserMessage::Train(TrainMessage { data: 12 }));
189 crate::test_roundtrip_write(UserMessage::SayText2(Box::new(SayText2Message {
190 client: 3u32.into(),
191 raw: 1,
192 kind: ChatMessageKind::ChatTeamDead,
193 from: Some("Old Billy Riley".into()),
194 text: "[P-REC] Stop record.".into(),
195 })));
196}
197
198#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
199#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
200pub enum ChatMessageKind {
201 #[serde(rename = "TF_Chat_All")]
202 ChatAll,
203 #[serde(rename = "TF_Chat_Team")]
204 ChatTeam,
205 #[serde(rename = "TF_Chat_AllDead")]
206 ChatAllDead,
207 #[serde(rename = "TF_Chat_Team_Dead")]
208 ChatTeamDead,
209 #[serde(rename = "TF_Chat_AllSpec")]
210 ChatAllSpec,
211 NameChange,
212 Empty,
213}
214
215impl BitRead<'_, LittleEndian> for ChatMessageKind {
216 fn read(stream: &mut Stream) -> ReadResult<Self> {
217 let raw: String = stream.read()?;
218 Ok(match raw.as_str() {
219 "TF_Chat_Team" => ChatMessageKind::ChatTeam,
220 "TF_Chat_AllDead" => ChatMessageKind::ChatAllDead,
221 "TF_Chat_Team_Dead" => ChatMessageKind::ChatTeamDead,
222 "#TF_Name_Change" => ChatMessageKind::NameChange,
223 "TF_Chat_All" => ChatMessageKind::ChatAll,
224 "TF_Chat_AllSpec" => ChatMessageKind::ChatAllSpec,
225 _ => ChatMessageKind::ChatAll,
226 })
227 }
228}
229
230#[cfg(feature = "write")]
231impl BitWrite<LittleEndian> for ChatMessageKind {
232 fn write(&self, stream: &mut BitWriteStream<LittleEndian>) -> ReadResult<()> {
233 match self {
234 ChatMessageKind::ChatAll => "TF_Chat_All",
235 ChatMessageKind::ChatTeam => "TF_Chat_Team",
236 ChatMessageKind::ChatAllDead => "TF_Chat_AllDead",
237 ChatMessageKind::ChatTeamDead => "TF_Chat_Team_Dead",
238 ChatMessageKind::ChatAllSpec => "TF_Chat_AllSpec",
239 ChatMessageKind::NameChange => "#TF_Name_Change",
240 ChatMessageKind::Empty => "",
241 }
242 .write(stream)
243 }
244}
245
246#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
248pub struct SayText2Message {
249 pub client: EntityId,
250 pub raw: u8,
251 pub kind: ChatMessageKind,
252 pub from: Option<MaybeUtf8String>,
253 pub text: MaybeUtf8String,
254}
255
256fn to_plain_text(text: &str) -> String {
257 let mut text = text.replace(|c| c <= char::from(6), "");
259 while let Some(pos) = text.chars().enumerate().find_map(|(index, c)| {
261 if c == char::from(7) {
262 Some(index)
263 } else {
264 None
265 }
266 }) {
267 text = text
268 .chars()
269 .take(pos)
270 .chain(text.chars().skip(pos + 7))
271 .collect();
272 }
273 while let Some(pos) = text.chars().enumerate().find_map(|(index, c)| {
275 if c == char::from(9) {
276 Some(index)
277 } else {
278 None
279 }
280 }) {
281 text = text
282 .chars()
283 .take(pos)
284 .chain(text.chars().skip(pos + 9))
285 .collect();
286 }
287 text
288}
289
290impl SayText2Message {
291 pub fn plain_text(&self) -> String {
292 to_plain_text(self.text.as_ref())
293 }
294}
295
296impl BitRead<'_, LittleEndian> for SayText2Message {
297 fn read(stream: &mut Stream) -> ReadResult<Self> {
298 let client = EntityId::from(stream.read::<u8>()? as u32);
299 let raw = stream.read()?;
300 let (kind, from, text): (ChatMessageKind, Option<MaybeUtf8String>, MaybeUtf8String) = {
301 let next_byte = stream.read::<u8>()?;
302 stream.set_pos(stream.pos() - 8)?;
303 if next_byte > 0 && next_byte <= 8 {
304 let text: MaybeUtf8String = stream.read()?;
309 (ChatMessageKind::ChatAll, None, text)
310 } else {
311 let kind = stream.read()?;
312 let from = stream.read()?;
313 let text = stream.read()?;
314
315 if stream.bits_left() >= 16 {
317 let _: u16 = stream.read()?;
318 }
319 (kind, Some(from), text)
320 }
321 };
322
323 Ok(SayText2Message {
324 client,
325 raw,
326 kind,
327 from,
328 text,
329 })
330 }
331}
332
333#[cfg(feature = "write")]
334impl BitWrite<LittleEndian> for SayText2Message {
335 fn write(&self, stream: &mut BitWriteStream<LittleEndian>) -> ReadResult<()> {
336 (u32::from(self.client) as u8).write(stream)?;
337 self.raw.write(stream)?;
338
339 if let Some(from) = self.from.as_ref().map(|s| s.as_ref()) {
340 self.kind.write(stream)?;
341 from.write(stream)?;
342 self.text.write(stream)?;
343 0u16.write(stream)?;
344 } else {
345 self.text.write(stream)?;
346 }
347
348 Ok(())
349 }
350}
351
352#[test]
353#[cfg(feature = "write")]
354fn test_say_text2_roundtrip() {
355 crate::test_roundtrip_write(SayText2Message {
356 client: 3u32.into(),
357 raw: 1,
358 kind: ChatMessageKind::ChatTeamDead,
359 from: Some("Old Billy Riley".into()),
360 text: "[P-REC] Stop record.".into(),
361 });
362}
363
364#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
365#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
366#[cfg_attr(feature = "write", derive(BitWrite))]
367#[discriminant_bits = 8]
368pub enum HudTextLocation {
369 PrintNotify = 1,
370 PrintConsole,
371 PrintTalk,
372 PrintCenter,
373}
374
375#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
376#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
377#[cfg_attr(feature = "write", derive(BitWrite))]
378pub struct TextMessage {
379 pub location: HudTextLocation,
380 pub text: MaybeUtf8String,
381 pub substitute: [MaybeUtf8String; 4],
382}
383
384impl TextMessage {
385 pub fn plain_text(&self) -> String {
386 to_plain_text(self.text.as_ref())
387 }
388}
389
390#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
391#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
392#[cfg_attr(feature = "write", derive(BitWrite))]
393pub struct ResetHudMessage {
394 pub data: u8,
395}
396
397#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
398#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
399#[cfg_attr(feature = "write", derive(BitWrite))]
400pub struct TrainMessage {
401 pub data: u8,
402}
403
404#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
405#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
406#[cfg_attr(feature = "write", derive(BitWrite))]
407pub struct VoiceSubtitleMessage {
408 pub client: u8,
409 pub menu: u8,
410 pub item: u8,
411}
412
413#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
414#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
415#[cfg_attr(feature = "write", derive(BitWrite))]
416pub struct ShakeMessage {
417 pub command: u8,
418 pub amplitude: f32,
419 pub frequency: f32,
420 pub duration: f32,
421}
422
423#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
424#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
425pub struct VGuiMenuMessage {
426 pub name: MaybeUtf8String,
427 pub show: u8,
428 #[size_bits = 8]
429 pub data: Vec<VGuiMenuMessageData>,
430}
431
432#[cfg(feature = "write")]
433impl<E: Endianness> BitWrite<E> for VGuiMenuMessage {
434 fn write(&self, stream: &mut BitWriteStream<E>) -> ReadResult<()> {
435 self.name.write(stream)?;
436 self.show.write(stream)?;
437 (self.data.len() as u8).write(stream)?;
438 for item in &self.data {
439 item.write(stream)?;
440 }
441 Ok(())
442 }
443}
444
445#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
446#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
447#[cfg_attr(feature = "write", derive(BitWrite))]
448pub struct VGuiMenuMessageData {
449 pub key: MaybeUtf8String,
450 pub data: MaybeUtf8String,
451}
452
453#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
454#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
455#[cfg_attr(feature = "write", derive(BitWrite))]
456pub struct RumbleMessage {
457 pub waveform_index: u8,
458 pub rumble_data: u8,
459 pub rumble_flags: u8,
460}
461
462#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
463#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
464#[cfg_attr(feature = "write", derive(BitWrite))]
465pub struct FadeMessage {
466 pub duration: u16,
467 pub hold: u16,
468 pub flags: u16,
469 pub color: [u8; 4],
470}
471
472#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
473#[derive(BitRead, Debug, Clone, PartialEq, Serialize, Deserialize)]
474#[cfg_attr(feature = "write", derive(BitWrite))]
475pub struct HapMeleeContactMessage {
476 pub data: u8,
477}
478
479#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
480#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
481#[serde(bound(deserialize = "'a: 'static"))]
482pub struct UnknownUserMessage<'a> {
483 pub raw_type: u8,
484 pub data: Stream<'a>,
485}