1pub use crate::demo::data::game_state::{
2 Building, BuildingClass, Dispenser, GameState, Kill, PlayerState, Sentry, Teleporter, World,
3};
4use crate::demo::data::game_state::{Cart, Handle, Objective, PipeType, Projectile, ProjectileType};
5use crate::demo::data::DemoTick;
6use crate::demo::gameevent_gen::ObjectDestroyedEvent;
7use crate::demo::gamevent::GameEvent;
8use crate::demo::message::gameevent::GameEventMessage;
9use crate::demo::message::packetentities::{EntityId, PacketEntity, UpdateType};
10use crate::demo::message::Message;
11use crate::demo::packet::datatable::{ParseSendTable, ServerClass, ServerClassName};
12use crate::demo::packet::message::MessagePacketMeta;
13use crate::demo::packet::stringtable::StringTableEntry;
14pub use crate::demo::parser::analyser::{Class, Team, UserId};
15use crate::demo::parser::handler::BorrowMessageHandler;
16use crate::demo::parser::MessageHandler;
17use crate::demo::sendprop::{SendProp, SendPropIdentifier, SendPropValue};
18use crate::demo::vector::{Vector, VectorXY};
19use crate::{MessageType, ParserState, ReadResult, Stream};
20use std::convert::TryFrom;
21use std::str::FromStr;
22
23pub struct CachedEntities {}
24
25#[derive(Default, Debug)]
26pub struct GameStateAnalyser {
27 pub state: GameState,
28 tick: DemoTick,
29 class_names: Vec<ServerClassName>, }
31
32impl MessageHandler for GameStateAnalyser {
33 type Output = GameState;
34
35 fn does_handle(message_type: MessageType) -> bool {
36 matches!(
37 message_type,
38 MessageType::PacketEntities | MessageType::GameEvent | MessageType::ServerInfo
39 )
40 }
41
42 fn handle_message(&mut self, message: &Message, _tick: DemoTick, parser_state: &ParserState) {
43 match message {
44 Message::PacketEntities(message) => {
45 for entity in &message.entities {
46 self.handle_entity(entity, parser_state);
47 }
48 for id in &message.removed_entities {
49 self.state.projectile_destroy(*id);
50 self.state.remove_building(*id);
51 }
52 }
53 Message::ServerInfo(message) => {
54 self.state.interval_per_tick = message.interval_per_tick
55 }
56 Message::GameEvent(GameEventMessage { event, .. }) => {
57 self.state.events.push((self.tick, event.clone()));
58 match event {
59 GameEvent::PlayerDeath(death) => {
60 self.state.kills.push(Kill::new(self.tick, death.as_ref()))
61 }
62 GameEvent::RoundStart(_) => {
63 self.state.buildings.clear();
64 self.state.projectiles.clear();
65 }
66 GameEvent::TeamPlayRoundStart(_) => {
67 self.state.buildings.clear();
68 self.state.projectiles.clear();
69 }
70 GameEvent::ObjectDestroyed(ObjectDestroyedEvent { index, .. }) => {
71 self.state.remove_building((*index as u32).into());
72 }
73 _ => {}
74 }
75 }
76 _ => {}
77 }
78 }
79
80 fn handle_string_entry(
81 &mut self,
82 table: &str,
83 index: usize,
84 entry: &StringTableEntry,
85 _parser_state: &ParserState,
86 ) {
87 if table == "userinfo" {
88 let _ = self.parse_user_info(
89 index,
90 entry.text.as_ref().map(|s| s.as_ref()),
91 entry.extra_data.as_ref().map(|data| data.data.clone()),
92 );
93 }
94 }
95
96 fn handle_data_tables(
97 &mut self,
98 _parse_tables: &[ParseSendTable],
99 server_classes: &[ServerClass],
100 _parser_state: &ParserState,
101 ) {
102 self.class_names = server_classes
103 .iter()
104 .map(|class| &class.name)
105 .cloned()
106 .collect();
107 }
108
109 fn handle_packet_meta(
110 &mut self,
111 tick: DemoTick,
112 _meta: &MessagePacketMeta,
113 _parser_state: &ParserState,
114 ) {
115 self.state.tick = tick;
116 self.tick = tick;
117 }
118
119 fn into_output(mut self, state: &ParserState) -> Self::Output {
120 self.state.server_classes = state.server_classes.clone();
121 self.state
122 }
123}
124
125impl BorrowMessageHandler for GameStateAnalyser {
126 fn borrow_output(&self, _state: &ParserState) -> &Self::Output {
127 &self.state
128 }
129}
130
131impl GameStateAnalyser {
132 pub fn new() -> Self {
133 Self::default()
134 }
135
136 pub fn handle_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
137 const OUTER: SendPropIdentifier =
138 SendPropIdentifier::new("DT_AttributeContainer", "m_hOuter");
139
140 let Some(class_name) = self.class_names.get(usize::from(entity.server_class)) else {
141 return;
142 };
143
144 for prop in &entity.props {
145 if prop.identifier == OUTER {
146 let outer = i64::try_from(&prop.value).unwrap_or_default();
147 self.state
148 .outer_map
149 .insert(Handle(outer), entity.entity_index);
150 }
151 }
152
153 match class_name.as_str() {
154 "CTFPlayer" => self.handle_player_entity(entity, parser_state),
155 "CTFPlayerResource" => self.handle_player_resource(entity, parser_state),
156 "CWorld" => self.handle_world_entity(entity, parser_state),
157 "CObjectSentrygun" => self.handle_sentry_entity(entity, parser_state),
158 "CObjectDispenser" => self.handle_dispenser_entity(entity, parser_state),
159 "CObjectTeleporter" => self.handle_teleporter_entity(entity, parser_state),
160 "CFuncTrackTrain" => self.handle_train_entity(entity, parser_state),
161 _ if class_name.starts_with("CTFProjectile_")
162 || class_name.as_str() == "CTFGrenadePipebombProjectile" =>
163 {
164 self.handle_projectile_entity(entity, parser_state)
165 }
166 _ => {}
167 }
168 }
169
170 pub fn handle_player_resource(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
171 for prop in entity.props(parser_state) {
172 if let Some((table_name, prop_name)) = prop.identifier.names() {
173 if let Ok(player_id) = u32::from_str(prop_name.as_str()) {
174 let entity_id = EntityId::from(player_id);
175 if let Some(player) = self
176 .state
177 .players
178 .iter_mut()
179 .find(|player| player.entity == entity_id)
180 {
181 match table_name.as_str() {
182 "m_iTeam" => {
183 player.team =
184 Team::new(i64::try_from(&prop.value).unwrap_or_default())
185 }
186 "m_iMaxHealth" => {
187 player.max_health =
188 i64::try_from(&prop.value).unwrap_or_default() as u16
189 }
190 "m_iPlayerClass" => {
191 player.class =
192 Class::new(i64::try_from(&prop.value).unwrap_or_default())
193 }
194 "m_iChargeLevel" => {
195 player.charge = i64::try_from(&prop.value).unwrap_or_default() as u8
196 }
197 "m_iPing" => {
198 player.ping = i64::try_from(&prop.value).unwrap_or_default() as u16
199 }
200 _ => {}
201 }
202 }
203 }
204 }
205 }
206 }
207
208 pub fn handle_player_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
209 let player = self.state.get_or_create_player(entity.entity_index);
210
211 const HEALTH_PROP: SendPropIdentifier =
212 SendPropIdentifier::new("DT_BasePlayer", "m_iHealth");
213 const MAX_HEALTH_PROP: SendPropIdentifier =
214 SendPropIdentifier::new("DT_BasePlayer", "m_iMaxHealth");
215 const LIFE_STATE_PROP: SendPropIdentifier =
216 SendPropIdentifier::new("DT_BasePlayer", "m_lifeState");
217
218 const LOCAL_ORIGIN: SendPropIdentifier =
219 SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_vecOrigin");
220 const NON_LOCAL_ORIGIN: SendPropIdentifier =
221 SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_vecOrigin");
222 const LOCAL_ORIGIN_Z: SendPropIdentifier =
223 SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_vecOrigin[2]");
224 const NON_LOCAL_ORIGIN_Z: SendPropIdentifier =
225 SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_vecOrigin[2]");
226 const LOCAL_EYE_ANGLES: SendPropIdentifier =
227 SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_angEyeAngles[1]");
228 const NON_LOCAL_EYE_ANGLES: SendPropIdentifier =
229 SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_angEyeAngles[1]");
230 const LOCAL_PITCH_ANGLES: SendPropIdentifier =
231 SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_angEyeAngles[0]");
232 const NON_LOCAL_PITCH_ANGLES: SendPropIdentifier =
233 SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_angEyeAngles[0]");
234
235 const SIMTIME_PROP: SendPropIdentifier =
236 SendPropIdentifier::new("DT_BaseEntity", "m_flSimulationTime");
237 const PROP_BB_MAX: SendPropIdentifier =
238 SendPropIdentifier::new("DT_CollisionProperty", "m_vecMaxsPreScaled");
239 const PLAYER_COND: SendPropIdentifier =
240 SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCond");
241 const PLAYER_COND_EX1: SendPropIdentifier =
242 SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx");
243 const PLAYER_COND_EX2: SendPropIdentifier =
244 SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx2");
245 const PLAYER_COND_EX3: SendPropIdentifier =
246 SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx3");
247 const PLAYER_COND_EX4: SendPropIdentifier =
248 SendPropIdentifier::new("DT_TFPlayerShared", "m_nPlayerCondEx4");
249 const PLAYER_COND_BITS: SendPropIdentifier =
250 SendPropIdentifier::new("DT_TFPlayerConditionListExclusive", "_condition_bits");
251
252 const WEAPON_0: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "000");
253 const WEAPON_1: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "001");
254 const WEAPON_2: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "002");
255
256 player.in_pvs = entity.in_pvs;
257
258 for prop in entity.props(parser_state) {
259 match prop.identifier {
260 HEALTH_PROP => {
261 player.health = i64::try_from(&prop.value).unwrap_or_default() as u16
262 }
263 MAX_HEALTH_PROP => {
264 player.max_health = i64::try_from(&prop.value).unwrap_or_default() as u16
265 }
266 LIFE_STATE_PROP => {
267 player.state = PlayerState::new(i64::try_from(&prop.value).unwrap_or_default())
268 }
269 LOCAL_ORIGIN | NON_LOCAL_ORIGIN => {
270 let pos_xy = VectorXY::try_from(&prop.value).unwrap_or_default();
271 player.position.x = pos_xy.x;
272 player.position.y = pos_xy.y;
273 }
274 LOCAL_ORIGIN_Z | NON_LOCAL_ORIGIN_Z => {
275 player.position.z = f32::try_from(&prop.value).unwrap_or_default()
276 }
277 LOCAL_EYE_ANGLES | NON_LOCAL_EYE_ANGLES => {
278 player.view_angle = f32::try_from(&prop.value).unwrap_or_default()
279 }
280 LOCAL_PITCH_ANGLES | NON_LOCAL_PITCH_ANGLES => {
281 player.pitch_angle = f32::try_from(&prop.value).unwrap_or_default()
282 }
283 SIMTIME_PROP => {
284 player.simtime = i64::try_from(&prop.value).unwrap_or_default() as u16
285 }
286 PROP_BB_MAX => {
287 let max = Vector::try_from(&prop.value).unwrap_or_default();
288 player.bounds.max = max;
289 }
290 WEAPON_0 => {
291 let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
292 player.weapons[0] = handle;
293 }
294 WEAPON_1 => {
295 let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
296 player.weapons[1] = handle;
297 }
298 WEAPON_2 => {
299 let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
300 player.weapons[2] = handle;
301 }
302 PLAYER_COND | PLAYER_COND_BITS => {
303 player.conditions[0..4].copy_from_slice(
304 &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
305 );
306 }
307 PLAYER_COND_EX1 => {
308 player.conditions[4..8].copy_from_slice(
309 &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
310 );
311 }
312 PLAYER_COND_EX2 => {
313 player.conditions[8..12].copy_from_slice(
314 &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
315 );
316 }
317 PLAYER_COND_EX3 => {
318 player.conditions[12..16].copy_from_slice(
319 &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
320 );
321 }
322 PLAYER_COND_EX4 => {
323 player.conditions[16..20].copy_from_slice(
324 &i64::try_from(&prop.value).unwrap_or_default().to_le_bytes()[0..4],
325 );
326 }
327 _ => {}
328 }
329 }
330 }
331
332 pub fn handle_world_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
333 if let (
334 Some(SendProp {
335 value: SendPropValue::Vector(boundary_min),
336 ..
337 }),
338 Some(SendProp {
339 value: SendPropValue::Vector(boundary_max),
340 ..
341 }),
342 ) = (
343 entity.get_prop_by_name("DT_WORLD", "m_WorldMins", parser_state),
344 entity.get_prop_by_name("DT_WORLD", "m_WorldMaxs", parser_state),
345 ) {
346 self.state.world = Some(World {
347 boundary_min,
348 boundary_max,
349 })
350 }
351 }
352
353 pub fn handle_sentry_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
354 const ANGLE: SendPropIdentifier =
355 SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_angEyeAngles[1]");
356 const MINI: SendPropIdentifier =
357 SendPropIdentifier::new("DT_BaseObject", "m_bMiniBuilding");
358 const CONTROLLED: SendPropIdentifier =
359 SendPropIdentifier::new("DT_ObjectSentrygun", "m_bPlayerControlled");
360 const TARGET: SendPropIdentifier =
361 SendPropIdentifier::new("DT_ObjectSentrygun", "m_hAutoAimTarget");
362 const SHELLS: SendPropIdentifier =
363 SendPropIdentifier::new("DT_ObjectSentrygun", "m_iAmmoShells");
364 const ROCKETS: SendPropIdentifier =
365 SendPropIdentifier::new("DT_ObjectSentrygun", "m_iAmmoRockets");
366
367 if entity.update_type == UpdateType::Delete {
368 self.state.remove_building(entity.entity_index);
369 return;
370 }
371
372 self.handle_building(entity, parser_state, BuildingClass::Sentry);
373
374 let building = self
375 .state
376 .get_or_create_building(entity.entity_index, BuildingClass::Sentry);
377
378 if let Building::Sentry(sentry) = building {
379 for prop in entity.props(parser_state) {
380 match prop.identifier {
381 ANGLE => sentry.angle = f32::try_from(&prop.value).unwrap_or_default(),
382 MINI => sentry.is_mini = i64::try_from(&prop.value).unwrap_or_default() > 0,
383 CONTROLLED => {
384 sentry.player_controlled =
385 i64::try_from(&prop.value).unwrap_or_default() > 0
386 }
387 TARGET => {
388 sentry.auto_aim_target =
389 UserId::from(i64::try_from(&prop.value).unwrap_or_default() as u16)
390 }
391 SHELLS => sentry.shells = i64::try_from(&prop.value).unwrap_or_default() as u16,
392 ROCKETS => {
393 sentry.rockets = i64::try_from(&prop.value).unwrap_or_default() as u16
394 }
395 _ => {}
396 }
397 }
398 }
399 }
400
401 pub fn handle_teleporter_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
402 const RECHARGE_TIME: SendPropIdentifier =
403 SendPropIdentifier::new("DT_ObjectTeleporter", "m_flRechargeTime");
404 const RECHARGE_DURATION: SendPropIdentifier =
405 SendPropIdentifier::new("DT_ObjectTeleporter", "m_flCurrentRechargeDuration");
406 const TIMES_USED: SendPropIdentifier =
407 SendPropIdentifier::new("DT_ObjectTeleporter", "m_iTimesUsed");
408 const OTHER_END: SendPropIdentifier =
409 SendPropIdentifier::new("DT_ObjectTeleporter", "m_bMatchBuilding");
410 const YAW_TO_EXIT: SendPropIdentifier =
411 SendPropIdentifier::new("DT_ObjectTeleporter", "m_flYawToExit");
412 const IS_ENTRANCE: SendPropIdentifier =
413 SendPropIdentifier::new("DT_BaseObject", "m_iObjectMode");
414
415 if entity.update_type == UpdateType::Delete {
416 self.state.remove_building(entity.entity_index);
417 return;
418 }
419
420 self.handle_building(entity, parser_state, BuildingClass::Teleporter);
421
422 let building = self
423 .state
424 .get_or_create_building(entity.entity_index, BuildingClass::Teleporter);
425
426 if let Building::Teleporter(teleporter) = building {
427 for prop in entity.props(parser_state) {
428 match prop.identifier {
429 RECHARGE_TIME => {
430 teleporter.recharge_time = f32::try_from(&prop.value).unwrap_or_default()
431 }
432 RECHARGE_DURATION => {
433 teleporter.recharge_duration =
434 f32::try_from(&prop.value).unwrap_or_default()
435 }
436 TIMES_USED => {
437 teleporter.times_used =
438 i64::try_from(&prop.value).unwrap_or_default() as u16
439 }
440 OTHER_END => {
441 teleporter.other_end =
442 EntityId::from(i64::try_from(&prop.value).unwrap_or_default() as u32)
443 }
444 YAW_TO_EXIT => {
445 teleporter.yaw_to_exit = f32::try_from(&prop.value).unwrap_or_default()
446 }
447 IS_ENTRANCE => {
448 teleporter.is_entrance = i64::try_from(&prop.value).unwrap_or_default() == 0
449 }
450 _ => {}
451 }
452 }
453 }
454 }
455
456 pub fn handle_dispenser_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
457 const AMMO: SendPropIdentifier =
458 SendPropIdentifier::new("DT_ObjectDispenser", "m_iAmmoMetal");
459 const HEALING: SendPropIdentifier =
460 SendPropIdentifier::new("DT_ObjectDispenser", "healing_array");
461
462 if entity.update_type == UpdateType::Delete {
463 self.state.remove_building(entity.entity_index);
464 return;
465 }
466
467 self.handle_building(entity, parser_state, BuildingClass::Dispenser);
468
469 let building = self
470 .state
471 .get_or_create_building(entity.entity_index, BuildingClass::Dispenser);
472
473 if let Building::Dispenser(dispenser) = building {
474 for prop in entity.props(parser_state) {
475 match prop.identifier {
476 AMMO => dispenser.metal = i64::try_from(&prop.value).unwrap_or_default() as u16,
477 HEALING => {
478 let values = match &prop.value {
479 SendPropValue::Array(vec) => vec.as_slice(),
480 _ => Default::default(),
481 };
482
483 dispenser.healing = values
484 .iter()
485 .map(|val| UserId::from(i64::try_from(val).unwrap_or_default() as u16))
486 .collect()
487 }
488 _ => {}
489 }
490 }
491 }
492 }
493
494 fn handle_building(
495 &mut self,
496 entity: &PacketEntity,
497 parser_state: &ParserState,
498 class: BuildingClass,
499 ) {
500 let building = self
501 .state
502 .get_or_create_building(entity.entity_index, class);
503
504 const LOCAL_ORIGIN: SendPropIdentifier =
505 SendPropIdentifier::new("DT_BaseEntity", "m_vecOrigin");
506 const TEAM: SendPropIdentifier = SendPropIdentifier::new("DT_BaseEntity", "m_iTeamNum");
507 const ANGLE: SendPropIdentifier = SendPropIdentifier::new("DT_BaseEntity", "m_angRotation");
508 const SAPPED: SendPropIdentifier = SendPropIdentifier::new("DT_BaseObject", "m_bHasSapper");
509 const BUILDING: SendPropIdentifier =
510 SendPropIdentifier::new("DT_BaseObject", "m_bBuilding");
511 const LEVEL: SendPropIdentifier =
512 SendPropIdentifier::new("DT_BaseObject", "m_iUpgradeLevel");
513 const BUILDER: SendPropIdentifier = SendPropIdentifier::new("DT_BaseObject", "m_hBuilder");
514 const MAX_HEALTH: SendPropIdentifier =
515 SendPropIdentifier::new("DT_BaseObject", "m_iMaxHealth");
516 const HEALTH: SendPropIdentifier = SendPropIdentifier::new("DT_BaseObject", "m_iHealth");
517
518 match building {
519 Building::Sentry(Sentry {
520 position,
521 team,
522 angle,
523 sapped,
524 builder,
525 level,
526 building,
527 max_health,
528 health,
529 ..
530 })
531 | Building::Dispenser(Dispenser {
532 position,
533 team,
534 angle,
535 sapped,
536 builder,
537 level,
538 building,
539 max_health,
540 health,
541 ..
542 })
543 | Building::Teleporter(Teleporter {
544 position,
545 team,
546 angle,
547 sapped,
548 builder,
549 level,
550 building,
551 max_health,
552 health,
553 ..
554 }) => {
555 for prop in entity.props(parser_state) {
556 match prop.identifier {
557 LOCAL_ORIGIN => {
558 *position = Vector::try_from(&prop.value).unwrap_or_default()
559 }
560 TEAM => *team = Team::new(i64::try_from(&prop.value).unwrap_or_default()),
561 ANGLE => *angle = f32::try_from(&prop.value).unwrap_or_default(),
562 SAPPED => *sapped = i64::try_from(&prop.value).unwrap_or_default() > 0,
563 BUILDING => *building = i64::try_from(&prop.value).unwrap_or_default() > 0,
564 LEVEL => *level = i64::try_from(&prop.value).unwrap_or_default() as u8,
565 BUILDER => {
566 *builder =
567 UserId::from(i64::try_from(&prop.value).unwrap_or_default() as u16)
568 }
569 MAX_HEALTH => {
570 *max_health = i64::try_from(&prop.value).unwrap_or_default() as u16
571 }
572 HEALTH => *health = i64::try_from(&prop.value).unwrap_or_default() as u16,
573 _ => {}
574 }
575 }
576 }
577 }
578 }
579
580 pub fn handle_projectile_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
581 let Some(class_name) = self.class_names.get(usize::from(entity.server_class)) else {
582 return;
583 };
584
585 const ROCKET_ORIGIN: SendPropIdentifier =
586 SendPropIdentifier::new("DT_TFBaseRocket", "m_vecOrigin"); const GRENADE_ORIGIN: SendPropIdentifier =
588 SendPropIdentifier::new("DT_TFWeaponBaseGrenadeProj", "m_vecOrigin");
589 const TEAM: SendPropIdentifier = SendPropIdentifier::new("DT_BaseEntity", "m_iTeamNum");
591 const INITIAL_SPEED: SendPropIdentifier =
592 SendPropIdentifier::new("DT_TFBaseRocket", "m_vInitialVelocity");
593 const LAUNCHER: SendPropIdentifier =
594 SendPropIdentifier::new("DT_BaseProjectile", "m_hOriginalLauncher");
595 const PIPE_TYPE: SendPropIdentifier =
596 SendPropIdentifier::new("DT_TFProjectile_Pipebomb", "m_iType");
597 const ROCKET_ROTATION: SendPropIdentifier =
598 SendPropIdentifier::new("DT_TFBaseRocket", "m_angRotation");
599 const GRENADE_ROTATION: SendPropIdentifier =
600 SendPropIdentifier::new("DT_TFWeaponBaseGrenadeProj", "m_angRotation");
601 const CRITICAL_GRENADE: SendPropIdentifier =
602 SendPropIdentifier::new("DT_TFWeaponBaseGrenadeProj", "m_bCritical");
603 const CRITICAL_ROCKET: SendPropIdentifier =
604 SendPropIdentifier::new("DT_TFProjectile_Rocket", "m_bCritical");
605 const CRITICAL_FLARE: SendPropIdentifier =
606 SendPropIdentifier::new("DT_TFProjectile_Flare", "m_bCritical");
607 const CRITICAL_ARROW: SendPropIdentifier =
608 SendPropIdentifier::new("DT_TFProjectile_Arrow", "m_bCritical");
609
610 if entity.update_type == UpdateType::Delete {
611 self.state.projectile_destroy(entity.entity_index);
612 return;
613 }
614
615 let projectile = self
616 .state
617 .projectiles
618 .entry(entity.entity_index)
619 .or_insert_with(|| {
620 Projectile::new(entity.entity_index, entity.server_class, class_name)
621 });
622
623 for prop in entity.props(parser_state) {
626 match prop.identifier {
627 ROCKET_ORIGIN | GRENADE_ORIGIN => {
628 let pos = Vector::try_from(&prop.value).unwrap_or_default();
629 projectile.position = pos
630 }
631 TEAM => {
632 let team = Team::new(i64::try_from(&prop.value).unwrap_or_default());
633 projectile.team = team;
634 }
635 INITIAL_SPEED => {
636 let speed = Vector::try_from(&prop.value).unwrap_or_default();
637 projectile.initial_speed = speed;
638 }
639 LAUNCHER => {
640 let launcher = Handle(i64::try_from(&prop.value).unwrap_or_default());
641 projectile.launcher = launcher;
642 }
643 PIPE_TYPE => {
644 let pipe_type = PipeType::new(i64::try_from(&prop.value).unwrap_or_default());
645 if let Some(class_name) = self.class_names.get(usize::from(entity.server_class))
646 {
647 let ty = ProjectileType::new(class_name, Some(pipe_type));
648 projectile.ty = ty;
649 }
650 }
651 ROCKET_ROTATION | GRENADE_ROTATION => {
652 let rotation = Vector::try_from(&prop.value).unwrap_or_default();
653 projectile.rotation = rotation;
654 }
655 CRITICAL_GRENADE | CRITICAL_ROCKET | CRITICAL_FLARE | CRITICAL_ARROW => {
656 let critical = bool::try_from(&prop.value).unwrap_or_default();
657 projectile.critical = critical;
658 }
659 _ => {}
660 }
661 }
662 }
663
664 pub fn handle_train_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
665 const POSITION: SendPropIdentifier =
666 SendPropIdentifier::new("DT_BaseEntity", "m_vecOrigin");
667
668 let objective = self
669 .state
670 .objectives
671 .entry(entity.entity_index)
672 .or_insert_with(|| {
673 Objective::Cart(Cart::default())
674 });
675
676 #[allow(irrefutable_let_patterns)]
677 if let Objective::Cart(cart) = objective {
678 for prop in entity.props(parser_state) {
679 if prop.identifier == POSITION {
680 let pos = Vector::try_from(&prop.value).unwrap_or_default();
681 cart.position = pos
682 }
683 }
684 }
685 }
686
687 #[allow(dead_code, unused_variables)]
688 pub fn handle_cp_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
689 const OWNERS: [SendPropIdentifier; 5] = [
690 SendPropIdentifier::new("m_iOwner", "000"),
691 SendPropIdentifier::new("m_iOwner", "001"),
692 SendPropIdentifier::new("m_iOwner", "002"),
693 SendPropIdentifier::new("m_iOwner", "003"),
694 SendPropIdentifier::new("m_iOwner", "004"),
695 ];
696 const CAP_PERCENTAGE: [SendPropIdentifier; 5] = [
697 SendPropIdentifier::new("m_flLazyCapPerc", "000"),
698 SendPropIdentifier::new("m_flLazyCapPerc", "001"),
699 SendPropIdentifier::new("m_flLazyCapPerc", "002"),
700 SendPropIdentifier::new("m_flLazyCapPerc", "003"),
701 SendPropIdentifier::new("m_flLazyCapPerc", "004"),
702 ];
703
704 let objective = self
705 .state
706 .objectives
707 .entry(entity.entity_index)
708 .or_insert_with(|| {
709 Objective::Cart(Cart::default())
710 });
711
712 todo!()
713 }
714
715 fn parse_user_info(
716 &mut self,
717 index: usize,
718 text: Option<&str>,
719 data: Option<Stream>,
720 ) -> ReadResult<()> {
721 if let Some(user_info) =
722 crate::demo::data::UserInfo::parse_from_string_table(index as u16, text, data)?
723 {
724 let id = user_info.entity_id;
725 self.state.get_or_create_player(id).info = Some(user_info.into());
726 }
727
728 Ok(())
729 }
730}