mirror of
https://codeberg.org/demostf/parser.git
synced 2026-06-03 10:14:06 +02:00
more projectile tracking work
This commit is contained in:
parent
a9c031345f
commit
e0ee7c87d2
3 changed files with 207 additions and 45 deletions
|
|
@ -1,11 +1,15 @@
|
|||
use crate::demo::data::DemoTick;
|
||||
use crate::demo::gameevent_gen::PlayerDeathEvent;
|
||||
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::vector::Vector;
|
||||
use parse_display::Display;
|
||||
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)]
|
||||
pub enum PlayerState {
|
||||
|
|
@ -65,6 +69,7 @@ pub struct Player {
|
|||
pub ping: u16,
|
||||
pub in_pvs: bool,
|
||||
pub bounds: Box,
|
||||
pub weapons: [Handle; 3],
|
||||
}
|
||||
|
||||
pub const PLAYER_BOX_DEFAULT: Box = Box {
|
||||
|
|
@ -91,7 +96,7 @@ impl Player {
|
|||
|
||||
pub fn collides(&self, projectile: &Projectile, time_per_tick: f32) -> bool {
|
||||
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 {
|
||||
Some(_) => todo!(),
|
||||
None => {
|
||||
|
|
@ -275,19 +280,96 @@ pub struct Projectile {
|
|||
pub team: Team,
|
||||
pub class: ClassId,
|
||||
pub position: Vector,
|
||||
pub speed: Vector,
|
||||
pub rotation: Vector,
|
||||
pub initial_speed: Vector,
|
||||
pub bounds: Option<Box>,
|
||||
pub launcher: Handle,
|
||||
pub ty: ProjectileType,
|
||||
}
|
||||
|
||||
impl Projectile {
|
||||
pub fn new(id: EntityId, class: ClassId) -> Self {
|
||||
pub fn new(id: EntityId, class: ClassId, class_name: &ServerClassName) -> Self {
|
||||
Projectile {
|
||||
id,
|
||||
team: Team::default(),
|
||||
class,
|
||||
position: Vector::default(),
|
||||
speed: Vector::default(),
|
||||
rotation: Vector::default(),
|
||||
initial_speed: Vector::default(),
|
||||
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 server_classes: Vec<ServerClass>,
|
||||
pub interval_per_tick: f32,
|
||||
pub outer_map: HashMap<Handle, EntityId>,
|
||||
}
|
||||
|
||||
impl GameState {
|
||||
|
|
@ -373,12 +456,6 @@ impl GameState {
|
|||
.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> {
|
||||
self.players
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
pub use crate::demo::data::game_state::{
|
||||
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::gameevent_gen::ObjectDestroyedEvent;
|
||||
use crate::demo::gamevent::GameEvent;
|
||||
|
|
@ -44,6 +45,10 @@ impl MessageHandler for GameStateAnalyser {
|
|||
for entity in &message.entities {
|
||||
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) => {
|
||||
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) {
|
||||
let class_name: &str = self
|
||||
.class_names
|
||||
.get(usize::from(entity.server_class))
|
||||
.map(|class_name| class_name.as_str())
|
||||
.unwrap_or("");
|
||||
match class_name {
|
||||
const OUTER: SendPropIdentifier =
|
||||
SendPropIdentifier::new("DT_AttributeContainer", "m_hOuter");
|
||||
|
||||
let Some(class_name) = self.class_names.get(usize::from(entity.server_class)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
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),
|
||||
"CTFPlayerResource" => self.handle_player_resource(entity, parser_state),
|
||||
"CWorld" => self.handle_world_entity(entity, parser_state),
|
||||
"CObjectSentrygun" => self.handle_sentry_entity(entity, parser_state),
|
||||
"CObjectDispenser" => self.handle_dispenser_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)
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -213,6 +231,10 @@ impl GameStateAnalyser {
|
|||
const PROP_BB_MAX: SendPropIdentifier =
|
||||
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;
|
||||
|
||||
for prop in entity.props(parser_state) {
|
||||
|
|
@ -247,6 +269,18 @@ impl GameStateAnalyser {
|
|||
let max = Vector::try_from(&prop.value).unwrap_or_default();
|
||||
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) {
|
||||
let Some(class_name) = self.class_names.get(usize::from(entity.server_class)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
const ROCKET_ORIGIN: SendPropIdentifier =
|
||||
SendPropIdentifier::new("DT_TFBaseRocket", "m_vecOrigin"); // rockets, arrows, more?
|
||||
const GRENADE_ORIGIN: SendPropIdentifier =
|
||||
|
|
@ -509,34 +547,62 @@ impl GameStateAnalyser {
|
|||
const TEAM: SendPropIdentifier = SendPropIdentifier::new("DT_BaseEntity", "m_iTeamNum");
|
||||
const INITIAL_SPEED: SendPropIdentifier =
|
||||
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.in_pvs {
|
||||
let projectile = self
|
||||
.state
|
||||
.get_or_create_projectile(entity.entity_index, entity.server_class);
|
||||
|
||||
// todo: bounds for grenades
|
||||
// todo: track owner
|
||||
|
||||
for prop in entity.props(parser_state) {
|
||||
match prop.identifier {
|
||||
ROCKET_ORIGIN | GRENADE_ORIGIN => {
|
||||
let pos = Vector::try_from(&prop.value).unwrap_or_default();
|
||||
projectile.position = pos
|
||||
}
|
||||
TEAM => {
|
||||
let team = Team::new(i64::try_from(&prop.value).unwrap_or_default());
|
||||
projectile.team = team;
|
||||
}
|
||||
INITIAL_SPEED => {
|
||||
let speed = Vector::try_from(&prop.value).unwrap_or_default();
|
||||
projectile.speed = speed;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if entity.update_type == UpdateType::Delete {
|
||||
self.state.projectile_destroy(entity.entity_index);
|
||||
return;
|
||||
}
|
||||
|
||||
let projectile = self
|
||||
.state
|
||||
.projectiles
|
||||
.entry(entity.entity_index)
|
||||
.or_insert_with(|| {
|
||||
Projectile::new(entity.entity_index, entity.server_class, class_name)
|
||||
});
|
||||
|
||||
// todo: bounds for grenades
|
||||
|
||||
for prop in entity.props(parser_state) {
|
||||
match prop.identifier {
|
||||
ROCKET_ORIGIN | GRENADE_ORIGIN => {
|
||||
let pos = Vector::try_from(&prop.value).unwrap_or_default();
|
||||
projectile.position = pos
|
||||
}
|
||||
TEAM => {
|
||||
let team = Team::new(i64::try_from(&prop.value).unwrap_or_default());
|
||||
projectile.team = team;
|
||||
}
|
||||
INITIAL_SPEED => {
|
||||
let speed = Vector::try_from(&prop.value).unwrap_or_default();
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue