Skip to content
This repository was archived by the owner on May 21, 2025. It is now read-only.

Commit e0ee7c8

Browse files
committed
more projectile tracking work
1 parent a9c0313 commit e0ee7c8

File tree

3 files changed

+207
-45
lines changed

3 files changed

+207
-45
lines changed

src/bin/direct_hits.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,29 @@ fn main() -> Result<(), MainError> {
4949
.get(usize::from(collision.projectile.class))
5050
.map(|class| class.name.as_str())
5151
.unwrap_or("unknown weapon");
52-
println!(
53-
"{}: {} hit by {}",
54-
collision.tick, player.name, weapon_class
55-
);
52+
53+
let shooter = state
54+
.players
55+
.iter()
56+
.find(|player| {
57+
player
58+
.weapons
59+
.iter()
60+
.any(|weapon| collision.projectile.launcher == *weapon)
61+
})
62+
.and_then(|player| player.info.as_ref());
63+
64+
if let Some(shooter) = shooter {
65+
println!(
66+
"{}: {} hit by {} from {}",
67+
collision.tick, player.name, weapon_class, shooter.name
68+
);
69+
} else {
70+
println!(
71+
"{}: {} hit by {} from unknown player {}",
72+
collision.tick, player.name, weapon_class, collision.projectile.launcher
73+
);
74+
}
5675
}
5776
}
5877

src/demo/data/game_state.rs

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
use crate::demo::data::DemoTick;
22
use crate::demo::gameevent_gen::PlayerDeathEvent;
33
use crate::demo::message::packetentities::EntityId;
4-
use crate::demo::packet::datatable::{ClassId, ServerClass};
4+
use crate::demo::packet::datatable::{ClassId, ServerClass, ServerClassName};
55
use crate::demo::parser::analyser::{Class, Team, UserId, UserInfo};
66
use crate::demo::vector::Vector;
7+
use parse_display::Display;
78
use serde::{Deserialize, Serialize};
8-
use std::collections::BTreeMap;
9+
use std::collections::{BTreeMap, HashMap};
10+
11+
#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash, Display)]
12+
pub struct Handle(pub i64);
913

1014
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Default)]
1115
pub enum PlayerState {
@@ -65,6 +69,7 @@ pub struct Player {
6569
pub ping: u16,
6670
pub in_pvs: bool,
6771
pub bounds: Box,
72+
pub weapons: [Handle; 3],
6873
}
6974

7075
pub const PLAYER_BOX_DEFAULT: Box = Box {
@@ -91,7 +96,7 @@ impl Player {
9196

9297
pub fn collides(&self, projectile: &Projectile, time_per_tick: f32) -> bool {
9398
let current_position = projectile.position;
94-
let next_position = projectile.position + (projectile.speed * time_per_tick);
99+
let next_position = projectile.position + (projectile.initial_speed * time_per_tick);
95100
match projectile.bounds {
96101
Some(_) => todo!(),
97102
None => {
@@ -275,19 +280,96 @@ pub struct Projectile {
275280
pub team: Team,
276281
pub class: ClassId,
277282
pub position: Vector,
278-
pub speed: Vector,
283+
pub rotation: Vector,
284+
pub initial_speed: Vector,
279285
pub bounds: Option<Box>,
286+
pub launcher: Handle,
287+
pub ty: ProjectileType,
280288
}
281289

282290
impl Projectile {
283-
pub fn new(id: EntityId, class: ClassId) -> Self {
291+
pub fn new(id: EntityId, class: ClassId, class_name: &ServerClassName) -> Self {
284292
Projectile {
285293
id,
286294
team: Team::default(),
287295
class,
288296
position: Vector::default(),
289-
speed: Vector::default(),
297+
rotation: Vector::default(),
298+
initial_speed: Vector::default(),
290299
bounds: None,
300+
launcher: Handle::default(),
301+
ty: ProjectileType::new(class_name, None),
302+
}
303+
}
304+
}
305+
306+
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
307+
pub enum PipeType {
308+
Regular = 0,
309+
Sticky = 1,
310+
StickyJumper = 2,
311+
LooseCannon = 3,
312+
}
313+
314+
impl PipeType {
315+
pub fn new(number: i64) -> Self {
316+
match number {
317+
1 => PipeType::Sticky,
318+
2 => PipeType::StickyJumper,
319+
3 => PipeType::LooseCannon,
320+
_ => PipeType::Regular,
321+
}
322+
}
323+
324+
pub fn is_sticky(&self) -> bool {
325+
match self {
326+
PipeType::Regular | PipeType::LooseCannon => false,
327+
PipeType::Sticky | PipeType::StickyJumper => true,
328+
}
329+
}
330+
}
331+
332+
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
333+
#[repr(u8)]
334+
pub enum ProjectileType {
335+
Rocket = 0,
336+
HealingArrow = 1,
337+
Sticky = 2,
338+
Pipe = 3,
339+
Flare = 4,
340+
LooseCannon = 5,
341+
#[default]
342+
Unknown = 7,
343+
}
344+
345+
impl ProjectileType {
346+
pub fn new(class: &ServerClassName, pipe_type: Option<PipeType>) -> Self {
347+
match (class.as_str(), pipe_type) {
348+
("CTFGrenadePipebombProjectile", Some(PipeType::Sticky | PipeType::StickyJumper)) => {
349+
ProjectileType::Sticky
350+
}
351+
("CTFGrenadePipebombProjectile", Some(PipeType::LooseCannon)) => {
352+
ProjectileType::LooseCannon
353+
}
354+
("CTFGrenadePipebombProjectile", _) => ProjectileType::Pipe,
355+
("CTFProjectile_SentryRocket" | "CTFProjectile_Rocket", _) => ProjectileType::Rocket,
356+
("CTFProjectile_Flare", _) => ProjectileType::Flare,
357+
("CTFProjectile_HealingBolt", _) => ProjectileType::HealingArrow,
358+
_ => ProjectileType::Unknown,
359+
}
360+
}
361+
}
362+
363+
impl From<u8> for ProjectileType {
364+
fn from(value: u8) -> Self {
365+
match value {
366+
0 => ProjectileType::Rocket,
367+
1 => ProjectileType::HealingArrow,
368+
2 => ProjectileType::Sticky,
369+
3 => ProjectileType::Pipe,
370+
4 => ProjectileType::Flare,
371+
5 => ProjectileType::LooseCannon,
372+
_ => ProjectileType::Unknown,
291373
}
292374
}
293375
}
@@ -337,6 +419,7 @@ pub struct GameState {
337419
pub tick: DemoTick,
338420
pub server_classes: Vec<ServerClass>,
339421
pub interval_per_tick: f32,
422+
pub outer_map: HashMap<Handle, EntityId>,
340423
}
341424

342425
impl GameState {
@@ -373,12 +456,6 @@ impl GameState {
373456
.or_insert_with(|| Building::new(entity_id, class))
374457
}
375458

376-
pub fn get_or_create_projectile(&mut self, id: EntityId, class: ClassId) -> &mut Projectile {
377-
self.projectiles
378-
.entry(id)
379-
.or_insert_with(|| Projectile::new(id, class))
380-
}
381-
382459
pub fn check_collision(&self, projectile: &Projectile) -> Option<&Player> {
383460
self.players
384461
.iter()

src/demo/parser/gamestateanalyser.rs

Lines changed: 95 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub use crate::demo::data::game_state::{
22
Building, BuildingClass, Dispenser, GameState, Kill, PlayerState, Sentry, Teleporter, World,
33
};
4+
use crate::demo::data::game_state::{Handle, PipeType, Projectile, ProjectileType};
45
use crate::demo::data::DemoTick;
56
use crate::demo::gameevent_gen::ObjectDestroyedEvent;
67
use crate::demo::gamevent::GameEvent;
@@ -44,6 +45,10 @@ impl MessageHandler for GameStateAnalyser {
4445
for entity in &message.entities {
4546
self.handle_entity(entity, parser_state);
4647
}
48+
for id in &message.removed_entities {
49+
self.state.projectile_destroy(*id);
50+
self.state.remove_building(*id);
51+
}
4752
}
4853
Message::ServerInfo(message) => {
4954
self.state.interval_per_tick = message.interval_per_tick
@@ -124,19 +129,32 @@ impl GameStateAnalyser {
124129
}
125130

126131
pub fn handle_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
127-
let class_name: &str = self
128-
.class_names
129-
.get(usize::from(entity.server_class))
130-
.map(|class_name| class_name.as_str())
131-
.unwrap_or("");
132-
match class_name {
132+
const OUTER: SendPropIdentifier =
133+
SendPropIdentifier::new("DT_AttributeContainer", "m_hOuter");
134+
135+
let Some(class_name) = self.class_names.get(usize::from(entity.server_class)) else {
136+
return;
137+
};
138+
139+
for prop in &entity.props {
140+
if prop.identifier == OUTER {
141+
let outer = i64::try_from(&prop.value).unwrap_or_default();
142+
self.state
143+
.outer_map
144+
.insert(Handle(outer), entity.entity_index);
145+
}
146+
}
147+
148+
match class_name.as_str() {
133149
"CTFPlayer" => self.handle_player_entity(entity, parser_state),
134150
"CTFPlayerResource" => self.handle_player_resource(entity, parser_state),
135151
"CWorld" => self.handle_world_entity(entity, parser_state),
136152
"CObjectSentrygun" => self.handle_sentry_entity(entity, parser_state),
137153
"CObjectDispenser" => self.handle_dispenser_entity(entity, parser_state),
138154
"CObjectTeleporter" => self.handle_teleporter_entity(entity, parser_state),
139-
_ if class_name.starts_with("CTFProjectile_") => {
155+
_ if class_name.starts_with("CTFProjectile_")
156+
|| class_name.as_str() == "CTFGrenadePipebombProjectile" =>
157+
{
140158
self.handle_projectile_entity(entity, parser_state)
141159
}
142160
_ => {}
@@ -213,6 +231,10 @@ impl GameStateAnalyser {
213231
const PROP_BB_MAX: SendPropIdentifier =
214232
SendPropIdentifier::new("DT_CollisionProperty", "m_vecMaxsPreScaled");
215233

234+
const WEAPON_0: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "000");
235+
const WEAPON_1: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "001");
236+
const WEAPON_2: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "002");
237+
216238
player.in_pvs = entity.in_pvs;
217239

218240
for prop in entity.props(parser_state) {
@@ -247,6 +269,18 @@ impl GameStateAnalyser {
247269
let max = Vector::try_from(&prop.value).unwrap_or_default();
248270
player.bounds.max = max;
249271
}
272+
WEAPON_0 => {
273+
let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
274+
player.weapons[0] = handle;
275+
}
276+
WEAPON_1 => {
277+
let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
278+
player.weapons[1] = handle;
279+
}
280+
WEAPON_2 => {
281+
let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
282+
player.weapons[2] = handle;
283+
}
250284
_ => {}
251285
}
252286
}
@@ -501,6 +535,10 @@ impl GameStateAnalyser {
501535
}
502536

503537
pub fn handle_projectile_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
538+
let Some(class_name) = self.class_names.get(usize::from(entity.server_class)) else {
539+
return;
540+
};
541+
504542
const ROCKET_ORIGIN: SendPropIdentifier =
505543
SendPropIdentifier::new("DT_TFBaseRocket", "m_vecOrigin"); // rockets, arrows, more?
506544
const GRENADE_ORIGIN: SendPropIdentifier =
@@ -509,34 +547,62 @@ impl GameStateAnalyser {
509547
const TEAM: SendPropIdentifier = SendPropIdentifier::new("DT_BaseEntity", "m_iTeamNum");
510548
const INITIAL_SPEED: SendPropIdentifier =
511549
SendPropIdentifier::new("DT_TFBaseRocket", "m_vInitialVelocity");
550+
const LAUNCHER: SendPropIdentifier =
551+
SendPropIdentifier::new("DT_BaseProjectile", "m_hOriginalLauncher");
552+
const PIPE_TYPE: SendPropIdentifier =
553+
SendPropIdentifier::new("DT_TFProjectile_Pipebomb", "m_iType");
554+
const ROCKET_ROTATION: SendPropIdentifier =
555+
SendPropIdentifier::new("DT_TFBaseRocket", "m_angRotation");
556+
const GRENADE_ROTATION: SendPropIdentifier =
557+
SendPropIdentifier::new("DT_TFWeaponBaseGrenadeProj", "m_angRotation");
512558

513-
if entity.in_pvs {
514-
let projectile = self
515-
.state
516-
.get_or_create_projectile(entity.entity_index, entity.server_class);
559+
if entity.update_type == UpdateType::Delete {
560+
self.state.projectile_destroy(entity.entity_index);
561+
return;
562+
}
517563

518-
// todo: bounds for grenades
519-
// todo: track owner
564+
let projectile = self
565+
.state
566+
.projectiles
567+
.entry(entity.entity_index)
568+
.or_insert_with(|| {
569+
Projectile::new(entity.entity_index, entity.server_class, class_name)
570+
});
520571

521-
for prop in entity.props(parser_state) {
522-
match prop.identifier {
523-
ROCKET_ORIGIN | GRENADE_ORIGIN => {
524-
let pos = Vector::try_from(&prop.value).unwrap_or_default();
525-
projectile.position = pos
526-
}
527-
TEAM => {
528-
let team = Team::new(i64::try_from(&prop.value).unwrap_or_default());
529-
projectile.team = team;
530-
}
531-
INITIAL_SPEED => {
532-
let speed = Vector::try_from(&prop.value).unwrap_or_default();
533-
projectile.speed = speed;
572+
// todo: bounds for grenades
573+
574+
for prop in entity.props(parser_state) {
575+
match prop.identifier {
576+
ROCKET_ORIGIN | GRENADE_ORIGIN => {
577+
let pos = Vector::try_from(&prop.value).unwrap_or_default();
578+
projectile.position = pos
579+
}
580+
TEAM => {
581+
let team = Team::new(i64::try_from(&prop.value).unwrap_or_default());
582+
projectile.team = team;
583+
}
584+
INITIAL_SPEED => {
585+
let speed = Vector::try_from(&prop.value).unwrap_or_default();
586+
projectile.initial_speed = speed;
587+
}
588+
LAUNCHER => {
589+
let launcher = Handle(i64::try_from(&prop.value).unwrap_or_default());
590+
projectile.launcher = launcher;
591+
}
592+
PIPE_TYPE => {
593+
let pipe_type = PipeType::new(i64::try_from(&prop.value).unwrap_or_default());
594+
if let Some(class_name) = self.class_names.get(usize::from(entity.server_class))
595+
{
596+
let ty = ProjectileType::new(class_name, Some(pipe_type));
597+
projectile.ty = ty;
534598
}
535-
_ => {}
536599
}
600+
ROCKET_ROTATION | GRENADE_ROTATION => {
601+
let rotation = Vector::try_from(&prop.value).unwrap_or_default();
602+
projectile.rotation = rotation;
603+
}
604+
_ => {}
537605
}
538-
} else {
539-
self.state.projectile_destroy(entity.entity_index);
540606
}
541607
}
542608

0 commit comments

Comments
 (0)