-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathdeadlock-gametime.rs
113 lines (98 loc) · 3.88 KB
/
deadlock-gametime.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/// this example shows how to compute game time in deadlock.
/// note that logic for dota 2 would be a little bit different, especially for older replays.
use std::fs::File;
use std::io::BufReader;
use anyhow::{Context as _, Result};
use haste::demofile::DemoFile;
use haste::demostream::CmdHeader;
use haste::entities::{fkey_from_path, DeltaHeader, Entity};
use haste::fxhash;
use haste::parser::{Context, Parser, Visitor};
use haste::valveprotos::common::{CnetMsgTick, EDemoCommands, NetMessages};
use haste::valveprotos::prost::Message;
const DEADLOCK_GAMERULES_ENTITY: u64 = fxhash::hash_bytes(b"CCitadelGameRulesProxy");
#[derive(Default)]
struct MyVisitor {
net_tick: u32,
/// tick intervals in dota 2 and in deadlock are different.
tick_interval: Option<f32>,
/// game does not start when replay recording starts. in various deadlock replays i observed
/// following values: 26.866669, 45.01667, 19.983334.
game_start_time: Option<f32>,
game_paused: bool,
pause_start_tick: i32,
total_paused_ticks: i32,
}
impl MyVisitor {
fn handle_net_tick(&mut self, data: &[u8]) -> anyhow::Result<()> {
let msg = CnetMsgTick::decode(data)?;
if let Some(net_tick) = msg.tick {
self.net_tick = net_tick;
}
Ok(())
}
fn handle_game_rules(&mut self, entity: &Entity) -> anyhow::Result<()> {
debug_assert!(entity.serializer_name_heq(DEADLOCK_GAMERULES_ENTITY));
let game_start_time: f32 =
entity.try_get_value(&fkey_from_path(&["m_pGameRules", "m_flGameStartTime"]))?;
// NOTE: 0.001 is an arbitrary number; nothing special.
if game_start_time < 0.001 {
return Ok(());
}
self.game_start_time = Some(game_start_time);
self.game_paused =
entity.try_get_value(&fkey_from_path(&["m_pGameRules", "m_bGamePaused"]))?;
self.pause_start_tick =
entity.try_get_value(&fkey_from_path(&["m_pGameRules", "m_nPauseStartTick"]))?;
self.total_paused_ticks =
entity.try_get_value(&fkey_from_path(&["m_pGameRules", "m_nTotalPausedTicks"]))?;
Ok(())
}
/// `None` means that the game has not started yet.
fn get_game_time(&self) -> Option<f32> {
Some(
((self.net_tick as f32 - self.total_paused_ticks as f32) * self.tick_interval?)
- self.game_start_time?,
)
}
}
impl Visitor for MyVisitor {
fn on_cmd(&mut self, ctx: &Context, cmd_header: &CmdHeader, _data: &[u8]) -> Result<()> {
// DemSyncTick indicates that all initialization messages were handled and now actual data
// will flow; at this point tick interval is known.
if self.tick_interval.is_none() && cmd_header.cmd == EDemoCommands::DemSyncTick {
self.tick_interval = Some(ctx.tick_interval());
}
Ok(())
}
fn on_packet(&mut self, _ctx: &Context, packet_type: u32, data: &[u8]) -> Result<()> {
if packet_type == NetMessages::NetTick as u32 {
self.handle_net_tick(data)?;
}
Ok(())
}
fn on_entity(
&mut self,
_ctx: &Context,
_delta_header: DeltaHeader,
entity: &Entity,
) -> Result<()> {
if entity.serializer_name_heq(DEADLOCK_GAMERULES_ENTITY) {
self.handle_game_rules(entity)?;
}
Ok(())
}
fn on_tick_end(&mut self, _ctx: &Context) -> Result<()> {
eprintln!("game_time: {:?}", self.get_game_time());
Ok(())
}
}
fn main() -> Result<()> {
let args: Vec<String> = std::env::args().collect();
let filepath = args.get(1).context("usage: deadlock-gametime <filepath>")?;
let file = File::open(filepath)?;
let buf_reader = BufReader::new(file);
let demo_file = DemoFile::start_reading(buf_reader)?;
let mut parser = Parser::from_stream_with_visitor(demo_file, MyVisitor::default())?;
parser.run_to_end()
}