1pub mod replay;
6pub mod spawnset;
7
8use crate::utils;
9use std::fmt::Write;
10use num_derive::FromPrimitive;
11use num_traits::FromPrimitive;
12
13#[derive(Debug, FromPrimitive, Clone, Copy, PartialEq, Eq, serde::Serialize)]
14pub enum GameMode {
15 Survival = 0,
16 TimeAttack,
17 Race,
18}
19
20impl std::default::Default for GameMode {
21 fn default() -> Self {
22 GameMode::Survival
23 }
24}
25
26impl std::convert::From<u8> for GameMode {
27 fn from(v: u8) -> Self {
28 match v {
29 0 => GameMode::Survival,
30 1 => GameMode::TimeAttack,
31 2 => GameMode::Race,
32 _ => GameMode::default(),
33 }
34 }
35}
36
37#[repr(C)]
38#[derive(Debug, Clone, Default, serde::Serialize)]
39pub struct StatsDataBlock {
40 marker: [u8; 11],
41 pub ddstats_version: i32,
42 pub player_id: i32,
43 pub username: [u8; 32],
44 pub time: f32,
45 pub gems_collected: i32,
46 pub kills: i32,
47 pub daggers_fired: i32,
48 pub daggers_hit: i32,
49 pub enemies_alive: i32,
50 pub level_gems: i32,
51 pub homing: i32,
52 pub gems_despawned: i32,
53 pub gems_eaten: i32,
54 pub gems_total: i32,
55 pub daggers_eaten: i32,
56 pub per_enemy_alive_count: [i16; 17],
57 pub per_enemy_kill_count: [i16; 17],
58 pub is_player_alive: bool,
59 pub is_replay: bool,
60 pub death_type: u8,
61 pub is_in_game: bool,
62 pub replay_player_id: i32,
63 pub replay_player_name: [u8; 32],
64 pub survival_md5: [u8; 16],
65 pub time_lvl2: f32,
66 pub time_lvl3: f32,
67 pub time_lvl4: f32,
68 pub levi_down_time: f32,
69 pub orb_down_time: f32,
70 pub status: i32, pub max_homing: i32,
72 pub time_max_homing: f32, pub enemies_alive_max: i32, pub time_enemies_alive_max: f32,
75 pub time_max: f32, padding1: [u8; 4], pub stats_base: [u8; 8], pub stats_frames_loaded: i32,
79 pub stats_finished_loading: bool,
80 padding2: [u8; 3],
81 pub starting_hand: i32,
82 pub starting_homing: i32,
83 pub starting_time: f32,
84 pub prohibited_mods: bool,
85 padding3: [u8; 3],
86 pub replay_base: [u8; 8],
87 pub replay_buffer_length: i32,
88 pub replay_flag: bool,
89 pub game_mode: u8,
90 pub is_time_attack_or_race_finished: bool
91}
92
93#[repr(C)]
94#[derive(Debug, Clone, Copy, Default, serde::Serialize)]
95pub struct StatsFrame {
96 pub gems_collected: i32,
97 pub kills: i32,
98 pub daggers_fired: i32,
99 pub daggers_hit: i32,
100 pub enemies_alive: i32,
101 pub level_gems: i32,
102 pub homing: i32,
103 pub gems_despawned: i32,
104 pub gems_eaten: i32,
105 pub gems_total: i32,
106 pub daggers_eaten: i32,
107 pub per_enemy_alive_count: [i16; 17],
108 pub per_enemy_kill_count: [i16; 17],
109}
110
111#[derive(Debug, Default, Clone, serde::Serialize)]
112pub struct StatsBlockWithFrames {
113 pub block: StatsDataBlock,
114 pub frames: Vec<StatsFrame>,
115}
116
117impl StatsDataBlock {
118 pub fn player_username(&self) -> String {
119 utils::byte_array_to_string(&self.username[..]).unwrap_or_else(|_| "unknown".into())
120 }
121
122 pub fn replay_player_username(&self) -> String {
123 utils::byte_array_to_string(&self.replay_player_name[..]).unwrap_or_else(|_| "unknown".into())
124 }
125
126 pub fn level_hash(&self) -> String {
127 let mut s = String::with_capacity(2 * self.survival_md5.len());
128 for byte in self.survival_md5 {
129 write!(s, "{:02X}", byte).expect("Couldn't decode hash byte");
130 }
131 s
132 }
133
134 pub fn get_stats_pointer(&self) -> usize {
135 i64::from_le_bytes(self.stats_base) as usize
136 }
137
138 pub fn get_replay_pointer(&self) -> usize {
139 i64::from_le_bytes(self.replay_base) as usize
140 }
141
142 pub fn status(&self) -> GameStatus {
143 FromPrimitive::from_i32(self.status).unwrap()
144 }
145}
146
147impl StatsBlockWithFrames {
148 #[rustfmt::skip]
149 pub fn get_frame_for_time(&self, time: f32) -> Option<&StatsFrame> {
150 let real_time = time - self.block.starting_time;
151 if real_time <= 0. { return None; }
152 if real_time + 1. > self.frames.len() as f32 { return None; }
153 Some(&self.frames[real_time as usize])
154 }
155
156 pub fn get_frames_until_time(&self, mut time: f32) -> Vec<&StatsFrame> {
157 let mut res = vec![];
158 if time as usize + 1 > self.frames.len() {
159 time = self.frames.len() as f32 - 1.;
160 }
161 res.extend(self.frames[..time as usize].iter());
162 res
163 }
164
165 #[rustfmt::skip]
166 pub fn homing_usage_from_frames(&self, time: Option<f32>) -> u32 {
167 let mut neg_diff_lvl3 = 0;
168 let mut neg_diff_lvl4 = 0;
169 let mut last_frame_homing_lvl3 = 0;
170 let mut last_frame_homing_lvl4 = 0;
171 let cutoff = if let Some(time) = time { time } else { f32::MAX };
172 for frame in &self.get_frames_until_time(cutoff) {
173 if frame.level_gems == 70 {
174 if frame.homing < last_frame_homing_lvl3 {
175 neg_diff_lvl3 += -(frame.homing - last_frame_homing_lvl3);
176 }
177 last_frame_homing_lvl3 = frame.homing;
178 } else if frame.level_gems == 71 {
179 if frame.homing < last_frame_homing_lvl4 {
180 neg_diff_lvl4 += -(frame.homing - last_frame_homing_lvl4);
181 }
182 last_frame_homing_lvl4 = frame.homing;
183 }
184 }
185 (neg_diff_lvl3 + neg_diff_lvl4) as u32
186 }
187}
188
189#[derive(FromPrimitive, Debug, PartialEq, Clone, Copy)]
190pub enum GameStatus {
191 Title = 0,
192 Menu,
193 Lobby,
194 Playing,
195 Dead,
196 OwnReplayFromLastRun,
197 OwnReplayFromLeaderboard,
198 OtherReplay,
199 LocalReplay,
200}