Skip to content

Commit 7afd829

Browse files
committed
Sentry positions
1 parent 31f5bc3 commit 7afd829

6 files changed

Lines changed: 150 additions & 32 deletions

File tree

src/demo_player.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::{
1212
path::PathBuf,
1313
};
1414

15-
use coldmaps::heatmap_analyser::{Class, HeatmapAnalyser, HeatmapAnalysis, PlayerState, Spawn, Team, UserId, UserInfo};
15+
use coldmaps::heatmap_analyser::{Class, HeatmapAnalyser, HeatmapAnalysis, PlayerState, Spawn, Team, UserId, UserInfo, handle_to_entity_index};
1616
use serde::Serialize;
1717
use tf_demo_parser::{
1818
demo::gamevent::GameEvent, demo::header::Header, demo::message::packetentities::EntityId, demo::message::packetentities::PacketEntity, demo::message::packetentities::PVS,
@@ -893,12 +893,4 @@ impl DemoAnalyzer {
893893
}
894894
Ok(())
895895
}
896-
}
897-
898-
fn handle_to_entity_index(handle: i64) -> Option<NonZeroU32> {
899-
let ret = handle as u32 & 0b111_1111_1111; // The rest of the bits is probably some kind of generational index
900-
if ret == 2047 {
901-
return None
902-
}
903-
NonZeroU32::new(ret)
904896
}

src/filters.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,12 @@ impl Display for PropertyOperator {
7272
pub enum Property {
7373
Suicide,
7474
Posthumous,
75-
DuringRound
75+
DuringRound,
76+
DiedToSentry,
7677
}
7778

7879
impl Property {
79-
pub const ALL: [Property; 3] = [Property::Suicide, Property::Posthumous, Property::DuringRound];
80+
pub const ALL: [Property; 4] = [Property::Suicide, Property::Posthumous, Property::DuringRound, Property::DiedToSentry];
8081
}
8182

8283
impl Default for Property {
@@ -91,6 +92,7 @@ impl Display for Property {
9192
Property::Suicide => write!(f, "Suicide"),
9293
Property::Posthumous => write!(f, "Posthumous"),
9394
Property::DuringRound => write!(f, "During round"),
95+
Property::DiedToSentry => write!(f, "Died to sentry"),
9496
}
9597
}
9698
}
@@ -297,7 +299,8 @@ impl FilterTrait for PropertyFilter {
297299
Some(PlayerEntity { state: PlayerState::Alive, .. }) => false,
298300
_ => true,
299301
},
300-
Property::DuringRound => death.during_round
302+
Property::DuringRound => death.during_round,
303+
Property::DiedToSentry => death.sentry_position.is_some(),
301304
};
302305
match self.op {
303306
PropertyOperator::IsPresent => ret,

src/heatmap.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ struct HeatMapParameters {
5858
top_y: f32,
5959
bottom_y: f32,
6060
radius: f32,
61-
intensity: Option<f32>
61+
intensity: Option<f32>,
62+
use_sentry_position: bool,
6263
}
6364

6465
#[derive(Debug)]
@@ -67,7 +68,7 @@ pub struct HeatMapGenerator {
6768
}
6869

6970
impl HeatMapGenerator {
70-
pub fn new(pos_x: f32, pos_y: f32, screen_width: u32, screen_height: u32, scale: f32, coords_type: CoordsType, radius: f32, intensity: Option<f32>) -> Self {
71+
pub fn new(pos_x: f32, pos_y: f32, screen_width: u32, screen_height: u32, scale: f32, coords_type: CoordsType, radius: f32, intensity: Option<f32>, use_sentry_position: bool) -> Self {
7172
let screen_width = screen_width as f32;
7273
let screen_height = screen_height as f32;
7374
let aspect_ratio = screen_width / screen_height;
@@ -81,7 +82,8 @@ impl HeatMapGenerator {
8182
top_y: pos_y + scale * LEVELOVERVIEW_SCALE_MULTIPLIER,
8283
bottom_y: pos_y - scale * LEVELOVERVIEW_SCALE_MULTIPLIER,
8384
radius,
84-
intensity
85+
intensity,
86+
use_sentry_position,
8587
},
8688
},
8789
CoordsType::Console => Self {
@@ -93,7 +95,8 @@ impl HeatMapGenerator {
9395
top_y: pos_y,
9496
bottom_y: pos_y - scale * LEVELOVERVIEW_SCALE_MULTIPLIER * 2.0,
9597
radius,
96-
intensity
98+
intensity,
99+
use_sentry_position,
97100
},
98101
},
99102
}
@@ -118,9 +121,19 @@ impl HeatMapGenerator {
118121
// LinSrgba::new(0.0, 0.0, 1.0, 1.0),
119122
]);
120123
for death in deaths {
121-
if let (Some(killer_entity), Some(victim_entity)) = (&death.killer_entity_state, &death.victim_entity_state) {
122-
let killer_coords = self.game_coords_to_screen_coords(killer_entity.position.x, killer_entity.position.y);
123-
let victim_coords = self.game_coords_to_screen_coords(victim_entity.position.x, victim_entity.position.y);
124+
let killer_pos = if self.params.use_sentry_position {
125+
if let Some(sentry_position) = death.sentry_position {
126+
Some(sentry_position)
127+
} else {
128+
death.killer_entity_state.as_ref().map(|entity| entity.position)
129+
}
130+
} else {
131+
death.killer_entity_state.as_ref().map(|entity| entity.position)
132+
};
133+
let victim_pos = death.victim_entity_state.as_ref().map(|entity| entity.position);
134+
if let (Some(killer_pos), Some(victim_pos)) = (killer_pos, victim_pos) {
135+
let killer_coords = self.game_coords_to_screen_coords(killer_pos.x, killer_pos.y);
136+
let victim_coords = self.game_coords_to_screen_coords(victim_pos.x, victim_pos.y);
124137
let points: Vec<((i32, i32), f32)> = line_drawing::XiaolinWu::<f32, i32>::new(killer_coords, victim_coords).collect();
125138

126139
// this is needed because the line drawing algorithm doesn't always go in the start-end order, we need to check what order was used and invert the gradient as needed
@@ -179,13 +192,19 @@ impl HeatMapGenerator {
179192
let radius = self.params.radius / 10.0;
180193
let pixels_iters = (radius * 2.0).ceil() as i32;
181194
for death in deaths {
182-
let entity_state = match heatmap_type {
183-
HeatmapType::VictimPosition => &death.victim_entity_state,
184-
HeatmapType::KillerPosition => &death.killer_entity_state,
185-
HeatmapType::Lines => unreachable!(),
195+
let game_coords = match (heatmap_type, self.params.use_sentry_position) {
196+
(HeatmapType::VictimPosition, _) => death.victim_entity_state.as_ref().map(|entity| entity.position),
197+
(HeatmapType::KillerPosition, false) => death.killer_entity_state.as_ref().map(|entity| entity.position),
198+
(HeatmapType::KillerPosition, true) => {
199+
if let Some(sentry_position) = death.sentry_position {
200+
Some(sentry_position)
201+
} else {
202+
death.killer_entity_state.as_ref().map(|entity| entity.position)
203+
}
204+
}
205+
(HeatmapType::Lines, _) => unreachable!(),
186206
};
187-
if let Some(entity_state) = entity_state {
188-
let game_coords = entity_state.position;
207+
if let Some(game_coords) = game_coords {
189208
let (x_f, y_f) = self.game_coords_to_screen_coords(game_coords.x, game_coords.y);
190209
let x_i = x_f.round() as i32;
191210
let y_i = y_f.round() as i32;

src/heatmap_analyser.rs

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use num_enum::TryFromPrimitive;
22
use serde::{Deserialize, Serialize};
3-
use std::collections::{BTreeMap, HashMap};
3+
use std::{collections::{BTreeMap, HashMap}, num::NonZeroU32};
44
use std::convert::TryFrom;
55
use 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};
77
use tf_demo_parser::demo::message::packetentities::{EntityId, PacketEntity};
88
use tf_demo_parser::demo::message::usermessage::{ChatMessageKind, SayText2Message, UserMessage};
99
use 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

163164
impl 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+
262270
impl 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+
}

src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ pub fn generate_heatmap<'a>(
7373
scale: f32,
7474
coords_type: CoordsType,
7575
radius: f32,
76-
intensity: Option<f32>
76+
intensity: Option<f32>,
77+
use_sentry_position: bool,
7778
) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
78-
let heatmap_generator = heatmap::HeatMapGenerator::new(pos_x, pos_y, screen_width, screen_height, scale, coords_type, radius, intensity);
79+
let heatmap_generator = heatmap::HeatMapGenerator::new(pos_x, pos_y, screen_width, screen_height, scale, coords_type, radius, intensity, use_sentry_position);
7980
heatmap_generator.generate_heatmap(heatmap_type, deaths, &mut image);
8081
image
8182
}

0 commit comments

Comments
 (0)