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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use crate::utils;
use std::fmt::Write;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
#[repr(C)]
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct StatsDataBlock {
pub ddstats_version: i32,
pub player_id: i32,
pub username: [u8; 32],
pub time: f32,
pub gems_collected: i32,
pub kills: i32,
pub daggers_fired: i32,
pub daggers_hit: i32,
pub enemies_alive: i32,
pub level_gems: i32,
pub homing: i32,
pub gems_despawned: i32,
pub gems_eaten: i32,
pub gems_total: i32,
pub daggers_eaten: i32,
pub per_enemy_alive_count: [i16; 17],
pub per_enemy_kill_count: [i16; 17],
pub is_player_alive: bool,
pub is_replay: bool,
pub death_type: u8,
pub is_in_game: bool,
pub replay_player_id: i32,
pub replay_player_name: [u8; 32],
pub survival_md5: [u8; 16],
pub time_lvl2: f32,
pub time_lvl3: f32,
pub time_lvl4: f32,
pub levi_down_time: f32,
pub orb_down_time: f32,
pub status: i32,
pub max_homing: i32,
pub time_max_homing: f32,
pub enemies_alive_max: i32,
pub time_enemies_alive_max: f32,
pub time_max: f32,
padding1: [u8; 4],
pub stats_base: [u8; 8],
pub stats_frames_loaded: i32,
pub stats_finished_loading: bool,
padding2: [u8; 3],
pub starting_hand: i32,
pub starting_homing: i32,
pub starting_time: f32,
pub prohibited_mods: bool,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, serde::Serialize)]
pub struct StatsFrame {
pub gems_collected: i32,
pub kills: i32,
pub daggers_fired: i32,
pub daggers_hit: i32,
pub enemies_alive: i32,
pub level_gems: i32,
pub homing: i32,
pub gems_despawned: i32,
pub gems_eaten: i32,
pub gems_total: i32,
pub daggers_eaten: i32,
pub per_enemy_alive_count: [i16; 17],
pub per_enemy_kill_count: [i16; 17],
}
#[derive(Debug, Default, Clone, serde::Serialize)]
pub struct StatsBlockWithFrames {
pub block: StatsDataBlock,
pub frames: Vec<StatsFrame>,
}
impl StatsDataBlock {
pub fn player_username(&self) -> String {
utils::byte_array_to_string(&self.username[..]).unwrap_or("unknown".into())
}
pub fn replay_player_username(&self) -> String {
utils::byte_array_to_string(&self.replay_player_name[..]).unwrap_or("unknown".into())
}
pub fn level_hash(&self) -> String {
let mut s = String::with_capacity(2 * self.survival_md5.len());
for byte in self.survival_md5 {
write!(s, "{:02X}", byte).expect("Couldn't decode hash byte");
}
return s;
}
pub fn get_stats_pointer(&self) -> usize {
i64::from_le_bytes(self.stats_base) as usize
}
pub fn status(&self) -> GameStatus {
FromPrimitive::from_i32(self.status).unwrap()
}
}
impl StatsBlockWithFrames {
#[rustfmt::skip]
pub fn get_frame_for_time(&self, time: f32) -> Option<&StatsFrame> {
let real_time = time - self.block.starting_time;
if real_time <= 0. { return None; }
if real_time + 1. > self.frames.len() as f32 { return None; }
return Some(&self.frames[real_time as usize]);
}
pub fn get_frames_until_time(&self, mut time: f32) -> Vec<&StatsFrame> {
let mut res = vec![];
if time as usize + 1 > self.frames.len() {
time = self.frames.len() as f32 - 1.;
}
res.extend(self.frames[..time as usize].iter());
res
}
#[rustfmt::skip]
pub fn homing_usage_from_frames(&self, time: Option<f32>) -> u32 {
let mut neg_diff_lvl3 = 0;
let mut neg_diff_lvl4 = 0;
let mut last_frame_homing_lvl3 = 0;
let mut last_frame_homing_lvl4 = 0;
let cutoff = if time.is_none() { f32::MAX } else { time.unwrap() };
for frame in &self.get_frames_until_time(cutoff) {
if frame.level_gems == 70 {
if frame.homing < last_frame_homing_lvl3 {
neg_diff_lvl3 += -(frame.homing - last_frame_homing_lvl3);
}
last_frame_homing_lvl3 = frame.homing;
} else if frame.level_gems == 71 {
if frame.homing < last_frame_homing_lvl4 {
neg_diff_lvl4 += -(frame.homing - last_frame_homing_lvl4);
}
last_frame_homing_lvl4 = frame.homing;
}
}
(neg_diff_lvl3 + neg_diff_lvl4) as u32
}
}
#[derive(FromPrimitive, Debug, PartialEq, Clone, Copy)]
pub enum GameStatus {
Title = 0,
Menu,
Lobby,
Playing,
Dead,
OwnReplayFromLastRun,
OwnReplayFromLeaderboard,
OtherReplay,
}