mirror of
https://codeberg.org/demostf/parser.git
synced 2026-06-03 18:24:05 +02:00
more projectile tracking work
This commit is contained in:
parent
a9c031345f
commit
e0ee7c87d2
3 changed files with 207 additions and 45 deletions
|
|
@ -49,10 +49,29 @@ fn main() -> Result<(), MainError> {
|
||||||
.get(usize::from(collision.projectile.class))
|
.get(usize::from(collision.projectile.class))
|
||||||
.map(|class| class.name.as_str())
|
.map(|class| class.name.as_str())
|
||||||
.unwrap_or("unknown weapon");
|
.unwrap_or("unknown weapon");
|
||||||
|
|
||||||
|
let shooter = state
|
||||||
|
.players
|
||||||
|
.iter()
|
||||||
|
.find(|player| {
|
||||||
|
player
|
||||||
|
.weapons
|
||||||
|
.iter()
|
||||||
|
.any(|weapon| collision.projectile.launcher == *weapon)
|
||||||
|
})
|
||||||
|
.and_then(|player| player.info.as_ref());
|
||||||
|
|
||||||
|
if let Some(shooter) = shooter {
|
||||||
println!(
|
println!(
|
||||||
"{}: {} hit by {}",
|
"{}: {} hit by {} from {}",
|
||||||
collision.tick, player.name, weapon_class
|
collision.tick, player.name, weapon_class, shooter.name
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{}: {} hit by {} from unknown player {}",
|
||||||
|
collision.tick, player.name, weapon_class, collision.projectile.launcher
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
use crate::demo::data::DemoTick;
|
use crate::demo::data::DemoTick;
|
||||||
use crate::demo::gameevent_gen::PlayerDeathEvent;
|
use crate::demo::gameevent_gen::PlayerDeathEvent;
|
||||||
use crate::demo::message::packetentities::EntityId;
|
use crate::demo::message::packetentities::EntityId;
|
||||||
use crate::demo::packet::datatable::{ClassId, ServerClass};
|
use crate::demo::packet::datatable::{ClassId, ServerClass, ServerClassName};
|
||||||
use crate::demo::parser::analyser::{Class, Team, UserId, UserInfo};
|
use crate::demo::parser::analyser::{Class, Team, UserId, UserInfo};
|
||||||
use crate::demo::vector::Vector;
|
use crate::demo::vector::Vector;
|
||||||
|
use parse_display::Display;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash, Display)]
|
||||||
|
pub struct Handle(pub i64);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Default)]
|
||||||
pub enum PlayerState {
|
pub enum PlayerState {
|
||||||
|
|
@ -65,6 +69,7 @@ pub struct Player {
|
||||||
pub ping: u16,
|
pub ping: u16,
|
||||||
pub in_pvs: bool,
|
pub in_pvs: bool,
|
||||||
pub bounds: Box,
|
pub bounds: Box,
|
||||||
|
pub weapons: [Handle; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PLAYER_BOX_DEFAULT: Box = Box {
|
pub const PLAYER_BOX_DEFAULT: Box = Box {
|
||||||
|
|
@ -91,7 +96,7 @@ impl Player {
|
||||||
|
|
||||||
pub fn collides(&self, projectile: &Projectile, time_per_tick: f32) -> bool {
|
pub fn collides(&self, projectile: &Projectile, time_per_tick: f32) -> bool {
|
||||||
let current_position = projectile.position;
|
let current_position = projectile.position;
|
||||||
let next_position = projectile.position + (projectile.speed * time_per_tick);
|
let next_position = projectile.position + (projectile.initial_speed * time_per_tick);
|
||||||
match projectile.bounds {
|
match projectile.bounds {
|
||||||
Some(_) => todo!(),
|
Some(_) => todo!(),
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -275,19 +280,96 @@ pub struct Projectile {
|
||||||
pub team: Team,
|
pub team: Team,
|
||||||
pub class: ClassId,
|
pub class: ClassId,
|
||||||
pub position: Vector,
|
pub position: Vector,
|
||||||
pub speed: Vector,
|
pub rotation: Vector,
|
||||||
|
pub initial_speed: Vector,
|
||||||
pub bounds: Option<Box>,
|
pub bounds: Option<Box>,
|
||||||
|
pub launcher: Handle,
|
||||||
|
pub ty: ProjectileType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Projectile {
|
impl Projectile {
|
||||||
pub fn new(id: EntityId, class: ClassId) -> Self {
|
pub fn new(id: EntityId, class: ClassId, class_name: &ServerClassName) -> Self {
|
||||||
Projectile {
|
Projectile {
|
||||||
id,
|
id,
|
||||||
team: Team::default(),
|
team: Team::default(),
|
||||||
class,
|
class,
|
||||||
position: Vector::default(),
|
position: Vector::default(),
|
||||||
speed: Vector::default(),
|
rotation: Vector::default(),
|
||||||
|
initial_speed: Vector::default(),
|
||||||
bounds: None,
|
bounds: None,
|
||||||
|
launcher: Handle::default(),
|
||||||
|
ty: ProjectileType::new(class_name, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum PipeType {
|
||||||
|
Regular = 0,
|
||||||
|
Sticky = 1,
|
||||||
|
StickyJumper = 2,
|
||||||
|
LooseCannon = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PipeType {
|
||||||
|
pub fn new(number: i64) -> Self {
|
||||||
|
match number {
|
||||||
|
1 => PipeType::Sticky,
|
||||||
|
2 => PipeType::StickyJumper,
|
||||||
|
3 => PipeType::LooseCannon,
|
||||||
|
_ => PipeType::Regular,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_sticky(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
PipeType::Regular | PipeType::LooseCannon => false,
|
||||||
|
PipeType::Sticky | PipeType::StickyJumper => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum ProjectileType {
|
||||||
|
Rocket = 0,
|
||||||
|
HealingArrow = 1,
|
||||||
|
Sticky = 2,
|
||||||
|
Pipe = 3,
|
||||||
|
Flare = 4,
|
||||||
|
LooseCannon = 5,
|
||||||
|
#[default]
|
||||||
|
Unknown = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectileType {
|
||||||
|
pub fn new(class: &ServerClassName, pipe_type: Option<PipeType>) -> Self {
|
||||||
|
match (class.as_str(), pipe_type) {
|
||||||
|
("CTFGrenadePipebombProjectile", Some(PipeType::Sticky | PipeType::StickyJumper)) => {
|
||||||
|
ProjectileType::Sticky
|
||||||
|
}
|
||||||
|
("CTFGrenadePipebombProjectile", Some(PipeType::LooseCannon)) => {
|
||||||
|
ProjectileType::LooseCannon
|
||||||
|
}
|
||||||
|
("CTFGrenadePipebombProjectile", _) => ProjectileType::Pipe,
|
||||||
|
("CTFProjectile_SentryRocket" | "CTFProjectile_Rocket", _) => ProjectileType::Rocket,
|
||||||
|
("CTFProjectile_Flare", _) => ProjectileType::Flare,
|
||||||
|
("CTFProjectile_HealingBolt", _) => ProjectileType::HealingArrow,
|
||||||
|
_ => ProjectileType::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for ProjectileType {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
0 => ProjectileType::Rocket,
|
||||||
|
1 => ProjectileType::HealingArrow,
|
||||||
|
2 => ProjectileType::Sticky,
|
||||||
|
3 => ProjectileType::Pipe,
|
||||||
|
4 => ProjectileType::Flare,
|
||||||
|
5 => ProjectileType::LooseCannon,
|
||||||
|
_ => ProjectileType::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -337,6 +419,7 @@ pub struct GameState {
|
||||||
pub tick: DemoTick,
|
pub tick: DemoTick,
|
||||||
pub server_classes: Vec<ServerClass>,
|
pub server_classes: Vec<ServerClass>,
|
||||||
pub interval_per_tick: f32,
|
pub interval_per_tick: f32,
|
||||||
|
pub outer_map: HashMap<Handle, EntityId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameState {
|
impl GameState {
|
||||||
|
|
@ -373,12 +456,6 @@ impl GameState {
|
||||||
.or_insert_with(|| Building::new(entity_id, class))
|
.or_insert_with(|| Building::new(entity_id, class))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_or_create_projectile(&mut self, id: EntityId, class: ClassId) -> &mut Projectile {
|
|
||||||
self.projectiles
|
|
||||||
.entry(id)
|
|
||||||
.or_insert_with(|| Projectile::new(id, class))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_collision(&self, projectile: &Projectile) -> Option<&Player> {
|
pub fn check_collision(&self, projectile: &Projectile) -> Option<&Player> {
|
||||||
self.players
|
self.players
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
pub use crate::demo::data::game_state::{
|
pub use crate::demo::data::game_state::{
|
||||||
Building, BuildingClass, Dispenser, GameState, Kill, PlayerState, Sentry, Teleporter, World,
|
Building, BuildingClass, Dispenser, GameState, Kill, PlayerState, Sentry, Teleporter, World,
|
||||||
};
|
};
|
||||||
|
use crate::demo::data::game_state::{Handle, PipeType, Projectile, ProjectileType};
|
||||||
use crate::demo::data::DemoTick;
|
use crate::demo::data::DemoTick;
|
||||||
use crate::demo::gameevent_gen::ObjectDestroyedEvent;
|
use crate::demo::gameevent_gen::ObjectDestroyedEvent;
|
||||||
use crate::demo::gamevent::GameEvent;
|
use crate::demo::gamevent::GameEvent;
|
||||||
|
|
@ -44,6 +45,10 @@ impl MessageHandler for GameStateAnalyser {
|
||||||
for entity in &message.entities {
|
for entity in &message.entities {
|
||||||
self.handle_entity(entity, parser_state);
|
self.handle_entity(entity, parser_state);
|
||||||
}
|
}
|
||||||
|
for id in &message.removed_entities {
|
||||||
|
self.state.projectile_destroy(*id);
|
||||||
|
self.state.remove_building(*id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::ServerInfo(message) => {
|
Message::ServerInfo(message) => {
|
||||||
self.state.interval_per_tick = message.interval_per_tick
|
self.state.interval_per_tick = message.interval_per_tick
|
||||||
|
|
@ -124,19 +129,32 @@ impl GameStateAnalyser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
|
pub fn handle_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
|
||||||
let class_name: &str = self
|
const OUTER: SendPropIdentifier =
|
||||||
.class_names
|
SendPropIdentifier::new("DT_AttributeContainer", "m_hOuter");
|
||||||
.get(usize::from(entity.server_class))
|
|
||||||
.map(|class_name| class_name.as_str())
|
let Some(class_name) = self.class_names.get(usize::from(entity.server_class)) else {
|
||||||
.unwrap_or("");
|
return;
|
||||||
match class_name {
|
};
|
||||||
|
|
||||||
|
for prop in &entity.props {
|
||||||
|
if prop.identifier == OUTER {
|
||||||
|
let outer = i64::try_from(&prop.value).unwrap_or_default();
|
||||||
|
self.state
|
||||||
|
.outer_map
|
||||||
|
.insert(Handle(outer), entity.entity_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match class_name.as_str() {
|
||||||
"CTFPlayer" => self.handle_player_entity(entity, parser_state),
|
"CTFPlayer" => self.handle_player_entity(entity, parser_state),
|
||||||
"CTFPlayerResource" => self.handle_player_resource(entity, parser_state),
|
"CTFPlayerResource" => self.handle_player_resource(entity, parser_state),
|
||||||
"CWorld" => self.handle_world_entity(entity, parser_state),
|
"CWorld" => self.handle_world_entity(entity, parser_state),
|
||||||
"CObjectSentrygun" => self.handle_sentry_entity(entity, parser_state),
|
"CObjectSentrygun" => self.handle_sentry_entity(entity, parser_state),
|
||||||
"CObjectDispenser" => self.handle_dispenser_entity(entity, parser_state),
|
"CObjectDispenser" => self.handle_dispenser_entity(entity, parser_state),
|
||||||
"CObjectTeleporter" => self.handle_teleporter_entity(entity, parser_state),
|
"CObjectTeleporter" => self.handle_teleporter_entity(entity, parser_state),
|
||||||
_ if class_name.starts_with("CTFProjectile_") => {
|
_ if class_name.starts_with("CTFProjectile_")
|
||||||
|
|| class_name.as_str() == "CTFGrenadePipebombProjectile" =>
|
||||||
|
{
|
||||||
self.handle_projectile_entity(entity, parser_state)
|
self.handle_projectile_entity(entity, parser_state)
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -213,6 +231,10 @@ impl GameStateAnalyser {
|
||||||
const PROP_BB_MAX: SendPropIdentifier =
|
const PROP_BB_MAX: SendPropIdentifier =
|
||||||
SendPropIdentifier::new("DT_CollisionProperty", "m_vecMaxsPreScaled");
|
SendPropIdentifier::new("DT_CollisionProperty", "m_vecMaxsPreScaled");
|
||||||
|
|
||||||
|
const WEAPON_0: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "000");
|
||||||
|
const WEAPON_1: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "001");
|
||||||
|
const WEAPON_2: SendPropIdentifier = SendPropIdentifier::new("m_hMyWeapons", "002");
|
||||||
|
|
||||||
player.in_pvs = entity.in_pvs;
|
player.in_pvs = entity.in_pvs;
|
||||||
|
|
||||||
for prop in entity.props(parser_state) {
|
for prop in entity.props(parser_state) {
|
||||||
|
|
@ -247,6 +269,18 @@ impl GameStateAnalyser {
|
||||||
let max = Vector::try_from(&prop.value).unwrap_or_default();
|
let max = Vector::try_from(&prop.value).unwrap_or_default();
|
||||||
player.bounds.max = max;
|
player.bounds.max = max;
|
||||||
}
|
}
|
||||||
|
WEAPON_0 => {
|
||||||
|
let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
|
||||||
|
player.weapons[0] = handle;
|
||||||
|
}
|
||||||
|
WEAPON_1 => {
|
||||||
|
let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
|
||||||
|
player.weapons[1] = handle;
|
||||||
|
}
|
||||||
|
WEAPON_2 => {
|
||||||
|
let handle = Handle(i64::try_from(&prop.value).unwrap_or_default());
|
||||||
|
player.weapons[2] = handle;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -501,6 +535,10 @@ impl GameStateAnalyser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_projectile_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
|
pub fn handle_projectile_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
|
||||||
|
let Some(class_name) = self.class_names.get(usize::from(entity.server_class)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
const ROCKET_ORIGIN: SendPropIdentifier =
|
const ROCKET_ORIGIN: SendPropIdentifier =
|
||||||
SendPropIdentifier::new("DT_TFBaseRocket", "m_vecOrigin"); // rockets, arrows, more?
|
SendPropIdentifier::new("DT_TFBaseRocket", "m_vecOrigin"); // rockets, arrows, more?
|
||||||
const GRENADE_ORIGIN: SendPropIdentifier =
|
const GRENADE_ORIGIN: SendPropIdentifier =
|
||||||
|
|
@ -509,14 +547,29 @@ impl GameStateAnalyser {
|
||||||
const TEAM: SendPropIdentifier = SendPropIdentifier::new("DT_BaseEntity", "m_iTeamNum");
|
const TEAM: SendPropIdentifier = SendPropIdentifier::new("DT_BaseEntity", "m_iTeamNum");
|
||||||
const INITIAL_SPEED: SendPropIdentifier =
|
const INITIAL_SPEED: SendPropIdentifier =
|
||||||
SendPropIdentifier::new("DT_TFBaseRocket", "m_vInitialVelocity");
|
SendPropIdentifier::new("DT_TFBaseRocket", "m_vInitialVelocity");
|
||||||
|
const LAUNCHER: SendPropIdentifier =
|
||||||
|
SendPropIdentifier::new("DT_BaseProjectile", "m_hOriginalLauncher");
|
||||||
|
const PIPE_TYPE: SendPropIdentifier =
|
||||||
|
SendPropIdentifier::new("DT_TFProjectile_Pipebomb", "m_iType");
|
||||||
|
const ROCKET_ROTATION: SendPropIdentifier =
|
||||||
|
SendPropIdentifier::new("DT_TFBaseRocket", "m_angRotation");
|
||||||
|
const GRENADE_ROTATION: SendPropIdentifier =
|
||||||
|
SendPropIdentifier::new("DT_TFWeaponBaseGrenadeProj", "m_angRotation");
|
||||||
|
|
||||||
|
if entity.update_type == UpdateType::Delete {
|
||||||
|
self.state.projectile_destroy(entity.entity_index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if entity.in_pvs {
|
|
||||||
let projectile = self
|
let projectile = self
|
||||||
.state
|
.state
|
||||||
.get_or_create_projectile(entity.entity_index, entity.server_class);
|
.projectiles
|
||||||
|
.entry(entity.entity_index)
|
||||||
|
.or_insert_with(|| {
|
||||||
|
Projectile::new(entity.entity_index, entity.server_class, class_name)
|
||||||
|
});
|
||||||
|
|
||||||
// todo: bounds for grenades
|
// todo: bounds for grenades
|
||||||
// todo: track owner
|
|
||||||
|
|
||||||
for prop in entity.props(parser_state) {
|
for prop in entity.props(parser_state) {
|
||||||
match prop.identifier {
|
match prop.identifier {
|
||||||
|
|
@ -530,14 +583,27 @@ impl GameStateAnalyser {
|
||||||
}
|
}
|
||||||
INITIAL_SPEED => {
|
INITIAL_SPEED => {
|
||||||
let speed = Vector::try_from(&prop.value).unwrap_or_default();
|
let speed = Vector::try_from(&prop.value).unwrap_or_default();
|
||||||
projectile.speed = speed;
|
projectile.initial_speed = speed;
|
||||||
|
}
|
||||||
|
LAUNCHER => {
|
||||||
|
let launcher = Handle(i64::try_from(&prop.value).unwrap_or_default());
|
||||||
|
projectile.launcher = launcher;
|
||||||
|
}
|
||||||
|
PIPE_TYPE => {
|
||||||
|
let pipe_type = PipeType::new(i64::try_from(&prop.value).unwrap_or_default());
|
||||||
|
if let Some(class_name) = self.class_names.get(usize::from(entity.server_class))
|
||||||
|
{
|
||||||
|
let ty = ProjectileType::new(class_name, Some(pipe_type));
|
||||||
|
projectile.ty = ty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ROCKET_ROTATION | GRENADE_ROTATION => {
|
||||||
|
let rotation = Vector::try_from(&prop.value).unwrap_or_default();
|
||||||
|
projectile.rotation = rotation;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.state.projectile_destroy(entity.entity_index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_user_info(
|
fn parse_user_info(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue