mgx/
record.rs

1use serde::Serialize;
2use std::fmt::Debug;
3
4/// Get value from `Option<T>` if it's Some and `anyhow::bail!` if `None`
5#[doc(hidden)]
6#[macro_export]
7macro_rules! val {
8    ($x:expr) => {
9        match $x {
10            Some(x) => x,
11            None => bail!("{:?} is None @ {}, line: {}", stringify!($x), file!(), line!()),
12        }
13    };
14}
15
16/// Store information of this game extracted from the recorded game. Most fields will be `None` if not present in the recorded game or exception occurs during parsing
17#[derive(Debug, Serialize, Default)]
18pub struct Record {
19    pub parser: String,
20    pub md5: Option<String>,
21    pub filename: String,
22    pub filesize: usize,
23    /// In milliseconds since epoch
24    pub lastmod: u128,
25    pub guid: Option<String>,
26    pub verlog: Option<u32>,
27    pub ver: Option<Version>,
28    pub verraw: Option<String>,
29    /// `11.76` for aoc10a/c
30    pub versave: Option<f32>,
31    pub versave2: Option<u32>,
32    pub verscenario: Option<f32>,
33    pub include_ai: Option<bool>,
34    pub speed_raw: Option<u32>,
35    pub speed: Option<String>,
36    pub recorder: Option<u16>,
37    /// **GAIA** is counted
38    pub totalplayers: Option<u8>,
39    pub mapsize_raw: Option<u32>,
40    pub mapsize: Option<String>,
41    pub revealmap_raw: Option<i32>,
42    pub revealmap: Option<String>,
43    pub mapx: Option<i32>,
44    pub mapy: Option<i32>,
45    pub fogofwar: Option<bool>,
46    pub instantbuild: Option<bool>,
47    pub enablecheats: Option<bool>,
48    pub restoretime: Option<u32>,
49    pub ismultiplayer: Option<bool>,
50    pub isconquest: Option<bool>,
51    pub relics2win: Option<i32>,
52    pub explored2win: Option<i32>,
53    pub anyorall: Option<bool>,
54    pub victorytype_raw: Option<i32>,
55    pub victorytype: Option<String>,
56    pub score2win: Option<i32>,
57    pub time2win_raw: Option<i32>,
58    pub time2win: Option<String>,
59    #[serde(skip)]
60    pub scenariofilename_raw: Option<Vec<u8>>,
61    pub scenariofilename: Option<String>,
62    #[serde(skip)]
63    pub instructions_raw: Option<Vec<u8>>,
64    pub instructions: Option<String>,
65    pub duration: u32,
66    pub chat: Vec<Chat>,
67    pub mapid: Option<u32>,
68    pub mapname: Option<String>,
69    pub difficulty_raw: Option<i32>,
70    pub difficulty: Option<String>,
71    pub lockteams: Option<bool>,
72    pub poplimit: Option<u32>,
73    pub gametype_raw: Option<u8>,
74    pub gametype: Option<String>,
75    pub lockdiplomacy: Option<bool>,
76    pub haswinner: bool,
77    pub matchup: Option<Vec<usize>>,
78    pub teams: Vec<Vec<i32>>,
79    pub players: [Player; 9],
80    /// Debug data used by the parser. Strip this out in output json.
81    #[serde(skip)]
82    pub debug: DebugInfo,
83}
84
85/// Information of a player
86#[derive(Debug, Serialize, Default)]
87pub struct Player {
88    pub slot: usize,
89    pub index: Option<i32>,
90    pub playertype: Option<i32>,
91    #[serde(skip)]
92    pub name_raw: Option<Vec<u8>>,
93    pub name: Option<String>,
94    pub teamid: Option<u8>,
95    pub ismainop: Option<bool>,
96    pub initx: Option<f32>,
97    pub inity: Option<f32>,
98    pub civ_raw: Option<u8>,
99    pub civ: Option<String>,
100    pub colorid: Option<u8>,
101    pub disconnected: Option<bool>,
102    pub resigned: Option<u32>,
103    pub feudaltime: Option<u32>,
104    pub castletime: Option<u32>,
105    pub imperialtime: Option<u32>,
106    pub initage_raw: Option<f32>,
107    pub initage: Option<String>,
108    pub initfood: Option<f32>,
109    pub initwood: Option<f32>,
110    pub initstone: Option<f32>,
111    pub initgold: Option<f32>,
112    pub initpop: Option<f32>,
113    pub initcivilian: Option<f32>,
114    pub initmilitary: Option<f32>,
115    /// Only presents in UP1.5
116    pub modversion: Option<f32>,
117    /// Default is `false`. Only for fair 2-sided games
118    pub winner: Option<bool>,
119}
120
121impl Player {
122    pub fn new(slot: usize) -> Self {
123        Player { slot, ..Default::default() }
124    }
125
126    pub fn isvalid(&self) -> bool {
127        self.playertype.is_some_and(|x| (2..=5).contains(&x))
128    }
129}
130
131/// Information of a chat message. Lobby chats don't have time. Field `player` is not implemented yet
132#[derive(Debug, Serialize)]
133pub struct Chat {
134    pub time: Option<u32>,
135    /// Not implemented yet
136    pub player: Option<u8>,
137    #[serde(skip)]
138    pub content_raw: Option<Vec<u8>>,
139    pub content: Option<String>,
140}
141
142/// Debug information used by the parser
143#[derive(Debug, Default)]
144pub struct DebugInfo {
145    pub currentpos_header: usize,
146    pub currentpos_body: usize,
147    pub aipos: usize,
148    pub initpos: usize,
149    pub triggerpos: usize,
150    pub triggersign: f64,
151    pub settingspos: usize,
152    pub disabledtechspos: usize,
153    pub victorypos: usize,
154    pub scenariopos: usize,
155    pub mappos: Option<usize>,
156    pub playerinitpos_by_idx: [Option<usize>; 9],
157    pub earlymovecount: usize,
158    pub earlymovecmd: Vec<[u8; 19]>,
159    pub earlymovetime: Vec<u32>,
160}
161
162/// Version of the recorded game
163#[derive(Debug, PartialEq, Serialize)]
164pub enum Version {
165    AoKTrial,
166    AoK,
167    AoCTrial,
168    AoC,
169    AoC10a,
170    AoC10c,
171    UP12,
172    UP13,
173    UP14,
174    UP14RC1,
175    UP14RC2,
176    UP15,
177    AoFE21,
178    HD,
179    DE,
180    MCP,
181    Unknown,
182}
183
184impl Record {
185    pub fn new(filename: String, filesize: usize, lastmod: u128) -> Self {
186        Record {
187            parser: format!(
188                "mgx-rs {}-{}",
189                env!("CARGO_PKG_VERSION"),
190                if cfg!(debug_assertions) { "debug" } else { "release" }
191            ),
192            filename,
193            filesize,
194            lastmod,
195            players: [
196                Player::new(0),
197                Player::new(1),
198                Player::new(2),
199                Player::new(3),
200                Player::new(4),
201                Player::new(5),
202                Player::new(6),
203                Player::new(7),
204                Player::new(8),
205            ],
206            debug: DebugInfo {
207                triggersign: 1.6, // Other values in higher versions
208                ..Default::default()
209            },
210            ..Default::default()
211        }
212    }
213}