11use num_enum:: TryFromPrimitive ;
22use serde:: { Deserialize , Serialize } ;
3- use std:: collections:: { BTreeMap , HashMap } ;
3+ use std:: { collections:: { BTreeMap , HashMap } , num :: NonZeroU32 } ;
44use std:: convert:: TryFrom ;
55use std:: str:: FromStr ;
6- use tf_demo_parser:: demo:: gameevent_gen:: { GameEvent , PlayerDeathEvent , PlayerSpawnEvent , TeamPlayRoundWinEvent } ;
6+ use tf_demo_parser:: demo:: { gameevent_gen:: { GameEvent , PlayerDeathEvent , PlayerSpawnEvent , TeamPlayRoundWinEvent } , message :: packetentities :: PVS } ;
77use tf_demo_parser:: demo:: message:: packetentities:: { EntityId , PacketEntity } ;
88use tf_demo_parser:: demo:: message:: usermessage:: { ChatMessageKind , SayText2Message , UserMessage } ;
99use tf_demo_parser:: demo:: message:: { Message , MessageType } ;
@@ -158,6 +158,7 @@ pub struct Death {
158158 pub tick : u32 ,
159159 pub round : u32 ,
160160 pub during_round : bool ,
161+ pub sentry_position : Option < Vector >
161162}
162163
163164impl Death {
@@ -193,6 +194,7 @@ impl Death {
193194 victim_steamid : users. get ( & victim) . expect ( "Can't get victim" ) . steam_id . clone ( ) ,
194195 victim_entity : event. victim_ent_index ,
195196 victim_entity_state : None ,
197+ sentry_position : None
196198 }
197199 }
198200}
@@ -259,6 +261,12 @@ pub struct PlayerEntity {
259261 pub state : PlayerState ,
260262}
261263
264+ #[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
265+ pub enum OtherEntity {
266+ Sentry { position : Option < Vector > } ,
267+ SentryRocket { sentry : Option < EntityId > }
268+ }
269+
262270impl MessageHandler for HeatmapAnalyser {
263271 type Output = HeatmapAnalysis ;
264272
@@ -280,7 +288,15 @@ impl MessageHandler for HeatmapAnalyser {
280288 Message :: UserMessage ( message) => self . handle_user_message ( & message, tick) ,
281289 Message :: PacketEntities ( message) => {
282290 for entity in & message. entities {
283- self . handle_entity ( entity) ;
291+ if entity. pvs == PVS :: Delete {
292+ let removed_entity = entity. entity_index ;
293+ let _removed = self . state . other_entities . remove ( & removed_entity) ;
294+ } else {
295+ self . handle_entity ( entity) ;
296+ }
297+ }
298+ for removed_entity in & message. removed_entities {
299+ let _removed = self . state . other_entities . remove ( removed_entity) ;
284300 }
285301 }
286302 _ => unreachable ! ( ) ,
@@ -318,6 +334,8 @@ impl HeatmapAnalyser {
318334 "CTFPlayer" => self . handle_player_entity ( entity) ,
319335 "CTFPlayerResource" => self . handle_player_resource ( entity) ,
320336 "CWorld" => self . handle_world_entity ( entity) ,
337+ "CObjectSentrygun" => self . handle_sentry_entity ( entity) ,
338+ "CTFProjectile_SentryRocket" => self . handle_sentry_rocket_entity ( entity) ,
321339 _ => { }
322340 }
323341 }
@@ -384,6 +402,46 @@ impl HeatmapAnalyser {
384402 }
385403 }
386404
405+ fn handle_sentry_entity ( & mut self , entity : & PacketEntity ) {
406+ let entry = self . state . other_entities . entry ( entity. entity_index ) . or_insert_with ( || OtherEntity :: Sentry {
407+ position : None
408+ } ) ;
409+ let mut position = if let OtherEntity :: Sentry { position } = * entry {
410+ position
411+ } else {
412+ None
413+ } ;
414+ for prop in & entity. props {
415+ match prop. definition . name . as_str ( ) {
416+ "m_vecOrigin" => position = Some ( Vector :: try_from ( & prop. value ) . unwrap_or_default ( ) ) ,
417+ _ => { }
418+ }
419+ }
420+ * entry = OtherEntity :: Sentry { position } ;
421+ }
422+
423+ fn handle_sentry_rocket_entity ( & mut self , entity : & PacketEntity ) {
424+ let entry = self . state . other_entities . entry ( entity. entity_index ) . or_insert_with ( || OtherEntity :: SentryRocket {
425+ sentry : None
426+ } ) ;
427+ let mut sentry = if let OtherEntity :: SentryRocket { sentry } = * entry {
428+ sentry
429+ } else {
430+ None
431+ } ;
432+ for prop in & entity. props {
433+ match prop. definition . name . as_str ( ) {
434+ "m_hOwnerEntity" => {
435+ let handle = i64:: try_from ( & prop. value ) . unwrap_or_default ( ) ;
436+ let entity_id = handle_to_entity_index ( handle) ;
437+ sentry = entity_id. map ( |id| id. get ( ) . into ( ) ) ;
438+ }
439+ _ => { }
440+ }
441+ }
442+ * entry = OtherEntity :: SentryRocket { sentry } ;
443+ }
444+
387445 fn handle_user_message ( & mut self , message : & UserMessage , tick : u32 ) {
388446 if let UserMessage :: SayText2 ( text_message) = message {
389447 if text_message. kind == ChatMessageKind :: NameChange {
@@ -423,6 +481,30 @@ impl HeatmapAnalyser {
423481 if let Some ( victim_entity) = victim. entity_id {
424482 death. victim_entity_state = Some ( self . state . get_or_create_player_entity ( victim_entity) . clone ( ) ) ;
425483 }
484+ match death. weapon . as_str ( ) {
485+ "obj_sentrygun" | "obj_sentrygun2" | "obj_sentrygun3" | "obj_minisentry" => {
486+ if let Some ( entity) = self . state . other_entities . get ( & death. killer_entity . into ( ) ) {
487+ match entity {
488+ OtherEntity :: Sentry { position } => {
489+ death. sentry_position = * position;
490+ }
491+ OtherEntity :: SentryRocket { sentry } => {
492+ if let Some ( sentry_entity) = sentry {
493+ if let Some ( entity) = self . state . other_entities . get ( sentry_entity) {
494+ match entity {
495+ OtherEntity :: Sentry { position } => {
496+ death. sentry_position = * position;
497+ }
498+ _ => { }
499+ }
500+ }
501+ }
502+ }
503+ }
504+ }
505+ }
506+ _ => { }
507+ }
426508 self . state . deaths . push ( death) ;
427509 }
428510 GameEvent :: PlayerSpawn ( event) => {
@@ -491,6 +573,7 @@ pub struct HeatmapAnalysis {
491573 pub in_round : bool ,
492574
493575 pub player_entities : Vec < PlayerEntity > ,
576+ pub other_entities : HashMap < EntityId , OtherEntity > ,
494577 pub world : Option < World > ,
495578}
496579
@@ -532,6 +615,7 @@ impl Default for HeatmapAnalysis {
532615 player_entities. push ( world) ;
533616 player_entities
534617 } ,
618+ other_entities : Default :: default ( ) ,
535619 world : Default :: default ( ) ,
536620 }
537621 }
@@ -568,3 +652,11 @@ impl HeatmapAnalysis {
568652 & mut self . player_entities [ index]
569653 }
570654}
655+
656+ pub fn handle_to_entity_index ( handle : i64 ) -> Option < NonZeroU32 > {
657+ let ret = handle as u32 & 0b111_1111_1111 ; // The rest of the bits is probably some kind of generational index
658+ if ret == 2047 {
659+ return None
660+ }
661+ NonZeroU32 :: new ( ret)
662+ }
0 commit comments