comparison formats/src/th06/ecl.rs @ 757:21b186be2590

Split the Rust version into multiple crates.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Tue, 05 Jan 2021 02:16:32 +0100
parents src/th06/ecl.rs@fc937d93a57c
children
comparison
equal deleted inserted replaced
756:4d91790cf8ab 757:21b186be2590
1 //! ECL enemy script format support.
2
3 use nom::{
4 IResult,
5 number::complete::{le_u8, le_u16, le_u32, le_i16, le_i32, le_f32},
6 sequence::tuple,
7 multi::{count, many0},
8 error::ErrorKind,
9 Err,
10 };
11 use encoding_rs::SHIFT_JIS;
12 use bitflags::bitflags;
13
14 bitflags! {
15 /// Bit flags describing the current difficulty level.
16 pub struct Rank: u16 {
17 /// Easy mode.
18 const EASY = 0x100;
19
20 /// Normal mode.
21 const NORMAL = 0x200;
22
23 /// Hard mode.
24 const HARD = 0x400;
25
26 /// Lunatic mode.
27 const LUNATIC = 0x800;
28
29 /// Any or all modes.
30 const ALL = 0xff00;
31 }
32 }
33
34 impl std::str::FromStr for Rank {
35 type Err = String;
36
37 fn from_str(s: &str) -> Result<Rank, Self::Err> {
38 Ok(match s {
39 "easy" => Rank::EASY,
40 "normal" => Rank::NORMAL,
41 "hard" => Rank::HARD,
42 "lunatic" => Rank::LUNATIC,
43 _ => return Err(format!("unknown rank {}", s))
44 })
45 }
46 }
47
48 /// A single instruction, part of a `Script`.
49 #[derive(Debug, Clone)]
50 pub struct CallSub {
51 /// Time at which this instruction will be called.
52 pub time: i32,
53
54 /// The difficulty level(s) this instruction will be called at.
55 pub rank_mask: Rank,
56
57 /// TODO
58 pub param_mask: u16,
59
60 /// The instruction to call.
61 pub instr: SubInstruction,
62 }
63
64 impl CallSub {
65 /// Create a new instruction call.
66 pub fn new(time: i32, rank_mask: Rank, instr: SubInstruction) -> CallSub {
67 CallSub {
68 time,
69 rank_mask,
70 param_mask: 0,
71 instr,
72 }
73 }
74 }
75
76 /// Script driving an animation.
77 #[derive(Debug, Clone)]
78 pub struct Sub {
79 /// List of instructions in this script.
80 pub instructions: Vec<CallSub>,
81 }
82
83 /// A single instruction, part of a `Script`.
84 #[derive(Debug, Clone)]
85 pub struct CallMain {
86 /// Time at which this instruction will be called.
87 pub time: u16,
88
89 /// Subroutine to call for this enemy.
90 pub sub: u16,
91
92 /// The instruction to call.
93 pub instr: MainInstruction,
94 }
95
96 /// Script driving an animation.
97 #[derive(Debug, Clone)]
98 pub struct Main {
99 /// List of instructions in this script.
100 pub instructions: Vec<CallMain>,
101 }
102
103 /// Main struct of the ANM0 animation format.
104 #[derive(Debug, Clone)]
105 pub struct Ecl {
106 /// A list of subs.
107 pub subs: Vec<Sub>,
108
109 /// A list of mains.
110 pub mains: Vec<Main>,
111 }
112
113 impl Ecl {
114 /// Parse a slice of bytes into an `Ecl` struct.
115 pub fn from_slice(data: &[u8]) -> IResult<&[u8], Ecl> {
116 parse_ecl(data)
117 }
118 }
119
120 macro_rules! declare_main_instructions {
121 ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ident),*)),*,) => {
122 /// Available instructions in an `Ecl`.
123 #[allow(missing_docs)]
124 #[derive(Debug, Clone, Copy)]
125 pub enum MainInstruction {
126 $(
127 $name($($arg_type),*)
128 ),*
129 }
130
131 fn parse_main_instruction_args(input: &[u8], opcode: u16) -> IResult<&[u8], MainInstruction> {
132 let mut i = &input[..];
133 let instr = match opcode {
134 $(
135 $opcode => {
136 $(
137 let (i2, $arg) = concat_idents!(le_, $arg_type)(i)?;
138 i = i2;
139 )*
140 MainInstruction::$name($($arg),*)
141 }
142 )*
143 _ => unreachable!()
144 };
145 Ok((i, instr))
146 }
147 };
148 }
149
150 /// Parse a SHIFT_JIS byte string of length 34 into a String.
151 #[allow(non_snake_case)]
152 pub fn le_String(i: &[u8]) -> IResult<&[u8], String> {
153 let data = i.splitn(2, |c| *c == b'\0').nth(0).unwrap();
154 let (string, _encoding, _replaced) = SHIFT_JIS.decode(data);
155 Ok((&i[34..], string.into_owned()))
156 }
157
158 macro_rules! declare_sub_instructions {
159 ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ident),*)),*,) => {
160 /// Available instructions in an `Ecl`.
161 #[allow(missing_docs)]
162 #[derive(Debug, Clone)]
163 pub enum SubInstruction {
164 $(
165 $name($($arg_type),*)
166 ),*
167 }
168
169 fn parse_sub_instruction_args(input: &[u8], opcode: u16) -> IResult<&[u8], SubInstruction> {
170 let mut i = &input[..];
171 let instr = match opcode {
172 $(
173 $opcode => {
174 $(
175 let (i2, $arg) = concat_idents!(le_, $arg_type)(i)?;
176 i = i2;
177 )*
178 SubInstruction::$name($($arg),*)
179 }
180 )*
181 _ => unreachable!()
182 };
183 Ok((i, instr))
184 }
185 };
186 }
187
188 declare_main_instructions!{
189 0 => fn SpawnEnemy(x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: u32),
190 2 => fn SpawnEnemyMirrored(x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: u32),
191 4 => fn SpawnEnemyRandom(x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: u32),
192 6 => fn SpawnEnemyMirroredRandom(x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: u32),
193 8 => fn CallMessage(),
194 9 => fn WaitMessage(),
195 10 => fn ResumeEcl(x: f32, y: f32),
196 12 => fn WaitForBossDeath(),
197 }
198
199 declare_sub_instructions!{
200 0 => fn Noop(),
201 1 => fn Destroy(unused: u32),
202 2 => fn RelativeJump(frame: i32, ip: i32),
203 3 => fn RelativeJumpEx(frame: i32, ip: i32, variable_id: i32),
204 4 => fn SetInt(var: i32, value: i32),
205 5 => fn SetFloat(var: i32, value: f32),
206 6 => fn SetRandomInt(var: i32, max: i32),
207 7 => fn SetRandomIntMin(var: i32, max: i32, min: i32),
208 8 => fn SetRandomFloat(var: i32, max: f32),
209 9 => fn SetRandomFloatMin(var: i32, amplitude: f32, min: f32),
210 10 => fn StoreX(var: i32),
211 11 => fn StoreY(var: i32),
212 12 => fn StoreZ(var: i32),
213 13 => fn AddInt(var: i32, a: i32, b: i32),
214 14 => fn SubstractInt(var: i32, a: i32, b: i32),
215 15 => fn MultiplyInt(var: i32, a: i32, b: i32),
216 16 => fn DivideInt(var: i32, a: i32, b: i32),
217 17 => fn ModuloInt(var: i32, a: i32, b: i32),
218 18 => fn Increment(var: i32),
219 19 => fn Decrement(var: i32),
220 20 => fn AddFloat(var: i32, a: f32, b: f32),
221 21 => fn SubstractFloat(var: i32, a: f32, b: f32),
222 22 => fn MultiplyFloat(var: i32, a: f32, b: f32),
223 23 => fn DivideFloat(var: i32, a: f32, b: f32),
224 24 => fn ModuloFloat(var: i32, a: f32, b: f32),
225 25 => fn GetDirection(var: i32, x1: f32, y1: f32, x2: f32, y2: f32),
226 26 => fn FloatToUnitCircle(var: i32),
227 27 => fn CompareInts(a: i32, b: i32),
228 28 => fn CompareFloats(a: f32, b: f32),
229 29 => fn RelativeJumpIfLowerThan(frame: i32, ip: i32),
230 30 => fn RelativeJumpIfLowerOrEqual(frame: i32, ip: i32),
231 31 => fn RelativeJumpIfEqual(frame: i32, ip: i32),
232 32 => fn RelativeJumpIfGreaterThan(frame: i32, ip: i32),
233 33 => fn RelativeJumpIfGreaterOrEqual(frame: i32, ip: i32),
234 34 => fn RelativeJumpIfNotEqual(frame: i32, ip: i32),
235 35 => fn Call(sub: i32, param1: i32, param2: f32),
236 36 => fn Return(),
237 37 => fn CallIfSuperior(sub: i32, param1: i32, param2: f32, a: i32, b: i32),
238 38 => fn CallIfSuperiorOrEqual(sub: i32, param1: i32, param2: f32, a: i32, b: i32),
239 39 => fn CallIfEqual(sub: i32, param1: i32, param2: f32, a: i32, b: i32),
240 40 => fn CallIfInferior(sub: i32, param1: i32, param2: f32, a: i32, b: i32),
241 41 => fn CallIfInferiorOrEqual(sub: i32, param1: i32, param2: f32, a: i32, b: i32),
242 42 => fn CallIfNotEqual(sub: i32, param1: i32, param2: f32, a: i32, b: i32),
243 43 => fn SetPosition(x: f32, y: f32, z: f32),
244 45 => fn SetAngleAndSpeed(angle: f32, speed: f32),
245 46 => fn SetRotationSpeed(speed: f32),
246 47 => fn SetSpeed(speed: f32),
247 48 => fn SetAcceleration(acceleration: f32),
248 49 => fn SetRandomAngle(min: f32, max: f32),
249 50 => fn SetRandomAngleEx(min: f32, max: f32),
250 51 => fn TargetPlayer(angle: f32, speed: f32),
251 52 => fn MoveInDecel(duration: i32, angle: f32, speed: f32),
252 56 => fn MoveToLinear(duration: i32, x: f32, y: f32, z: f32),
253 57 => fn MoveToDecel(duration: i32, x: f32, y: f32, z: f32),
254 59 => fn MoveToAccel(duration: i32, x: f32, y: f32, z: f32),
255 61 => fn StopIn(duration: i32),
256 63 => fn StopInAccel(duration: i32),
257 65 => fn SetScreenBox(xmin: f32, ymin: f32, xmax: f32, ymax: f32),
258 66 => fn ClearScreenBox(),
259 67 => fn SetBulletAttributes1(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: u32),
260 68 => fn SetBulletAttributes2(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: u32),
261 69 => fn SetBulletAttributes3(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: u32),
262 70 => fn SetBulletAttributes4(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: u32),
263 71 => fn SetBulletAttributes5(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: u32),
264 74 => fn SetBulletAttributes6(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: u32),
265 75 => fn SetBulletAttributes7(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: u32),
266 76 => fn SetBulletInterval(interval: i32),
267 77 => fn SetBulletIntervalEx(interval: i32),
268 78 => fn DelayAttack(),
269 79 => fn NoDelayAttack(),
270 81 => fn SetBulletLaunchOffset(x: f32, y: f32, z: f32),
271 82 => fn SetExtendedBulletAttributes(a: i32, b: i32, c: i32, d: i32, e: f32, f: f32, g: f32, h: f32),
272 83 => fn ChangeBulletsInStarBonus(),
273 // TODO: Found in stage 4 onward.
274 84 => fn SetBulletSound(sound: i32),
275 85 => fn NewLaser(laser_type: i16, sprite_idx_offset: i16, angle: f32, speed: f32, start_offset: f32, end_offset: f32, max_length: f32, width: f32, start_duration: i32, duration: i32, end_duration: i32, grazing_delay: i32, grazing_extra_duration: i32, UNK1: i32),
276 86 => fn NewLaserTowardsPlayer(laser_type: i16, sprite_idx_offset: i16, angle: f32, speed: f32, start_offset: f32, end_offset: f32, max_length: f32, width: f32, start_duration: i32, duration: i32, end_duration: i32, grazing_delay: i32, grazing_extra_duration: i32, UNK1: i32),
277 87 => fn SetUpcomingLaserId(id: u32),
278 88 => fn AlterLaserAngle(id: u32, delta: f32),
279 90 => fn RepositionLaser(id: u32, ox: f32, oy: f32, oz: f32),
280 91 => fn LaserSetCompare(id: u32),
281 92 => fn CancelLaser(id: u32),
282 93 => fn SetSpellcard(face: i16, number: i16, name: String),
283 94 => fn EndSpellcard(),
284 95 => fn SpawnEnemy(sub: i32, x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: i32),
285 96 => fn KillAllEnemies(),
286 97 => fn SetAnim(script: i32),
287 98 => fn SetMultipleAnims(default: i16, end_left: i16, end_right: i16, left: i16, right: i16, _unused: i16),
288 99 => fn SetAuxAnm(number: i32, script: i32),
289 100 => fn SetDeathAnim(sprite_index: i32),
290 101 => fn SetBossMode(value: i32),
291 102 => fn CreateSquares(UNK1: i32, UNK2: f32, UNK3: f32, UNK4: f32, UNK5: f32),
292 103 => fn SetHitbox(width: f32, height: f32, depth: f32),
293 104 => fn SetCollidable(collidable: i32),
294 105 => fn SetDamageable(damageable: i32),
295 106 => fn PlaySound(index: i32),
296 107 => fn SetDeathFlags(death_flags: u32),
297 108 => fn SetDeathCallback(sub: i32),
298 109 => fn MemoryWriteInt(value: i32, index: i32),
299 111 => fn SetLife(life: i32),
300 112 => fn SetElapsedTime(frame: i32),
301 113 => fn SetLowLifeTrigger(trigger: i32),
302 114 => fn SetLowLifeCallback(sub: i32),
303 115 => fn SetTimeout(timeout: i32),
304 116 => fn SetTimeoutCallback(sub: i32),
305 117 => fn SetTouchable(touchable: i32),
306 118 => fn DropParticles(anim: i32, number: u32, r: u8, g: u8, b: u8, a: u8),
307 119 => fn DropBonus(number: i32),
308 120 => fn SetAutomaticOrientation(automatic: i32),
309 121 => fn CallSpecialFunction(function: i32, argument: i32),
310 122 => fn SetSpecialFunctionCallback(function: i32),
311 123 => fn SkipFrames(frames: i32),
312 124 => fn DropSpecificBonus(type_: i32),
313 // TODO: Found in stage 3.
314 125 => fn UNK_ins125(),
315 126 => fn SetRemainingLives(lives: i32),
316 // TODO: Found in stage 4.
317 127 => fn UNK_ins127(UNK1: i32),
318 128 => fn Interrupt(event: i32),
319 129 => fn InterruptAux(number: i32, event: i32),
320 // TODO: Found in stage 4.
321 130 => fn UNK_ins130(UNK1: i32),
322 131 => fn SetDifficultyCoeffs(speed_a: f32, speed_b: f32, nb_a: i32, nb_b: i32, shots_a: i32, shots_b: i32),
323 132 => fn SetInvisible(invisible: i32),
324 133 => fn CopyCallbacks(),
325 // TODO: Found in stage 4.
326 134 => fn UNK_ins134(),
327 135 => fn EnableSpellcardBonus(UNK1: i32),
328 }
329
330 fn parse_sub_instruction(input: &[u8]) -> IResult<&[u8], CallSub> {
331 let i = &input[..];
332 let (i, (time, opcode)) = tuple((le_i32, le_u16))(i)?;
333 if time == -1 || opcode == 0xffff {
334 return Err(Err::Error(nom::error::Error::new(i, ErrorKind::Eof)));
335 }
336
337 let (i, (size, rank_mask, param_mask)) = tuple((le_u16, le_u16, le_u16))(i)?;
338 let rank_mask = Rank::from_bits(rank_mask).unwrap();
339 let (i, instr) = parse_sub_instruction_args(i, opcode)?;
340 assert_eq!(input.len() - i.len(), size as usize);
341 let call = CallSub { time, rank_mask, param_mask, instr };
342 Ok((i, call))
343 }
344
345 fn parse_sub(i: &[u8]) -> IResult<&[u8], Sub> {
346 let (i, instructions) = many0(parse_sub_instruction)(i)?;
347 let sub = Sub { instructions };
348 Ok((i, sub))
349 }
350
351 fn parse_main_instruction(input: &[u8]) -> IResult<&[u8], CallMain> {
352 let i = &input[..];
353 let (i, (time, sub)) = tuple((le_u16, le_u16))(i)?;
354 if time == 0xffff && sub == 4 {
355 return Err(Err::Error(nom::error::Error::new(i, ErrorKind::Eof)));
356 }
357
358 let (i, (opcode, size)) = tuple((le_u16, le_u16))(i)?;
359 let size = size as usize;
360 let (i, instr) = parse_main_instruction_args(i, opcode)?;
361 assert_eq!(input.len() - i.len(), size as usize);
362 let call = CallMain { time, sub, instr };
363 Ok((i, call))
364 }
365
366 fn parse_main(i: &[u8]) -> IResult<&[u8], Main> {
367 let (i, instructions) = many0(parse_main_instruction)(i)?;
368 let main = Main { instructions };
369 Ok((i, main))
370 }
371
372 fn parse_ecl(input: &[u8]) -> IResult<&[u8], Ecl> {
373 let i = input;
374
375 let (i, (sub_count, main_count)) = tuple((le_u16, le_u16))(i)?;
376 let sub_count = sub_count as usize;
377
378 if main_count != 0 {
379 // TODO: use a better error.
380 return Err(Err::Error(nom::error::Error::new(i, ErrorKind::Eof)));
381 }
382
383 let (_, (main_offsets, sub_offsets)) = tuple((
384 count(le_u32, 3),
385 count(le_u32, sub_count),
386 ))(i)?;
387
388 // Read all subs.
389 let mut subs = Vec::new();
390 for offset in sub_offsets.into_iter().map(|offset| offset as usize) {
391 let (_, sub) = parse_sub(&input[offset..])?;
392 subs.push(sub);
393 }
394
395 // Read all mains (always a single one atm).
396 let mut mains = Vec::new();
397 for offset in main_offsets.into_iter().map(|offset| offset as usize) {
398 if offset == 0 {
399 break;
400 }
401 let (_, main) = parse_main(&input[offset..])?;
402 mains.push(main);
403 }
404
405 let ecl = Ecl {
406 subs,
407 mains,
408 };
409 Ok((b"", ecl))
410 }
411
412 #[cfg(test)]
413 mod tests {
414 use super::*;
415 use std::io::{self, Read};
416 use std::fs::File;
417
418 #[test]
419 fn ecl() {
420 let file = File::open("EoSD/ST/ecldata1.ecl").unwrap();
421 let mut file = io::BufReader::new(file);
422 let mut buf = vec![];
423 file.read_to_end(&mut buf).unwrap();
424 let (_, ecl) = Ecl::from_slice(&buf).unwrap();
425 assert_eq!(ecl.subs.len(), 24);
426 assert_eq!(ecl.mains.len(), 1);
427 }
428 }