1mod utils;
2
3use aoe2rec::Savegame;
4use bytes::Bytes;
5
6use js_sys::Uint8Array;
7use wasm_bindgen::prelude::*;
8
9#[wasm_bindgen]
10extern "C" {
11 fn alert(s: &str);
12 #[wasm_bindgen(js_namespace = console)]
13 fn log(s: &str);
14}
15
16#[wasm_bindgen]
17pub fn parse_rec(buffer: js_sys::ArrayBuffer) -> JsValue {
18 utils::set_panic_hook();
19
20 let input_rec = Uint8Array::new(&buffer).to_vec();
21 let rec = Savegame::from_bytes(Bytes::from(input_rec)).unwrap();
22 serde_wasm_bindgen::to_value(&rec).unwrap()
23}
24
25#[wasm_bindgen]
26pub fn parse_rec_summary(buffer: js_sys::ArrayBuffer) -> SavegameSummary {
27 utils::set_panic_hook();
28
29 let input_rec = Uint8Array::new(&buffer).to_vec();
30 let rec = Savegame::from_bytes(Bytes::from(input_rec)).unwrap();
31 SavegameSummary::from(rec.get_summary())
32}
33
34#[wasm_bindgen(getter_with_clone)]
35pub struct SavegameSummary {
36 pub header: SavegameHeader,
37 pub teams: Vec<Team>,
38 pub duration: u32,
39}
40
41impl From<aoe2rec::summary::SavegameSummary<'_>> for SavegameSummary {
42 fn from(summary: aoe2rec::summary::SavegameSummary) -> SavegameSummary {
43 SavegameSummary {
44 header: SavegameHeader {
45 game_string: summary.header.game.to_owned().into(),
46 version_major: summary.header.version_major,
47 version_minor: summary.header.version_minor,
48 build: summary.header.build,
49 timestamp: summary.header.timestamp,
50 game_settings: GameSettings::from(&summary),
51 replay: Replay::from(&summary),
52 },
53 teams: summary.teams.iter().map(|team| team.into()).collect(),
54 duration: summary.duration,
55 }
56 }
57}
58
59#[wasm_bindgen(getter_with_clone)]
60#[derive(Clone)]
61pub struct SavegameHeader {
62 pub game_string: String,
63 pub version_major: u16,
64 pub version_minor: u16,
65 pub build: u32,
66 pub timestamp: i32,
67 pub game_settings: GameSettings,
68 pub replay: Replay,
69}
70
71#[wasm_bindgen(getter_with_clone)]
72#[derive(Clone)]
73pub struct GameSettings {
74 pub dataset_ref: u32,
75 pub difficulty: u32,
76 pub selected_map_id: u32,
77 pub resolved_map_id: u32,
78 pub reveal_map: u32,
79 pub victory_type_id: u32,
80 pub starting_resources_id: u32,
81 pub starting_age_id: u32,
82 pub ending_age_id: u32,
83 pub game_type: u32,
84 pub speed: f32,
85 pub treaty_length: u32,
86 pub population_limit: u32,
87 pub n_players: u32,
88 pub victory_amount: i32,
89 pub trade_enabled: bool,
90 pub team_bonus_disabled: bool,
91 pub random_positions: bool,
92 pub all_techs: bool,
93 pub num_starting_units: u8,
94 pub lock_teams: bool,
95 pub lock_speed: bool,
96 pub multiplayer: bool,
97 pub cheats: bool,
98 pub record_game: bool,
99 pub shared_exploration: bool,
100 pub team_positions: bool,
101 pub sub_game_mode: u32,
102 pub battle_royale_time: u32,
103 pub handicap: bool,
104 pub fog_of_war: bool,
105 pub ranked: bool,
106 pub allow_specs: bool,
107 pub lobby_visibility: u32,
108 pub hidden_civs: bool,
109 pub matchmaking: bool,
110 pub spec_delay: u32,
111 pub scenario_civ: bool,
112 pub rms_strings: Vec<String>,
113 pub lobby_name: String,
114 pub modded_dataset: String,
115}
116
117impl From<&aoe2rec::summary::SavegameSummary<'_>> for GameSettings {
118 fn from(summary: &aoe2rec::summary::SavegameSummary) -> GameSettings {
119 let gs = summary.header.game_settings;
120 let map_strings: Vec<String> = gs
121 .rms_strings
122 .iter()
123 .map(|map_str| map_str.clone().into())
124 .collect();
125 GameSettings {
126 dataset_ref: gs.dataset_ref,
127 difficulty: gs.difficulty,
128 selected_map_id: gs.selected_map_id,
129 resolved_map_id: gs.resolved_map_id,
130 reveal_map: gs.reveal_map,
131 victory_type_id: gs.victory_type_id,
132 starting_resources_id: gs.starting_resources_id,
133 starting_age_id: gs.starting_age_id,
134 ending_age_id: gs.ending_age_id,
135 game_type: gs.game_type,
136 speed: gs.speed,
137 treaty_length: gs.treaty_length,
138 population_limit: gs.population_limit,
139 n_players: gs.n_players,
140 victory_amount: gs.victory_amount,
141 trade_enabled: gs.trade_enabled.into(),
142 team_bonus_disabled: gs.team_bonus_disabled.into(),
143 random_positions: gs.random_positions.into(),
144 all_techs: gs.all_techs.into(),
145 num_starting_units: gs.num_starting_units,
146 lock_teams: gs.lock_teams.into(),
147 lock_speed: gs.lock_speed.into(),
148 multiplayer: gs.multiplayer.into(),
149 cheats: gs.cheats.into(),
150 record_game: gs.record_game.into(),
151 shared_exploration: gs.shared_exploration.into(),
152 team_positions: gs.team_positions.into(),
153 sub_game_mode: gs.sub_game_mode,
154 battle_royale_time: gs.battle_royale_time,
155 handicap: gs.handicap.into(),
156 fog_of_war: gs.fog_of_war.into(),
157 ranked: gs.ranked.into(),
158 allow_specs: gs.allow_specs.into(),
159 lobby_visibility: gs.lobby_visibility,
160 hidden_civs: gs.hidden_civs.into(),
161 matchmaking: gs.matchmaking.into(),
162 spec_delay: gs.spec_delay,
163 scenario_civ: gs.scenario_civ.into(),
164 rms_strings: map_strings,
165 lobby_name: gs.lobby_name.clone().into(),
166 modded_dataset: gs.modded_dataset.clone().into(),
167 }
168 }
169}
170
171#[wasm_bindgen(getter_with_clone)]
172#[derive(Clone)]
173pub struct Replay {
174 pub old_time: u32,
175 pub world_time: u32,
176 pub old_world_time: u32,
177 pub game_speed_id: u32,
178 pub world_time_delta_seconds: u32,
179 pub timer: f32,
180 pub game_speed: f32,
181 pub temp_pause: bool,
182 pub random_seed: u32,
183 pub random_seed_2: u32,
184 pub rec_player: u16,
185 pub num_players: u8,
186 pub instant_build: bool,
187 pub cheats_enabled: bool,
188 pub game_mode: u16,
189}
190
191impl From<&aoe2rec::summary::SavegameSummary<'_>> for Replay {
192 fn from(summary: &aoe2rec::summary::SavegameSummary) -> Replay {
193 let r = summary.header.replay;
194 Replay {
195 old_time: r.old_time,
196 world_time: r.world_time,
197 old_world_time: r.old_world_time,
198 game_speed_id: r.game_speed_id,
199 world_time_delta_seconds: r.world_time_delta_seconds,
200 timer: r.timer,
201 game_speed: r.game_speed,
202 temp_pause: r.temp_pause.into(),
203 random_seed: r.random_seed,
204 random_seed_2: r.random_seed_2,
205 rec_player: r.rec_player,
206 num_players: r.num_players,
207 instant_build: r.instant_build.into(),
208 cheats_enabled: r.cheats_enabled.into(),
209 game_mode: r.game_mode,
210 }
211 }
212}
213
214#[wasm_bindgen(getter_with_clone)]
215#[derive(Clone)]
216pub struct Team {
217 pub winner: bool,
218 pub players: Vec<Player>,
219}
220
221impl From<&aoe2rec::summary::GameTeam<'_>> for Team {
222 fn from(team: &aoe2rec::summary::GameTeam<'_>) -> Self {
223 Self {
224 winner: team.players.iter().any(|player| !player.resigned),
225 players: team.players.iter().map(|player| player.into()).collect(),
226 }
227 }
228}
229
230#[wasm_bindgen(getter_with_clone)]
231#[derive(Clone)]
232pub struct Player {
233 pub name: String,
234 pub color_id: i32,
235 pub selected_color: u8,
236 pub selected_team_id: u8,
237 pub resolved_team_id: u8,
238 pub civ_id: u32,
239 pub custom_civ_ids: Vec<u32>,
240 pub player_type: u32,
241 pub profile_id: i32,
242 pub player_number: i32,
243 pub prefer_random: bool,
244 pub resigned: bool,
245}
246
247impl From<&aoe2rec::summary::TeamPlayer<'_>> for Player {
248 fn from(player: &aoe2rec::summary::TeamPlayer<'_>) -> Self {
249 Self {
250 name: player.info.name.clone().into(),
251 color_id: player.info.color_id,
252 selected_color: player.info.selected_color,
253 selected_team_id: player.info.selected_team_id,
254 resolved_team_id: player.info.resolved_team_id,
255 civ_id: player.info.civ_id,
256 custom_civ_ids: player.info.custom_civ_ids.clone(),
257 player_type: player.info.player_type,
258 profile_id: player.info.profile_id,
259 player_number: player.info.player_number,
260 prefer_random: player.info.prefer_random.into(),
261 resigned: player.resigned,
262 }
263 }
264}