1use std::io::{self, Read, Seek};
2
3use crate::reader::BigEndianReader;
4
5use super::types::{
6 KILL_TYPE_COUNT, PC_STAT_COUNT, PERK_COUNT, SAVEABLE_STAT_COUNT, SKILL_COUNT,
7 TAGGED_SKILL_COUNT,
8};
9use crate::object::GameObject;
10
11pub fn parse_player_combat_id<R: Read + Seek>(r: &mut BigEndianReader<R>) -> io::Result<i32> {
14 r.read_i32()
15}
16
17pub struct GlobalVarsSection {
20 pub global_vars: Vec<i32>,
21 pub water_movie_played: bool,
22}
23
24pub fn parse_game_global_vars<R: Read + Seek>(
30 r: &mut BigEndianReader<R>,
31) -> io::Result<GlobalVarsSection> {
32 let start_pos = r.position()?;
33
34 let detected_n = detect_global_var_count(r, start_pos)?;
36
37 r.seek_to(start_pos)?;
39 let global_vars = r.read_i32_vec(detected_n)?;
40 let water_flag = r.read_u8()?;
41
42 Ok(GlobalVarsSection {
43 global_vars,
44 water_movie_played: water_flag != 0,
45 })
46}
47
48fn detect_global_var_count<R: Read + Seek>(
50 r: &mut BigEndianReader<R>,
51 handler2_start: u64,
52) -> io::Result<usize> {
53 for n in 100..2000 {
54 let handler3_pos = handler2_start + (n * 4) as u64 + 1;
56 r.seek_to(handler3_pos)?;
57
58 if let Ok(file_count) = r.read_i32()
59 && file_count > 0
60 && file_count < 200
61 && let Ok(filename) = r.read_null_terminated_string(16)
62 && !filename.is_empty()
63 && filename.is_ascii()
64 && filename.to_uppercase().ends_with(".SAV")
65 {
66 return Ok(n);
67 }
68 }
69
70 Err(io::Error::new(
71 io::ErrorKind::InvalidData,
72 "could not detect global variable count",
73 ))
74}
75
76pub struct MapFileListSection {
79 pub map_files: Vec<String>,
80 pub automap_size: i32,
81}
82
83pub fn parse_map_file_list<R: Read + Seek>(
84 r: &mut BigEndianReader<R>,
85) -> io::Result<MapFileListSection> {
86 let file_count = r.read_i32()?;
87 let mut map_files = Vec::with_capacity(file_count as usize);
88
89 for _ in 0..file_count {
90 let filename = r.read_null_terminated_string(16)?;
91 map_files.push(filename);
92 }
93
94 let automap_size = r.read_i32()?;
95
96 Ok(MapFileListSection {
97 map_files,
98 automap_size,
99 })
100}
101
102pub struct PlayerObjectSection {
105 pub player_object: GameObject,
106 pub center_tile: i32,
107}
108
109pub fn parse_player_object<R: Read + Seek>(
110 r: &mut BigEndianReader<R>,
111) -> io::Result<PlayerObjectSection> {
112 let player_object = GameObject::parse(r)?;
113 let center_tile = r.read_i32()?;
114 Ok(PlayerObjectSection {
115 player_object,
116 center_tile,
117 })
118}
119
120#[derive(Debug)]
123pub struct CritterProtoData {
124 pub sneak_working: i32,
125 pub flags: i32,
126 pub base_stats: [i32; SAVEABLE_STAT_COUNT],
127 pub bonus_stats: [i32; SAVEABLE_STAT_COUNT],
128 pub skills: [i32; SKILL_COUNT],
129 pub body_type: i32,
130 pub experience: i32,
131 pub kill_type: i32,
132}
133
134pub fn parse_critter_proto<R: Read + Seek>(
135 r: &mut BigEndianReader<R>,
136) -> io::Result<CritterProtoData> {
137 let sneak_working = r.read_i32()?;
138 let flags = r.read_i32()?;
139 let base_stats = r.read_i32_array::<SAVEABLE_STAT_COUNT>()?;
140 let bonus_stats = r.read_i32_array::<SAVEABLE_STAT_COUNT>()?;
141 let skills = r.read_i32_array::<SKILL_COUNT>()?;
142 let body_type = r.read_i32()?;
143 let experience = r.read_i32()?;
144 let kill_type = r.read_i32()?;
145
146 Ok(CritterProtoData {
147 sneak_working,
148 flags,
149 base_stats,
150 bonus_stats,
151 skills,
152 body_type,
153 experience,
154 kill_type,
155 })
156}
157
158pub fn parse_kill_counts<R: Read + Seek>(
161 r: &mut BigEndianReader<R>,
162) -> io::Result<[i32; KILL_TYPE_COUNT]> {
163 r.read_i32_array::<KILL_TYPE_COUNT>()
164}
165
166pub fn parse_tagged_skills<R: Read + Seek>(
169 r: &mut BigEndianReader<R>,
170) -> io::Result<[i32; TAGGED_SKILL_COUNT]> {
171 r.read_i32_array::<TAGGED_SKILL_COUNT>()
172}
173
174pub fn parse_perks<R: Read + Seek>(r: &mut BigEndianReader<R>) -> io::Result<[i32; PERK_COUNT]> {
177 r.read_i32_array::<PERK_COUNT>()
178}
179
180#[derive(Debug)]
183pub struct CombatState {
184 pub combat_state_flags: u32,
185 pub combat_data: Option<CombatData>,
186}
187
188#[derive(Debug)]
189pub struct CombatData {
190 pub turn_running: i32,
191 pub free_move: i32,
192 pub exps: i32,
193 pub list_com: i32,
194 pub list_noncom: i32,
195 pub list_total: i32,
196 pub dude_cid: i32,
197 pub combatant_cids: Vec<i32>,
198}
199
200pub fn parse_combat_state<R: Read + Seek>(r: &mut BigEndianReader<R>) -> io::Result<CombatState> {
201 let combat_state_flags = r.read_u32()?;
202
203 if (combat_state_flags & 0x01) == 0 {
205 return Ok(CombatState {
206 combat_state_flags,
207 combat_data: None,
208 });
209 }
210
211 let turn_running = r.read_i32()?;
212 let free_move = r.read_i32()?;
213 let exps = r.read_i32()?;
214 let list_com = r.read_i32()?;
215 let list_noncom = r.read_i32()?;
216 let list_total = r.read_i32()?;
217 let dude_cid = r.read_i32()?;
218 let combatant_cids = r.read_i32_vec(list_total as usize)?;
219
220 Ok(CombatState {
221 combat_state_flags,
222 combat_data: Some(CombatData {
223 turn_running,
224 free_move,
225 exps,
226 list_com,
227 list_noncom,
228 list_total,
229 dude_cid,
230 combatant_cids,
231 }),
232 })
233}
234
235#[derive(Debug)]
238pub struct PcStats {
239 pub unspent_skill_points: i32,
240 pub level: i32,
241 pub experience: i32,
242 pub reputation: i32,
243 pub karma: i32,
244}
245
246pub fn parse_pc_stats<R: Read + Seek>(r: &mut BigEndianReader<R>) -> io::Result<PcStats> {
247 let stats = r.read_i32_array::<PC_STAT_COUNT>()?;
248 Ok(PcStats {
249 unspent_skill_points: stats[0],
250 level: stats[1],
251 experience: stats[2],
252 reputation: stats[3],
253 karma: stats[4],
254 })
255}
256
257pub fn skip_event_queue<R: Read + Seek>(r: &mut BigEndianReader<R>) -> io::Result<()> {
261 let count = r.read_i32()?;
262 if !(0..=10_000).contains(&count) {
263 return Err(io::Error::new(
264 io::ErrorKind::InvalidData,
265 format!("invalid event queue count: {count}"),
266 ));
267 }
268
269 for _ in 0..count {
270 let _time = r.read_i32()?;
272 let event_type = r.read_i32()?;
273 let _object_id = r.read_i32()?;
274
275 let extra_bytes: u64 = match event_type {
277 0 => 12, 1 => 0, 2 => 4, 3 => 8, 4 => 0, 5 => 0, 6 => 0, _ => {
285 return Err(io::Error::new(
286 io::ErrorKind::InvalidData,
287 format!("unknown event type: {event_type}"),
288 ));
289 }
290 };
291 r.skip(extra_bytes)?;
292 }
293
294 Ok(())
295}
296
297pub fn parse_traits<R: Read + Seek>(r: &mut BigEndianReader<R>) -> io::Result<[i32; 2]> {
300 let trait1 = r.read_i32()?;
301 let trait2 = r.read_i32()?;
302 if !is_trait_value_valid(trait1) || !is_trait_value_valid(trait2) {
303 return Err(io::Error::new(
304 io::ErrorKind::InvalidData,
305 format!("invalid trait values: [{trait1}, {trait2}]"),
306 ));
307 }
308 Ok([trait1, trait2])
309}
310
311fn is_trait_value_valid(v: i32) -> bool {
312 v == -1 || (0..16).contains(&v)
313}