|
1 use crate::core::types::{GameCfg, TeamInfo}; |
|
2 |
|
3 use std::{ |
|
4 fs, |
|
5 io::{self, BufReader, Read, Write}, |
|
6 str::FromStr, |
|
7 }; |
|
8 |
|
9 struct Demo { |
|
10 teams: Vec<TeamInfo>, |
|
11 config: Vec<GameCfg>, |
|
12 messages: Vec<String>, |
|
13 } |
|
14 |
|
15 impl Demo { |
|
16 fn save(&self, file: String) -> io::Result<()> { |
|
17 Ok(unimplemented!()) |
|
18 } |
|
19 |
|
20 fn load(file: String) -> io::Result<Self> { |
|
21 Ok(unimplemented!()) |
|
22 } |
|
23 |
|
24 fn load_hwd(file: String) -> io::Result<Self> { |
|
25 let datum = fs::File::open(file)?; |
|
26 let mut reader = io::BufReader::new(datum); |
|
27 |
|
28 #[inline] |
|
29 fn error<T>(cause: &str) -> io::Result<T> { |
|
30 Err(io::Error::new(io::ErrorKind::InvalidData, cause)) |
|
31 } |
|
32 |
|
33 fn read_command<'a>( |
|
34 reader: &mut BufReader<fs::File>, |
|
35 buffer: &'a mut [u8], |
|
36 ) -> io::Result<Option<&'a str>> { |
|
37 use io::BufRead; |
|
38 |
|
39 let mut size = [0u8; 1]; |
|
40 if reader.read(&mut size)? == 0 { |
|
41 Ok(None) |
|
42 } else { |
|
43 let text = &mut buffer[0..size[0] as _]; |
|
44 |
|
45 if reader.read(text)? < text.len() { |
|
46 Err(io::Error::new( |
|
47 io::ErrorKind::UnexpectedEof, |
|
48 "Incomplete command", |
|
49 )) |
|
50 } else { |
|
51 std::str::from_utf8(text).map(Some).map_err(|e| { |
|
52 io::Error::new(io::ErrorKind::InvalidInput, "The string is not UTF8") |
|
53 }) |
|
54 } |
|
55 } |
|
56 } |
|
57 |
|
58 fn get_script_name(arg: &str) -> io::Result<String> { |
|
59 const PREFIX: &str = "Scripts/Multiplayer/"; |
|
60 const SUFFIX: &str = ".lua"; |
|
61 if arg.starts_with(PREFIX) && arg.ends_with(SUFFIX) { |
|
62 let script = arg[PREFIX.len()..arg.len() - SUFFIX.len()].to_string(); |
|
63 script.replace('_', " "); |
|
64 Ok(script) |
|
65 } else { |
|
66 error("Script is not multiplayer") |
|
67 } |
|
68 } |
|
69 |
|
70 fn get_game_flags(arg: &str) -> io::Result<Vec<String>> { |
|
71 const FLAGS: &[u32] = &[ |
|
72 0x0000_1000, |
|
73 0x0000_0010, |
|
74 0x0000_0004, |
|
75 0x0000_0008, |
|
76 0x0000_0020, |
|
77 0x0000_0040, |
|
78 0x0000_0080, |
|
79 0x0000_0100, |
|
80 0x0000_0200, |
|
81 0x0000_0400, |
|
82 0x0000_0800, |
|
83 0x0000_2000, |
|
84 0x0000_4000, |
|
85 0x0000_8000, |
|
86 0x0001_0000, |
|
87 0x0002_0000, |
|
88 0x0004_0000, |
|
89 0x0008_0000, |
|
90 0x0010_0000, |
|
91 0x0020_0000, |
|
92 0x0040_0000, |
|
93 0x0080_0000, |
|
94 0x0100_0000, |
|
95 0x0200_0000, |
|
96 0x0400_0000, |
|
97 ]; |
|
98 |
|
99 let flags = u32::from_str(arg).unwrap_or_default(); |
|
100 let game_flags = FLAGS |
|
101 .iter() |
|
102 .map(|flag| (flag & flags != 0).to_string()) |
|
103 .collect(); |
|
104 |
|
105 Ok(game_flags) |
|
106 } |
|
107 |
|
108 let mut config = Vec::new(); |
|
109 let mut buffer = [0u8; u8::max_value() as _]; |
|
110 |
|
111 let mut game_flags = vec![]; |
|
112 let mut scheme_properties: Vec<_> = [ |
|
113 "1", "1000", "100", "1", "1", "1000", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", |
|
114 "1", "", |
|
115 ] |
|
116 .iter() |
|
117 .map(|p| p.to_string()) |
|
118 .collect(); |
|
119 const SCHEME_PROPERTY_NAMES: &[&str] = &[ |
|
120 "$damagepct", |
|
121 "$turntime", |
|
122 "", |
|
123 "$sd_turns", |
|
124 "$casefreq", |
|
125 "$minestime", |
|
126 "$minesnum", |
|
127 "$minedudpct", |
|
128 "$explosives", |
|
129 "$airmines", |
|
130 "$healthprob", |
|
131 "$hcaseamount", |
|
132 "$waterrise", |
|
133 "$healthdec", |
|
134 "$ropepct", |
|
135 "$getawaytime", |
|
136 "$worldedge", |
|
137 ]; |
|
138 const AMMO_PROPERTY_NAMES: &[&str] = &["eammloadt", "eammprob", "eammdelay", "eammreinf"]; |
|
139 let mut ammo_settings = vec![String::new(); AMMO_PROPERTY_NAMES.len()]; |
|
140 let mut teams = vec![]; |
|
141 let mut hog_index = 7usize; |
|
142 |
|
143 let mut messages = vec![]; |
|
144 |
|
145 while let Some(cmd) = read_command(&mut reader, &mut buffer)? { |
|
146 if let Some(index) = cmd.find(' ') { |
|
147 match cmd.chars().next().unwrap_or_default() { |
|
148 'T' => { |
|
149 if cmd != "TD" { |
|
150 let () = error("Not a demo file")?; |
|
151 } |
|
152 } |
|
153 'e' => { |
|
154 if let Some(index) = cmd.find(' ') { |
|
155 let (name, arg) = cmd.split_at(index); |
|
156 match name { |
|
157 "script" => config.push(GameCfg::Script(get_script_name(arg)?)), |
|
158 "map" => config.push(GameCfg::MapType(arg.to_string())), |
|
159 "theme" => config.push(GameCfg::Theme(arg.to_string())), |
|
160 "seed" => config.push(GameCfg::Seed(arg.to_string())), |
|
161 "$gmflags" => game_flags = get_game_flags(arg)?, |
|
162 "$scriptparam" => { |
|
163 *scheme_properties.last_mut().unwrap() = arg.to_string() |
|
164 } |
|
165 "$template_filter" => config.push(GameCfg::Template( |
|
166 u32::from_str(arg).unwrap_or_default(), |
|
167 )), |
|
168 "$feature_size" => config.push(GameCfg::FeatureSize( |
|
169 u32::from_str(arg).unwrap_or_default(), |
|
170 )), |
|
171 "$map_gen" => config.push(GameCfg::MapGenerator( |
|
172 u32::from_str(arg).unwrap_or_default(), |
|
173 )), |
|
174 "$maze_size" => config.push(GameCfg::MazeSize( |
|
175 u32::from_str(arg).unwrap_or_default(), |
|
176 )), |
|
177 "addteam" => { |
|
178 if let parts = arg.splitn(3, ' ').collect::<Vec<_>>() { |
|
179 let color = parts.get(1).unwrap_or(&"1"); |
|
180 let name = parts.get(2).unwrap_or(&"Unnamed"); |
|
181 teams.push(TeamInfo { |
|
182 color: (u32::from_str(color).unwrap_or(2113696) |
|
183 / 2113696 |
|
184 - 1) |
|
185 as u8, |
|
186 name: name.to_string(), |
|
187 ..TeamInfo::default() |
|
188 }) |
|
189 }; |
|
190 } |
|
191 "fort" => teams |
|
192 .last_mut() |
|
193 .iter_mut() |
|
194 .for_each(|t| t.fort = arg.to_string()), |
|
195 "grave" => teams |
|
196 .last_mut() |
|
197 .iter_mut() |
|
198 .for_each(|t| t.grave = arg.to_string()), |
|
199 "addhh" => { |
|
200 hog_index = (hog_index + 1) % 8; |
|
201 if let parts = arg.splitn(3, ' ').collect::<Vec<_>>() { |
|
202 let health = parts.get(1).unwrap_or(&"100"); |
|
203 teams.last_mut().iter_mut().for_each(|t| { |
|
204 if let Some(difficulty) = parts.get(0) { |
|
205 t.difficulty = |
|
206 u8::from_str(difficulty).unwrap_or(0); |
|
207 } |
|
208 if let Some(init_health) = parts.get(1) { |
|
209 scheme_properties[2] = init_health.to_string(); |
|
210 } |
|
211 t.hedgehogs_number = (hog_index + 1) as u8; |
|
212 t.hedgehogs[hog_index].name = |
|
213 parts.get(2).unwrap_or(&"Unnamed").to_string() |
|
214 }); |
|
215 } |
|
216 } |
|
217 "hat" => { |
|
218 teams |
|
219 .last_mut() |
|
220 .iter_mut() |
|
221 .for_each(|t| t.hedgehogs[hog_index].hat = arg.to_string()); |
|
222 } |
|
223 name => { |
|
224 if let Some(index) = |
|
225 SCHEME_PROPERTY_NAMES.iter().position(|n| *n == name) |
|
226 { |
|
227 scheme_properties[index] = arg.to_string(); |
|
228 } else if let Some(index) = |
|
229 AMMO_PROPERTY_NAMES.iter().position(|n| *n == name) |
|
230 { |
|
231 ammo_settings[index] = arg.to_string(); |
|
232 } |
|
233 } |
|
234 } |
|
235 } |
|
236 } |
|
237 '+' => {} |
|
238 _ => (), |
|
239 } |
|
240 } |
|
241 } |
|
242 |
|
243 game_flags.append(&mut scheme_properties); |
|
244 config.push(GameCfg::Scheme("ADHOG_SCHEME".to_string(), game_flags)); |
|
245 config.push(GameCfg::Ammo( |
|
246 "ADHOG_AMMO".to_string(), |
|
247 Some(ammo_settings.concat()), |
|
248 )); |
|
249 |
|
250 Ok(Demo { |
|
251 teams, |
|
252 config, |
|
253 messages, |
|
254 }) |
|
255 } |
|
256 } |