custom analyzer

This commit is contained in:
Robin Appelman 2022-04-03 15:53:48 +02:00
commit 9c1b246e7c
2 changed files with 133 additions and 52 deletions

View file

@ -181,7 +181,7 @@ impl Control for DemoCamera {
"playing tick" "playing tick"
); );
// todo: interpolate // todo: interpolate
let (position, yaw, pitch) = self.demo.positions[tick as usize]; let (position, [pitch, yaw]) = self.demo.positions[tick as usize];
self.apply_view(_camera, position, yaw, pitch); self.apply_view(_camera, position, yaw, pitch);
} }
} }

View file

@ -2,14 +2,19 @@ use crate::bsp::map_coords;
use crate::Error; use crate::Error;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use tf_demo_parser::demo::parser::gamestateanalyser::{GameState, GameStateAnalyser}; use tf_demo_parser::demo::data::UserInfo;
use tf_demo_parser::{Demo, DemoParser}; use tf_demo_parser::demo::message::packetentities::EntityId;
use tf_demo_parser::demo::message::Message;
use tf_demo_parser::demo::packet::stringtable::StringTableEntry;
use tf_demo_parser::demo::parser::MessageHandler;
use tf_demo_parser::demo::sendprop::SendPropIdentifier;
use tf_demo_parser::demo::vector::{Vector, VectorXY};
use tf_demo_parser::{Demo, DemoParser, MessageType, ParserState, ReadResult, Stream};
use three_d::{vec3, Vec3}; use three_d::{vec3, Vec3};
use tracing::{error, info};
pub struct DemoInfo { pub struct DemoInfo {
pub map: String, pub map: String,
pub positions: Vec<(Vec3, f32, f32)>, pub positions: Vec<(Vec3, [f32; 2])>,
pub start_tick: u32, pub start_tick: u32,
pub time_per_tick: f64, pub time_per_tick: f64,
} }
@ -18,59 +23,135 @@ impl DemoInfo {
pub fn new(demo_path: impl AsRef<Path>, name: &str) -> Result<Self, Error> { pub fn new(demo_path: impl AsRef<Path>, name: &str) -> Result<Self, Error> {
let file = fs::read(demo_path)?; let file = fs::read(demo_path)?;
let demo = Demo::new(&file); let demo = Demo::new(&file);
let parser = DemoParser::new_with_analyser(demo.get_stream(), GameStateAnalyser::new()); let parser =
let (header, mut ticker) = parser.ticker()?; DemoParser::new_with_analyser(demo.get_stream(), PovAnalyzer::new(name.into()));
let (header, (positions, start_tick, interval_per_tick)) = parser.parse()?;
let mut positions = Vec::with_capacity(header.ticks as usize);
let mut user_id = None;
let mut start_tick = 0;
while let Some(tick) = ticker.next()? {
let state: &GameState = tick.state;
if user_id.is_none() {
if let Some(found) = state
.players
.iter()
.enumerate()
.filter_map(|(i, player)| Some((i, player.info.as_ref()?)))
.find_map(|(i, player)| {
player.name.to_ascii_lowercase().contains(name).then(|| i)
})
{
info!(user_id = found, "found user");
start_tick = tick.tick;
user_id = Some(found);
}
}
if let Some(user_id) = user_id {
let player = &state.players[user_id];
let coords = map_coords(player.position);
positions.push((
vec3(coords[0], coords[1], coords[2]),
player.view_angle,
player.pitch_angle,
))
}
}
if user_id.is_none() {
let found = ticker
.into_state()
.players
.into_iter()
.filter_map(|player| Some(player.info?.name))
.collect::<Vec<_>>();
error!(
"User {} not found in demo, found: {}",
name,
found.join(", ")
);
return Err("Failed to find user in demo".into());
}
Ok(DemoInfo { Ok(DemoInfo {
map: header.map, map: header.map,
positions, positions,
start_tick, start_tick,
time_per_tick: ticker.parser_state().demo_meta.interval_per_tick as f64, time_per_tick: interval_per_tick as f64,
}) })
} }
} }
struct PovAnalyzer {
last_position: Vector,
last_angles: [f32; 2],
positions: Vec<(Vec3, [f32; 2])>,
name: String,
player: Option<EntityId>,
start_tick: u32,
last_tick: u32,
}
impl MessageHandler for PovAnalyzer {
type Output = (Vec<(Vec3, [f32; 2])>, u32, f32);
fn does_handle(message_type: MessageType) -> bool {
matches!(message_type, MessageType::PacketEntities)
}
fn handle_message(&mut self, message: &Message, tick: u32) {
const LOCAL_ORIGIN: SendPropIdentifier =
SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_vecOrigin");
const NON_LOCAL_ORIGIN: SendPropIdentifier =
SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_vecOrigin");
const LOCAL_ORIGIN_Z: SendPropIdentifier =
SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_vecOrigin[2]");
const NON_LOCAL_ORIGIN_Z: SendPropIdentifier =
SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_vecOrigin[2]");
const LOCAL_YAW_ANGLES: SendPropIdentifier =
SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_angEyeAngles[1]");
const NON_LOCAL_YAW_ANGLES: SendPropIdentifier =
SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_angEyeAngles[1]");
const LOCAL_PITCH_ANGLES: SendPropIdentifier =
SendPropIdentifier::new("DT_TFLocalPlayerExclusive", "m_angEyeAngles[0]");
const NON_LOCAL_PITCH_ANGLES: SendPropIdentifier =
SendPropIdentifier::new("DT_TFNonLocalPlayerExclusive", "m_angEyeAngles[0]");
if let (Message::PacketEntities(message), Some(player_id)) = (message, self.player) {
if self.start_tick == 0 {
self.start_tick = tick;
}
for entity in &message.entities {
if entity.entity_index == player_id {
for prop in &entity.props {
match prop.identifier {
LOCAL_ORIGIN | NON_LOCAL_ORIGIN => {
let pos_xy = VectorXY::try_from(&prop.value).unwrap_or_default();
self.last_position.x = pos_xy.x;
self.last_position.y = pos_xy.y;
}
LOCAL_ORIGIN_Z | NON_LOCAL_ORIGIN_Z => {
self.last_position.z =
f32::try_from(&prop.value).unwrap_or_default()
}
LOCAL_YAW_ANGLES | NON_LOCAL_YAW_ANGLES => {
self.last_angles[1] = f32::try_from(&prop.value).unwrap_or_default()
}
LOCAL_PITCH_ANGLES | NON_LOCAL_PITCH_ANGLES => {
self.last_angles[0] = f32::try_from(&prop.value).unwrap_or_default()
}
_ => {}
}
}
}
}
}
if tick > self.last_tick {
self.last_tick = tick;
let pos = map_coords(self.last_position);
self.positions
.push((vec3(pos[0], pos[1], pos[2]), self.last_angles));
}
}
fn handle_string_entry(&mut self, table: &str, _index: usize, entry: &StringTableEntry) {
if table == "userinfo" && self.player.is_none() {
let _ = self.parse_user_info(
entry.text.as_ref().map(|s| s.as_ref()),
entry.extra_data.as_ref().map(|data| data.data.clone()),
);
}
}
fn into_output(self, state: &ParserState) -> Self::Output {
(
self.positions,
self.start_tick,
state.demo_meta.interval_per_tick,
)
}
}
impl PovAnalyzer {
pub fn new(name: String) -> Self {
PovAnalyzer {
last_position: Vector::default(),
last_angles: [0.0, 0.0],
positions: vec![],
name,
player: None,
start_tick: 0,
last_tick: 0,
}
}
fn parse_user_info(&mut self, text: Option<&str>, data: Option<Stream>) -> ReadResult<()> {
if let Some(user_info) = UserInfo::parse_from_string_table(text, data)? {
if user_info
.player_info
.name
.to_ascii_lowercase()
.contains(&self.name)
{
self.player = Some(user_info.entity_id);
}
}
Ok(())
}
}