1use crate::demo::data::{DemoTick, ServerTick};
2use crate::demo::gameevent_gen::{
3 GameEvent, PlayerDeathEvent, PlayerSpawnEvent, TeamPlayRoundWinEvent,
4};
5use crate::demo::message::packetentities::EntityId;
6use crate::demo::message::usermessage::{
7 ChatMessageKind, HudTextLocation, SayText2Message, TextMessage, UserMessage,
8};
9use crate::demo::message::{Message, MessageType};
10use crate::demo::packet::stringtable::StringTableEntry;
11use crate::demo::parser::handler::{BorrowMessageHandler, MessageHandler};
12use crate::demo::vector::Vector;
13use crate::{ParserState, ReadResult, Stream};
14use bitbuffer::{BitWrite, BitWriteStream, Endianness};
15use num_enum::TryFromPrimitive;
16use parse_display::{Display, FromStr};
17use serde::de::Error;
18use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
19use std::collections::{BTreeMap, HashMap};
20use std::convert::TryFrom;
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23pub struct ChatMessage {
24 pub kind: ChatMessageKind,
25 pub from: String,
26 pub text: String,
27 pub tick: DemoTick,
28}
29
30impl ChatMessage {
31 pub fn from_message(message: &SayText2Message, tick: DemoTick) -> Self {
32 ChatMessage {
33 kind: message.kind,
34 from: message
35 .from
36 .as_ref()
37 .map(|s| s.to_string())
38 .unwrap_or_default(),
39 text: message.plain_text(),
40 tick,
41 }
42 }
43
44 pub fn from_text(message: &TextMessage, tick: DemoTick) -> Self {
45 ChatMessage {
46 kind: ChatMessageKind::Empty,
47 from: String::new(),
48 text: message.plain_text(),
49 tick,
50 }
51 }
52}
53
54#[derive(
55 Debug,
56 Clone,
57 Serialize,
58 Deserialize,
59 Copy,
60 PartialEq,
61 Eq,
62 Hash,
63 TryFromPrimitive,
64 Default,
65 Display,
66)]
67#[serde(rename_all = "lowercase")]
68#[repr(u8)]
69pub enum Team {
70 #[default]
71 Other = 0,
72 Spectator = 1,
73 Red = 2,
74 Blue = 3,
75}
76
77impl Team {
78 pub fn new<U>(number: U) -> Self
79 where
80 u8: TryFrom<U>,
81 {
82 Team::try_from(u8::try_from(number).unwrap_or_default()).unwrap_or_default()
83 }
84
85 pub fn is_player(&self) -> bool {
86 *self == Team::Red || *self == Team::Blue
87 }
88}
89
90#[derive(
91 Debug, Clone, Serialize, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Display, FromStr, Default,
92)]
93#[display(style = "lowercase")]
94#[serde(rename_all = "lowercase")]
95#[repr(u8)]
96pub enum Class {
97 #[default]
98 Other = 0,
99 Scout = 1,
100 Sniper = 2,
101 Soldier = 3,
102 Demoman = 4,
103 Medic = 5,
104 Heavy = 6,
105 Pyro = 7,
106 Spy = 8,
107 Engineer = 9,
108}
109
110impl<'de> Deserialize<'de> for Class {
111 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
112 where
113 D: Deserializer<'de>,
114 {
115 #[derive(Deserialize, Debug)]
116 #[serde(untagged)]
117 enum IntOrStr<'a> {
118 Int(u8),
119 Str(&'a str),
120 }
121
122 let raw = IntOrStr::deserialize(deserializer)?;
123 match raw {
124 IntOrStr::Int(class) => Class::try_from_primitive(class).map_err(D::Error::custom),
125 IntOrStr::Str(class) if class.len() == 1 => {
126 Class::try_from_primitive(class.parse().map_err(D::Error::custom)?)
127 .map_err(D::Error::custom)
128 }
129 IntOrStr::Str(class) => class.parse().map_err(D::Error::custom),
130 }
131 }
132}
133
134#[test]
135fn test_class_deserialize() {
136 assert_eq!(Class::Scout, serde_json::from_str(r#""scout""#).unwrap());
137 assert_eq!(Class::Scout, serde_json::from_str(r#""1""#).unwrap());
138 assert_eq!(Class::Scout, serde_json::from_str("1").unwrap());
139}
140
141impl Class {
142 pub fn new<U>(number: U) -> Self
143 where
144 u8: TryFrom<U>,
145 {
146 Class::try_from(u8::try_from(number).unwrap_or_default()).unwrap_or_default()
147 }
148}
149
150#[derive(Default, Debug, Eq, PartialEq, Deserialize, Clone)]
151#[serde(from = "HashMap<Class, u16>")]
152pub struct ClassList([u16; 10]);
153
154impl ClassList {
155 pub fn iter(&self) -> impl Iterator<Item = (Class, u16)> + '_ {
157 self.0
158 .iter()
159 .copied()
160 .enumerate()
161 .map(|(class, count)| (Class::new(class), count))
162 .filter(|(_, count)| *count > 0)
163 }
164
165 pub fn sorted(&self) -> impl Iterator<Item = (Class, u16)> {
167 let mut classes = self.iter().collect::<Vec<(Class, u16)>>();
168 classes.sort_by(|a, b| a.1.cmp(&b.1).reverse());
169 classes.into_iter()
170 }
171
172 pub fn get(&self, class: Class) -> u16 {
173 #[allow(clippy::indexing_slicing)]
175 self.0[class as u8 as usize]
176 }
177
178 pub fn get_mut(&mut self, class: Class) -> &mut u16 {
179 #[allow(clippy::indexing_slicing)]
181 &mut self.0[class as u8 as usize]
182 }
183}
184
185#[test]
186fn test_classlist_sorted() {
187 let list = ClassList([0, 1, 5, 0, 0, 3, 0, 0, 0, 0]);
188 assert_eq!(
189 list.sorted().collect::<Vec<_>>(),
190 &[(Class::Sniper, 5), (Class::Medic, 3), (Class::Scout, 1)]
191 )
192}
193
194impl Serialize for ClassList {
195 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
196 where
197 S: Serializer,
198 {
199 let count = self.0.iter().filter(|c| **c > 0).count();
200 let mut classes = serializer.serialize_map(Some(count))?;
201 for (class, count) in self.0.iter().copied().enumerate() {
202 if count > 0 {
203 classes.serialize_entry(&class, &count)?;
204 }
205 }
206
207 classes.end()
208 }
209}
210
211impl From<HashMap<Class, u16>> for ClassList {
212 fn from(map: HashMap<Class, u16>) -> Self {
213 let mut classes = ClassList::default();
214
215 for (class, count) in map.into_iter() {
216 *classes.get_mut(class) = count;
217 }
218
219 classes
220 }
221}
222
223#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
224#[derive(
225 Debug,
226 Clone,
227 Serialize,
228 Deserialize,
229 Copy,
230 PartialEq,
231 Eq,
232 Hash,
233 Ord,
234 PartialOrd,
235 Default,
236 Display,
237)]
238pub struct UserId(u16);
239
240impl<E: Endianness> BitWrite<E> for UserId {
241 fn write(&self, stream: &mut BitWriteStream<E>) -> ReadResult<()> {
242 (self.0 as u32).write(stream)
243 }
244}
245
246impl From<u32> for UserId {
247 fn from(int: u32) -> Self {
248 UserId(int as u16)
249 }
250}
251
252impl From<u16> for UserId {
253 fn from(int: u16) -> Self {
254 UserId(int)
255 }
256}
257
258impl From<UserId> for u16 {
259 fn from(id: UserId) -> Self {
260 id.0
261 }
262}
263
264impl From<UserId> for u32 {
265 fn from(id: UserId) -> Self {
266 id.0 as u32
267 }
268}
269
270impl PartialEq<u16> for UserId {
271 fn eq(&self, other: &u16) -> bool {
272 self.0 == *other
273 }
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
277pub struct Spawn {
278 pub user: UserId,
279 pub class: Class,
280 pub team: Team,
281 pub tick: DemoTick,
282}
283
284impl Spawn {
285 pub fn from_event(event: &PlayerSpawnEvent, tick: DemoTick) -> Self {
286 Spawn {
287 user: UserId::from(event.user_id),
288 class: Class::new(event.class),
289 team: Team::new(event.team),
290 tick,
291 }
292 }
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
296#[serde(rename_all = "camelCase")]
297pub struct UserInfo {
298 pub classes: ClassList,
299 pub name: String,
300 pub user_id: UserId,
301 pub steam_id: String,
302 #[serde(skip)]
303 pub entity_id: EntityId,
304 pub team: Team,
305}
306
307impl From<crate::demo::data::UserInfo> for UserInfo {
308 fn from(info: crate::demo::data::UserInfo) -> Self {
309 UserInfo {
310 classes: ClassList::default(),
311 name: info.player_info.name,
312 user_id: info.player_info.user_id,
313 steam_id: info.player_info.steam_id,
314 entity_id: info.entity_id,
315 team: Team::default(),
316 }
317 }
318}
319
320impl PartialEq for UserInfo {
321 fn eq(&self, other: &UserInfo) -> bool {
322 self.classes == other.classes
323 && self.name == other.name
324 && self.user_id == other.user_id
325 && self.steam_id == other.steam_id
326 && self.team == other.team
327 }
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
331pub struct Death {
332 pub weapon: String,
333 pub victim: UserId,
334 pub assister: Option<UserId>,
335 pub killer: UserId,
336 pub tick: DemoTick,
337}
338
339impl Death {
340 pub fn from_event(event: &PlayerDeathEvent, tick: DemoTick) -> Self {
341 let assister = if event.assister < (16 * 1024) {
342 Some(UserId::from(event.assister))
343 } else {
344 None
345 };
346 Death {
347 assister,
348 tick,
349 killer: UserId::from(event.attacker),
350 weapon: event.weapon.to_string(),
351 victim: UserId::from(event.user_id),
352 }
353 }
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
357pub struct Round {
358 pub winner: Team,
359 pub length: f32,
360 pub end_tick: DemoTick,
361}
362
363impl Round {
364 pub fn from_event(event: &TeamPlayRoundWinEvent, tick: DemoTick) -> Self {
365 Round {
366 winner: Team::new(event.team),
367 length: event.round_time,
368 end_tick: tick,
369 }
370 }
371}
372
373#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
374pub struct World {
375 pub boundary_min: Vector,
376 pub boundary_max: Vector,
377}
378
379#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
380pub struct Analyser {
381 state: MatchState,
382 pause_start: Option<DemoTick>,
383 user_id_map: HashMap<EntityId, UserId>,
384}
385
386#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
387pub struct Pause {
388 from: DemoTick,
389 to: DemoTick,
390}
391
392impl MessageHandler for Analyser {
393 type Output = MatchState;
394
395 fn does_handle(message_type: MessageType) -> bool {
396 matches!(
397 message_type,
398 MessageType::GameEvent
399 | MessageType::UserMessage
400 | MessageType::ServerInfo
401 | MessageType::NetTick
402 | MessageType::SetPause
403 )
404 }
405
406 fn handle_message(&mut self, message: &Message, tick: DemoTick, _parser_state: &ParserState) {
407 match message {
408 Message::NetTick(msg) => {
409 if self.state.start_tick == 0 {
410 self.state.start_tick = msg.tick;
411 }
412 }
413 Message::ServerInfo(message) => {
414 self.state.interval_per_tick = message.interval_per_tick
415 }
416 Message::GameEvent(message) => self.handle_event(&message.event, tick),
417 Message::UserMessage(message) => self.handle_user_message(message, tick),
418 Message::SetPause(message) => {
419 if message.pause {
420 self.pause_start = Some(tick);
421 } else {
422 let start = self.pause_start.unwrap_or_default();
423 self.state.pauses.push(Pause {
424 from: start,
425 to: tick,
426 })
427 }
428 }
429 _ => {}
430 }
431 }
432
433 fn handle_string_entry(
434 &mut self,
435 table: &str,
436 index: usize,
437 entry: &StringTableEntry,
438 _parser_state: &ParserState,
439 ) {
440 if table == "userinfo" {
441 let _ = self.parse_user_info(
442 index,
443 entry.text.as_ref().map(|s| s.as_ref()),
444 entry.extra_data.as_ref().map(|data| data.data.clone()),
445 );
446 }
447 }
448
449 fn into_output(self, _state: &ParserState) -> Self::Output {
450 self.state
451 }
452}
453
454impl BorrowMessageHandler for Analyser {
455 fn borrow_output(&self, _state: &ParserState) -> &Self::Output {
456 &self.state
457 }
458}
459
460impl Analyser {
461 pub fn new() -> Self {
462 Self::default()
463 }
464
465 fn handle_user_message(&mut self, message: &UserMessage, tick: DemoTick) {
466 match message {
467 UserMessage::SayText2(text_message) => {
468 if text_message.kind == ChatMessageKind::NameChange {
469 if let Some(from) = text_message.from.clone() {
470 self.change_name(from.into(), text_message.plain_text());
471 }
472 } else {
473 self.state
474 .chat
475 .push(ChatMessage::from_message(text_message, tick));
476 }
477 }
478 UserMessage::Text(text_message) => {
479 if text_message.location == HudTextLocation::PrintTalk {
480 self.state
481 .chat
482 .push(ChatMessage::from_text(text_message, tick));
483 }
484 }
485 _ => {}
486 }
487 }
488
489 fn change_name(&mut self, from: String, to: String) {
490 if let Some(user) = self.state.users.values_mut().find(|user| user.name == from) {
491 user.name = to;
492 }
493 }
494
495 fn handle_event(&mut self, event: &GameEvent, tick: DemoTick) {
496 const WIN_REASON_TIME_LIMIT: u8 = 6;
497
498 match event {
499 GameEvent::PlayerDeath(event) => self.state.deaths.push(Death::from_event(event, tick)),
500 GameEvent::PlayerSpawn(event) => {
501 let spawn = Spawn::from_event(event, tick);
502 if let Some(user_state) = self.state.users.get_mut(&spawn.user) {
503 *user_state.classes.get_mut(spawn.class) += 1;
504 user_state.team = spawn.team;
505 }
506 }
507 GameEvent::TeamPlayRoundWin(event) => {
508 if event.win_reason != WIN_REASON_TIME_LIMIT {
509 self.state.rounds.push(Round::from_event(event, tick))
510 }
511 }
512 _ => {}
513 }
514 }
515
516 fn parse_user_info(
517 &mut self,
518 index: usize,
519 text: Option<&str>,
520 data: Option<Stream>,
521 ) -> ReadResult<()> {
522 if let Some(user_info) =
523 crate::demo::data::UserInfo::parse_from_string_table(index as u16, text, data)?
524 {
525 self.state
526 .users
527 .entry(user_info.player_info.user_id)
528 .and_modify(|info| {
529 info.entity_id = user_info.entity_id;
530 })
531 .or_insert_with(|| user_info.into());
532 }
533
534 Ok(())
535 }
536}
537
538#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
539#[serde(rename_all = "camelCase")]
540pub struct MatchState {
541 pub chat: Vec<ChatMessage>,
542 pub users: BTreeMap<UserId, UserInfo>,
543 pub deaths: Vec<Death>,
544 pub rounds: Vec<Round>,
545 pub start_tick: ServerTick,
546 pub interval_per_tick: f32,
547 pub pauses: Vec<Pause>,
548}