ddcore_rs/models/
mod.rs

1//
2// Models
3//
4
5pub 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, // 0 = Intro Screen | 1 = Main Menu | 2 = InGame | 3 = DEAD
71    pub max_homing: i32,
72    pub time_max_homing: f32, // gets updated every gem you get even if you dont have any homing
73    pub enemies_alive_max: i32, // doesn't get reset sometimes when restarting a run
74    pub time_enemies_alive_max: f32,
75    pub time_max: f32,       // Max time of replay / current time in-game
76    padding1: [u8; 4],       // fun
77    pub stats_base: [u8; 8], // Pointer to frames
78    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}