mirror of
https://codeberg.org/icewind/vbspview.git
synced 2026-06-03 10:14:10 +02:00
custom analyzer
This commit is contained in:
parent
067e1f99df
commit
9c1b246e7c
2 changed files with 133 additions and 52 deletions
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
185
src/demo.rs
185
src/demo.rs
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue