Mercurial > touhou
comparison interpreters/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_vm.rs@01915da33b99 |
children |
comparison
equal
deleted
inserted
replaced
756:4d91790cf8ab | 757:21b186be2590 |
---|---|
1 //! ECL runner. | |
2 | |
3 use touhou_formats::th06::ecl::{Ecl, SubInstruction}; | |
4 use crate::th06::enemy::{Enemy, Offset, BulletAttributes, Position}; | |
5 use touhou_utils::prng::Prng; | |
6 use std::cell::RefCell; | |
7 use std::rc::Rc; | |
8 | |
9 macro_rules! gen_SetBulletAttributes { | |
10 ($self:ident, $opcode:tt, $anim:ident, $sprite_index_offset:ident, $bullets_per_shot:ident, | |
11 $number_of_shots:ident, $speed:ident, $speed2:ident, $launch_angle:ident, $angle:ident, | |
12 $flags:ident) => {{ | |
13 let sprite_index_offset = $self.get_i32($sprite_index_offset as i32) as i16; | |
14 let bullets_per_shot = $self.get_i32($bullets_per_shot) as i16; | |
15 let number_of_shots = $self.get_i32($number_of_shots) as i16; | |
16 let speed = $self.get_f32($speed); | |
17 let speed2 = $self.get_f32($speed2); | |
18 let launch_angle = $self.get_f32($launch_angle); | |
19 let angle = $self.get_f32($angle); | |
20 | |
21 let mut enemy = $self.enemy.borrow_mut(); | |
22 enemy.set_bullet_attributes($opcode, $anim, sprite_index_offset, bullets_per_shot, | |
23 number_of_shots, speed, speed2, launch_angle, angle, $flags); | |
24 }}; | |
25 } | |
26 | |
27 #[derive(Clone, Default)] | |
28 struct StackFrame { | |
29 frame: i32, | |
30 ip: i32, | |
31 //ins122_callback: Option<Box<FnMut(Enemy)>>, | |
32 ints1: [i32; 4], | |
33 floats: [f32; 4], | |
34 ints2: [i32; 4], | |
35 comparison_reg: i32, | |
36 sub: u16, | |
37 } | |
38 | |
39 /// Interpreter for enemy scripts. | |
40 #[derive(Default)] | |
41 pub struct EclRunner { | |
42 /// XXX | |
43 pub enemy: Rc<RefCell<Enemy>>, | |
44 | |
45 ecl: Option<Ecl>, | |
46 /// XXX | |
47 pub running: bool, | |
48 frame: StackFrame, | |
49 // TODO: there are only 8 of these. | |
50 stack: Vec<StackFrame>, | |
51 } | |
52 | |
53 impl EclRunner { | |
54 /// Create a new ECL runner. | |
55 pub fn new(ecl: &Ecl, enemy: Rc<RefCell<Enemy>>, sub: u16) -> EclRunner { | |
56 let mut ecl_runner = EclRunner { | |
57 enemy, | |
58 // XXX: no clone. | |
59 ecl: Some(ecl.clone()), | |
60 running: true, | |
61 ..Default::default() | |
62 }; | |
63 ecl_runner.frame.sub = sub; | |
64 ecl_runner | |
65 } | |
66 | |
67 /// Advance the ECL of a single frame. | |
68 pub fn run_frame(&mut self) { | |
69 while self.running { | |
70 let ecl = self.ecl.clone().unwrap(); | |
71 let sub = &ecl.subs[self.frame.sub as usize]; | |
72 let call = match sub.instructions.get(self.frame.ip as usize) { | |
73 Some(call) => call, | |
74 None => { | |
75 self.running = false; | |
76 break; | |
77 } | |
78 }; | |
79 | |
80 if call.time > self.frame.frame { | |
81 break; | |
82 } | |
83 self.frame.ip += 1; | |
84 | |
85 let rank = self.enemy.borrow().get_rank(); | |
86 if (call.rank_mask & rank).is_empty() { | |
87 continue; | |
88 } | |
89 | |
90 if call.time == self.frame.frame { | |
91 self.run_instruction(call.instr.clone()); | |
92 } | |
93 } | |
94 self.frame.frame += 1; | |
95 } | |
96 | |
97 fn get_i32(&self, var: i32) -> i32 { | |
98 let enemy = self.enemy.borrow(); | |
99 match var { | |
100 -10001 => self.frame.ints1[0], | |
101 -10002 => self.frame.ints1[1], | |
102 -10003 => self.frame.ints1[2], | |
103 -10004 => self.frame.ints1[3], | |
104 -10005 => self.frame.floats[0] as i32, | |
105 -10006 => self.frame.floats[1] as i32, | |
106 -10007 => self.frame.floats[2] as i32, | |
107 -10008 => self.frame.floats[3] as i32, | |
108 -10009 => self.frame.ints2[0], | |
109 -10010 => self.frame.ints2[1], | |
110 -10011 => self.frame.ints2[2], | |
111 -10012 => self.frame.ints2[3], | |
112 -10013 => enemy.get_rank().bits() as i32, | |
113 -10014 => enemy.get_difficulty(), | |
114 -10015 => enemy.pos.x as i32, | |
115 -10016 => enemy.pos.y as i32, | |
116 -10017 => enemy.z as i32, | |
117 -10018 => unimplemented!(), | |
118 -10019 => unimplemented!(), | |
119 -10020 => unreachable!(), | |
120 -10021 => unimplemented!(), | |
121 -10022 => enemy.frame as i32, | |
122 -10023 => unreachable!(), | |
123 -10024 => enemy.life as i32, | |
124 -10025 => unimplemented!(), | |
125 _ => var | |
126 } | |
127 } | |
128 | |
129 fn get_f32(&self, var: f32) -> f32 { | |
130 let enemy = self.enemy.borrow(); | |
131 match var { | |
132 -10001.0 => self.frame.ints1[0] as f32, | |
133 -10002.0 => self.frame.ints1[1] as f32, | |
134 -10003.0 => self.frame.ints1[2] as f32, | |
135 -10004.0 => self.frame.ints1[3] as f32, | |
136 -10005.0 => self.frame.floats[0], | |
137 -10006.0 => self.frame.floats[1], | |
138 -10007.0 => self.frame.floats[2], | |
139 -10008.0 => self.frame.floats[3], | |
140 -10009.0 => self.frame.ints2[0] as f32, | |
141 -10010.0 => self.frame.ints2[1] as f32, | |
142 -10011.0 => self.frame.ints2[2] as f32, | |
143 -10012.0 => self.frame.ints2[3] as f32, | |
144 -10013.0 => enemy.get_rank().bits() as f32, | |
145 -10014.0 => enemy.get_difficulty() as f32, | |
146 -10015.0 => enemy.pos.x, | |
147 -10016.0 => enemy.pos.y, | |
148 -10017.0 => enemy.z, | |
149 -10018.0 => unimplemented!(), | |
150 -10019.0 => unimplemented!(), | |
151 -10020.0 => unreachable!(), | |
152 -10021.0 => unimplemented!(), | |
153 -10022.0 => enemy.frame as f32, | |
154 -10023.0 => unreachable!(), | |
155 -10024.0 => enemy.life as f32, | |
156 -10025.0 => unimplemented!(), | |
157 _ => var | |
158 } | |
159 } | |
160 | |
161 fn set_i32(&mut self, var: i32, value: i32) { | |
162 let mut enemy = self.enemy.borrow_mut(); | |
163 match var { | |
164 -10001 => self.frame.ints1[0] = value, | |
165 -10002 => self.frame.ints1[1] = value, | |
166 -10003 => self.frame.ints1[2] = value, | |
167 -10004 => self.frame.ints1[3] = value, | |
168 -10005 => unimplemented!(), | |
169 -10006 => unimplemented!(), | |
170 -10007 => unimplemented!(), | |
171 -10008 => unimplemented!(), | |
172 -10009 => self.frame.ints2[0] = value, | |
173 -10010 => self.frame.ints2[1] = value, | |
174 -10011 => self.frame.ints2[2] = value, | |
175 -10012 => self.frame.ints2[3] = value, | |
176 -10013 => unreachable!(), | |
177 -10014 => unreachable!(), | |
178 -10015 => unimplemented!(), | |
179 -10016 => unimplemented!(), | |
180 -10017 => unimplemented!(), | |
181 -10018 => unreachable!(), | |
182 -10019 => unreachable!(), | |
183 -10020 => unreachable!(), | |
184 -10021 => unreachable!(), | |
185 -10022 => enemy.frame = value as u32, | |
186 -10023 => unreachable!(), | |
187 -10024 => enemy.life = value as u32, | |
188 -10025 => unreachable!(), | |
189 _ => panic!("Unknown variable {}", var) | |
190 } | |
191 } | |
192 | |
193 fn set_f32(&mut self, var: f32, value: f32) { | |
194 let mut enemy = self.enemy.borrow_mut(); | |
195 match var { | |
196 -10001.0 => unimplemented!(), | |
197 -10002.0 => unimplemented!(), | |
198 -10003.0 => unimplemented!(), | |
199 -10004.0 => unimplemented!(), | |
200 -10005.0 => self.frame.floats[0] = value, | |
201 -10006.0 => self.frame.floats[1] = value, | |
202 -10007.0 => self.frame.floats[2] = value, | |
203 -10008.0 => self.frame.floats[3] = value, | |
204 -10009.0 => unimplemented!(), | |
205 -10010.0 => unimplemented!(), | |
206 -10011.0 => unimplemented!(), | |
207 -10012.0 => unimplemented!(), | |
208 -10013.0 => unreachable!(), | |
209 -10014.0 => unreachable!(), | |
210 -10015.0 => enemy.pos.x = value, | |
211 -10016.0 => enemy.pos.y = value, | |
212 -10017.0 => enemy.z = value, | |
213 -10018.0 => unreachable!(), | |
214 -10019.0 => unreachable!(), | |
215 -10020.0 => unreachable!(), | |
216 -10021.0 => unreachable!(), | |
217 -10022.0 => unimplemented!(), | |
218 -10023.0 => unreachable!(), | |
219 -10024.0 => unimplemented!(), | |
220 -10025.0 => unreachable!(), | |
221 _ => panic!("Unknown variable {}", var) | |
222 } | |
223 } | |
224 | |
225 fn get_prng(&mut self) -> Rc<RefCell<Prng>> { | |
226 let enemy = self.enemy.borrow(); | |
227 enemy.prng.upgrade().unwrap() | |
228 } | |
229 | |
230 fn run_instruction(&mut self, instruction: SubInstruction) { | |
231 println!("Running instruction {:?}", instruction); | |
232 match instruction { | |
233 SubInstruction::Noop() => { | |
234 // really | |
235 } | |
236 // 1 | |
237 SubInstruction::Destroy(_unused) => { | |
238 let mut enemy = self.enemy.borrow_mut(); | |
239 enemy.removed = true; | |
240 } | |
241 // 2 | |
242 SubInstruction::RelativeJump(frame, ip) => { | |
243 self.frame.frame = frame; | |
244 // ip = ip + flag in th06 | |
245 self.frame.ip = ip; | |
246 // we jump back to the main of the interpreter | |
247 } | |
248 // 3 | |
249 // GHIDRA SAYS THERE IS A COMPARISON_REG BUFFER BUT THERE IS NOT!!! | |
250 // | |
251 // MOV ECX,dword ptr [EBP + 0x8] jumptable 00407544 case 31 | |
252 // CMP dword ptr [0x9d4 + ECX],0x0 | |
253 // JLE LAB_00407abb | |
254 // aka ECX = enemy pointer | |
255 // ECX->9d4 (aka enemy_pointer_copy->comparison_reg) == 0 | |
256 // only the pointer is copied, not the value, thus we are safe | |
257 SubInstruction::RelativeJumpEx(frame, ip, var_id) => { | |
258 // TODO: counter_value is a field of "enemy" in th06, to check | |
259 let counter_value = self.get_i32(var_id) - 1; | |
260 if counter_value > 0 { | |
261 self.run_instruction(SubInstruction::RelativeJump(frame, ip)); | |
262 } | |
263 } | |
264 // 4 | |
265 SubInstruction::SetInt(var_id, value) => { | |
266 self.set_i32(var_id, value); | |
267 } | |
268 // 5 | |
269 SubInstruction::SetFloat(var_id, value) => { | |
270 self.set_f32(var_id as f32, value); | |
271 } | |
272 // 6 | |
273 SubInstruction::SetRandomInt(var_id, maxval) => { | |
274 let random = self.get_prng().borrow_mut().get_u32() as i32; | |
275 self.set_i32(var_id, random % self.get_i32(maxval)); | |
276 } | |
277 // 7 | |
278 SubInstruction::SetRandomIntMin(var_id, maxval, minval) => { | |
279 let random = self.get_prng().borrow_mut().get_u32() as i32; | |
280 self.set_i32(var_id, (random % self.get_i32(maxval)) + self.get_i32(minval)); | |
281 } | |
282 // 8 | |
283 SubInstruction::SetRandomFloat(var_id, maxval) => { | |
284 let random = self.get_prng().borrow_mut().get_f64() as f32; | |
285 self.set_f32(var_id as f32, self.get_f32(maxval) * random) | |
286 } | |
287 // 9 | |
288 SubInstruction::SetRandomFloatMin(var_id, maxval, minval) => { | |
289 let random = self.get_prng().borrow_mut().get_f64() as f32; | |
290 self.set_f32(var_id as f32, self.get_f32(maxval) * random + self.get_f32(minval)) | |
291 } | |
292 // 10 | |
293 SubInstruction::StoreX(var_id) => { | |
294 let x = { | |
295 let enemy = self.enemy.borrow(); | |
296 enemy.pos.x | |
297 }; | |
298 // TODO: is this really an i32? | |
299 self.set_i32(var_id, x as i32); | |
300 } | |
301 // 11 | |
302 SubInstruction::StoreY(var_id) => { | |
303 let y = { | |
304 let enemy = self.enemy.borrow(); | |
305 enemy.pos.y | |
306 }; | |
307 self.set_i32(var_id, y as i32); | |
308 } | |
309 // 12 | |
310 SubInstruction::StoreZ(var_id) => { | |
311 let z = { | |
312 let enemy = self.enemy.borrow(); | |
313 enemy.z | |
314 }; | |
315 self.set_i32(var_id, z as i32); | |
316 } | |
317 // 13(int), 20(float), same impl in th06 | |
318 SubInstruction::AddInt(var_id, a, b) => { | |
319 self.set_i32(var_id, self.get_i32(a) + self.get_i32(b)); | |
320 } | |
321 SubInstruction::AddFloat(var_id, a, b) => { | |
322 self.set_f32(var_id as f32, self.get_f32(a) + self.get_f32(b)); | |
323 } | |
324 // 14(int), 21(float), same impl in th06 | |
325 SubInstruction::SubstractInt(var_id, a, b) => { | |
326 self.set_i32(var_id, self.get_i32(a) - self.get_i32(b)); | |
327 } | |
328 SubInstruction::SubstractFloat(var_id, a, b) => { | |
329 self.set_f32(var_id as f32, self.get_f32(a) - self.get_f32(b)); | |
330 } | |
331 // 15(int), 22(unused) | |
332 SubInstruction::MultiplyInt(var_id, a, b) => { | |
333 self.set_i32(var_id, self.get_i32(a) * self.get_i32(b)); | |
334 } | |
335 /* | |
336 SubInstruction::MultiplyFloat(var_id, a, b) => { | |
337 self.set_f32(var_id as f32, self.get_f32(a) * self.get_f32(b)); | |
338 } | |
339 */ | |
340 // 16(int), 23(unused) | |
341 SubInstruction::DivideInt(var_id, a, b) => { | |
342 self.set_i32(var_id, self.get_i32(a) / self.get_i32(b)); | |
343 } | |
344 | |
345 SubInstruction::DivideFloat(var_id, a, b) => { | |
346 self.set_f32(var_id as f32, self.get_f32(a) / self.get_f32(b)); | |
347 } | |
348 | |
349 // 17(int) 24(unused) | |
350 SubInstruction::ModuloInt(var_id, a, b) => { | |
351 self.set_i32(var_id, self.get_i32(a) % self.get_i32(b)); | |
352 } | |
353 | |
354 SubInstruction::ModuloFloat(var_id, a, b) => { | |
355 self.set_f32(var_id as f32, self.get_f32(a) % self.get_f32(b)); | |
356 } | |
357 | |
358 // 18 | |
359 // setval used by pytouhou, but not in game(???) | |
360 SubInstruction::Increment(var_id) => { | |
361 self.set_i32(var_id, self.get_i32(var_id) + 1); | |
362 } | |
363 | |
364 // 19 | |
365 SubInstruction::Decrement(var_id) => { | |
366 self.set_i32(var_id, self.get_i32(var_id) - 1); | |
367 } | |
368 | |
369 //25 | |
370 SubInstruction::GetDirection(var_id, x1, y1, x2, y2) => { | |
371 //__ctrandisp2 in ghidra, let's assume from pytouhou it's atan2 | |
372 self.set_f32(var_id as f32, (self.get_f32(y2) - self.get_f32(y1)).atan2(self.get_f32(x2) - self.get_f32(x1))); | |
373 } | |
374 | |
375 // 26 | |
376 SubInstruction::FloatToUnitCircle(var_id) => { | |
377 // TODO: atan2(var_id, ??) is used by th06, maybe ?? is pi? | |
378 // we suck at trigonometry so let's use pytouhou for now | |
379 self.set_f32(var_id as f32, (self.get_f32(var_id as f32) + std::f32::consts::PI) % (2. * std::f32::consts::PI) - std::f32::consts::PI); | |
380 } | |
381 | |
382 // 27(int), 28(float) | |
383 SubInstruction::CompareInts(a, b) => { | |
384 let a = self.get_i32(a); | |
385 let b = self.get_i32(b); | |
386 if a < b { | |
387 self.frame.comparison_reg = -1; | |
388 } | |
389 else if a == b { | |
390 self.frame.comparison_reg = 0; | |
391 } | |
392 else { | |
393 self.frame.comparison_reg = 1; | |
394 } | |
395 } | |
396 SubInstruction::CompareFloats(a, b) => { | |
397 let a = self.get_f32(a); | |
398 let b = self.get_f32(b); | |
399 if a < b { | |
400 self.frame.comparison_reg = -1; | |
401 } | |
402 else if a == b { | |
403 self.frame.comparison_reg = 0; | |
404 } | |
405 else { | |
406 self.frame.comparison_reg = 1; | |
407 } | |
408 } | |
409 // 29 | |
410 SubInstruction::RelativeJumpIfLowerThan(frame, ip) => { | |
411 if self.frame.comparison_reg == -1 { | |
412 self.run_instruction(SubInstruction::RelativeJump(frame, ip)); | |
413 } | |
414 } | |
415 // 30 | |
416 SubInstruction::RelativeJumpIfLowerOrEqual(frame, ip) => { | |
417 if self.frame.comparison_reg != 1 { | |
418 self.run_instruction(SubInstruction::RelativeJump(frame, ip)); | |
419 } | |
420 } | |
421 // 31 | |
422 SubInstruction::RelativeJumpIfEqual(frame, ip) => { | |
423 if self.frame.comparison_reg == 0 { | |
424 self.run_instruction(SubInstruction::RelativeJump(frame, ip)); | |
425 } | |
426 } | |
427 // 32 | |
428 SubInstruction::RelativeJumpIfGreaterThan(frame, ip) => { | |
429 if self.frame.comparison_reg == 1 { | |
430 self.run_instruction(SubInstruction::RelativeJump(frame, ip)); | |
431 } | |
432 } | |
433 // 33 | |
434 SubInstruction::RelativeJumpIfGreaterOrEqual(frame, ip) => { | |
435 if self.frame.comparison_reg != -1 { | |
436 self.run_instruction(SubInstruction::RelativeJump(frame, ip)); | |
437 } | |
438 } | |
439 // 34 | |
440 SubInstruction::RelativeJumpIfNotEqual(frame, ip) => { | |
441 if self.frame.comparison_reg != 0 { | |
442 self.run_instruction(SubInstruction::RelativeJump(frame, ip)); | |
443 } | |
444 } | |
445 // 35 | |
446 SubInstruction::Call(sub, param1, param2) => { | |
447 self.stack.push(self.frame.clone()); | |
448 self.frame.sub = sub as u16; | |
449 self.frame.ints1[0] = param1; | |
450 self.frame.floats[0] = param2; | |
451 self.frame.frame = 0; | |
452 self.frame.ip = 0; | |
453 } | |
454 | |
455 // 36 | |
456 SubInstruction::Return() => { | |
457 self.frame = self.stack.pop().unwrap(); | |
458 } | |
459 // 37 | |
460 SubInstruction::CallIfSuperior(sub, param1, param2, a, b) => { | |
461 if self.get_i32(a) < self.get_i32(b) { | |
462 self.run_instruction(SubInstruction::Call(sub, param1, param2)); | |
463 } | |
464 } | |
465 // 38 | |
466 SubInstruction::CallIfSuperiorOrEqual(sub, param1, param2, a, b) => { | |
467 if self.get_i32(a) <= self.get_i32(b) { | |
468 self.run_instruction(SubInstruction::Call(sub, param1, param2)); | |
469 } | |
470 } | |
471 // 39 | |
472 SubInstruction::CallIfEqual(sub, param1, param2, a, b) => { | |
473 if self.get_i32(a) == self.get_i32(b) { | |
474 self.run_instruction(SubInstruction::Call(sub, param1, param2)); | |
475 } | |
476 } | |
477 // 40 | |
478 SubInstruction::CallIfInferior(sub, param1, param2, a, b) => { | |
479 if self.get_i32(b) < self.get_i32(a) { | |
480 self.run_instruction(SubInstruction::Call(sub, param1, param2)); | |
481 } | |
482 } | |
483 | |
484 // 41 | |
485 SubInstruction::CallIfInferiorOrEqual(sub, param1, param2, a, b) => { | |
486 if self.get_i32(b) <= self.get_i32(a) { | |
487 self.run_instruction(SubInstruction::Call(sub, param1, param2)); | |
488 } | |
489 } | |
490 //42 | |
491 SubInstruction::CallIfNotEqual(sub, param1, param2, a, b) => { | |
492 if self.get_i32(a) != self.get_i32(b) { | |
493 self.run_instruction(SubInstruction::Call(sub, param1, param2)); | |
494 } | |
495 } | |
496 | |
497 // 43 | |
498 SubInstruction::SetPosition(x, y, z) => { | |
499 let (x, y, z) = (self.get_f32(x), self.get_f32(y), self.get_f32(z)); | |
500 let mut enemy = self.enemy.borrow_mut(); | |
501 enemy.set_pos(x, y, z); | |
502 } | |
503 // 44 | |
504 /* | |
505 SubInstruction::SetAngularSpeed(x, y, z) => { | |
506 // same as above, except for angular speed | |
507 let mut enemy = self.enemy.borrow_mut(); | |
508 enemy.set_angular_speed(self.get_f32(x), self.get_f32(y), self.get_f32(z)); | |
509 } | |
510 */ | |
511 // 45 | |
512 SubInstruction::SetAngleAndSpeed(angle, speed) => { | |
513 let angle = self.get_f32(angle); | |
514 let speed = self.get_f32(speed); | |
515 let mut enemy = self.enemy.borrow_mut(); | |
516 enemy.update_mode = 0; | |
517 enemy.angle = angle; | |
518 enemy.speed = speed; | |
519 } | |
520 // 46 | |
521 SubInstruction::SetRotationSpeed(speed) => { | |
522 let rotation_speed = self.get_f32(speed); | |
523 let mut enemy = self.enemy.borrow_mut(); | |
524 enemy.update_mode = 0; | |
525 enemy.rotation_speed = rotation_speed; | |
526 } | |
527 // 47 | |
528 SubInstruction::SetSpeed(speed) => { | |
529 let speed = self.get_f32(speed); | |
530 let mut enemy = self.enemy.borrow_mut(); | |
531 enemy.update_mode = 0; | |
532 enemy.speed = speed; | |
533 } | |
534 // 48 | |
535 SubInstruction::SetAcceleration(acceleration) => { | |
536 let acceleration = self.get_f32(acceleration); | |
537 let mut enemy = self.enemy.borrow_mut(); | |
538 enemy.update_mode = 0; | |
539 enemy.acceleration = acceleration; | |
540 } | |
541 // 49 | |
542 SubInstruction::SetRandomAngle(min_angle, max_angle) => { | |
543 let angle = self.get_prng().borrow_mut().get_f64() as f32 * (max_angle - min_angle) + min_angle; | |
544 let mut enemy = self.enemy.borrow_mut(); | |
545 enemy.angle = angle; | |
546 } | |
547 // 51 | |
548 SubInstruction::TargetPlayer(delta_angle, speed) => { | |
549 let speed = self.get_f32(speed); | |
550 let mut enemy = self.enemy.borrow_mut(); | |
551 let game = enemy.game.upgrade().unwrap(); | |
552 let player = game.borrow().get_player(); | |
553 enemy.update_mode = 0; | |
554 enemy.speed = speed; | |
555 enemy.angle = enemy.get_angle_to(player) + delta_angle; | |
556 } | |
557 | |
558 // 52 to 64 are different interlacing fields | |
559 | |
560 // 65 | |
561 // to note: in game a flag is set to enable the screenbox and is set by 66 to disable | |
562 // it on top of setting our values. But we have a good engine and can detect if that's | |
563 // changed without setting a flag :) | |
564 SubInstruction::SetScreenBox(xmin, ymin, xmax, ymax) => { | |
565 let mut enemy = self.enemy.borrow_mut(); | |
566 enemy.screen_box = Some((xmin, ymin, xmax, ymax)); | |
567 } | |
568 // 66 | |
569 SubInstruction::ClearScreenBox() => { | |
570 let mut enemy = self.enemy.borrow_mut(); | |
571 enemy.screen_box = None; | |
572 } | |
573 | |
574 // 67 to 75 are set bullet attributes and it seems a pain to reverse rn | |
575 SubInstruction::SetBulletAttributes1(anim, sprite_index_offset, bullets_per_shot, | |
576 number_of_shots, speed, speed2, launch_angle, | |
577 angle, flags) => { | |
578 gen_SetBulletAttributes!(self, 67, anim, sprite_index_offset, bullets_per_shot, | |
579 number_of_shots, speed, speed2, launch_angle, angle, | |
580 flags); | |
581 } | |
582 SubInstruction::SetBulletAttributes2(anim, sprite_index_offset, bullets_per_shot, | |
583 number_of_shots, speed, speed2, launch_angle, | |
584 angle, flags) => { | |
585 gen_SetBulletAttributes!(self, 68, anim, sprite_index_offset, bullets_per_shot, | |
586 number_of_shots, speed, speed2, launch_angle, angle, | |
587 flags); | |
588 } | |
589 SubInstruction::SetBulletAttributes3(anim, sprite_index_offset, bullets_per_shot, | |
590 number_of_shots, speed, speed2, launch_angle, | |
591 angle, flags) => { | |
592 gen_SetBulletAttributes!(self, 69, anim, sprite_index_offset, bullets_per_shot, | |
593 number_of_shots, speed, speed2, launch_angle, angle, | |
594 flags); | |
595 } | |
596 SubInstruction::SetBulletAttributes4(anim, sprite_index_offset, bullets_per_shot, | |
597 number_of_shots, speed, speed2, launch_angle, | |
598 angle, flags) => { | |
599 gen_SetBulletAttributes!(self, 70, anim, sprite_index_offset, bullets_per_shot, | |
600 number_of_shots, speed, speed2, launch_angle, angle, | |
601 flags); | |
602 } | |
603 SubInstruction::SetBulletAttributes5(anim, sprite_index_offset, bullets_per_shot, | |
604 number_of_shots, speed, speed2, launch_angle, | |
605 angle, flags) => { | |
606 gen_SetBulletAttributes!(self, 71, anim, sprite_index_offset, bullets_per_shot, | |
607 number_of_shots, speed, speed2, launch_angle, angle, | |
608 flags); | |
609 } | |
610 SubInstruction::SetBulletAttributes6(anim, sprite_index_offset, bullets_per_shot, | |
611 number_of_shots, speed, speed2, launch_angle, | |
612 angle, flags) => { | |
613 gen_SetBulletAttributes!(self, 74, anim, sprite_index_offset, bullets_per_shot, | |
614 number_of_shots, speed, speed2, launch_angle, angle, | |
615 flags); | |
616 } | |
617 SubInstruction::SetBulletAttributes7(anim, sprite_index_offset, bullets_per_shot, | |
618 number_of_shots, speed, speed2, launch_angle, | |
619 angle, flags) => { | |
620 gen_SetBulletAttributes!(self, 75, anim, sprite_index_offset, bullets_per_shot, | |
621 number_of_shots, speed, speed2, launch_angle, angle, | |
622 flags); | |
623 } | |
624 | |
625 // 76 | |
626 SubInstruction::SetBulletInterval(interval) => { | |
627 let mut enemy = self.enemy.borrow_mut(); | |
628 enemy.set_bullet_launch_interval(0, interval); | |
629 } | |
630 | |
631 // 77 | |
632 SubInstruction::SetBulletIntervalEx(interval) => { | |
633 let rand_start = self.get_prng().borrow_mut().get_u32(); | |
634 | |
635 let mut enemy = self.enemy.borrow_mut(); | |
636 enemy.set_bullet_launch_interval(rand_start, interval); | |
637 } | |
638 | |
639 // 78-79 are more interpolation flags | |
640 // 78 | |
641 SubInstruction::DelayAttack() => { | |
642 let mut enemy = self.enemy.borrow_mut(); | |
643 enemy.delay_attack = true; | |
644 } | |
645 // 79 | |
646 SubInstruction::NoDelayAttack() => { | |
647 let mut enemy = self.enemy.borrow_mut(); | |
648 enemy.delay_attack = false; | |
649 } | |
650 // 80 | |
651 /* | |
652 SubInstruction::NoClue() => { | |
653 let mut enemy = self.enemy.borrow_mut(); | |
654 //bullet_pos = launch offset | |
655 (enemy->bullet_attributes).bullets_per_shot = enemy.pos.x + enemy->bullet_pos.pos.x; | |
656 (enemy->bullet_attributes).number_of_shots = enemy.pos.pos.y + enemy.bullet_pos.pos.y; | |
657 (enemy->bullet_attributes).speed = enemy.z + bullet_pos.z; | |
658 enemy.fire(bullet_attributes=bullet_attributes) | |
659 } | |
660 */ | |
661 | |
662 // 81 | |
663 SubInstruction::SetBulletLaunchOffset(dx, dy, dz) => { | |
664 let (dx, dy, dz) = (self.get_f32(dx), self.get_f32(dy), self.get_f32(dz)); | |
665 let mut enemy = self.enemy.borrow_mut(); | |
666 enemy.bullet_offset = Offset { dx, dy }; | |
667 } | |
668 | |
669 // 82 | |
670 SubInstruction::SetExtendedBulletAttributes(a, b, c, d, e, f, g, h) => { | |
671 let (a, b, c, d) = (self.get_i32(a), self.get_i32(b), self.get_i32(c), self.get_i32(d)); | |
672 let (e, f, g, h) = (self.get_f32(e), self.get_f32(f), self.get_f32(g), self.get_f32(h)); | |
673 let mut enemy = self.enemy.borrow_mut(); | |
674 enemy.bullet_attributes.extended_attributes = (a, b, c, d, e, f, g, h); | |
675 } | |
676 | |
677 // 83 | |
678 /* | |
679 SubInstruction::ChangeBulletsIntoStarBonus() => { | |
680 let mut game = self.game.borrow_mut(); | |
681 game.change_bullets_into_star_items(); | |
682 } | |
683 */ | |
684 | |
685 // 84 | |
686 SubInstruction::SetBulletSound(sound) => { | |
687 let mut enemy = self.enemy.borrow_mut(); | |
688 if sound < 0 { | |
689 enemy.bullet_attributes.sound = None; | |
690 } else { | |
691 // This assert isn’t part of the original engine, but it would crash on high | |
692 // values anyway. | |
693 assert!(sound <= 255); | |
694 enemy.bullet_attributes.sound = Some(sound as u8); | |
695 } | |
696 } | |
697 | |
698 // 85-86 ire newlaser functions | |
699 | |
700 // 87 | |
701 SubInstruction::SetUpcomingLaserId(laser_id) => { | |
702 let mut enemy = self.enemy.borrow_mut(); | |
703 enemy.current_laser_id = laser_id; | |
704 } | |
705 | |
706 // 88 | |
707 /* | |
708 SubInstruction::AlterLaserAngle(laser_id, delta) => { | |
709 let mut enemy = self.enemy.borrow_mut(); | |
710 if enemy.laser_by_id.contains_key(&laser_id) { | |
711 let mut laser = enemy.laser_by_id.get(&laser_id); | |
712 laser.angle += self.get_f32(delta); | |
713 } | |
714 } | |
715 */ | |
716 | |
717 // 89 | |
718 /* | |
719 SubInstruction::AlterLaserAnglePlayer(laser_id, delta) => { | |
720 let mut enemy = self.enemy.borrow_mut(); | |
721 if enemy.laser_by_id.contains_key(&laser_id) { | |
722 let mut laser = enemy.laser_by_id.get(laser_id); | |
723 let player = enemy.select_player(); | |
724 laser.angle = enemy.get_angle(player) + angle; | |
725 } | |
726 } | |
727 */ | |
728 | |
729 // 90 | |
730 /* | |
731 SubInstruction::RepositionLaser(laser_id, ox, oy, oz) => { | |
732 let mut enemy = self.enemy.borrow_mut(); | |
733 if enemy.laser_by_id.contains_key(&laser_id) { | |
734 let mut laser = enemy.laser_by_id.get(&laser_id); | |
735 laser.set_base_pos(enemy.pos.x + ox, enemy.pos.y + oy, enemy.z + oz) | |
736 } | |
737 } | |
738 */ | |
739 // 91 | |
740 // wat | |
741 SubInstruction::LaserSetCompare(laser_id) => { | |
742 let enemy = self.enemy.borrow_mut(); | |
743 // in game it checks if either the laser exists OR if one of its member is set to 0 | |
744 // which, uhhhh, we are not going to reimplement for obvious reasons | |
745 // the correct implementation would be: if this laser does not exist have a | |
746 // 1/100000 chance to continue, otherwise crash | |
747 if enemy.laser_by_id.contains_key(&laser_id) { | |
748 // let's assume we gud | |
749 self.frame.comparison_reg = 1; | |
750 } | |
751 else{ | |
752 self.frame.comparison_reg = 0; | |
753 } | |
754 } | |
755 | |
756 // 92 | |
757 /* | |
758 SubInstruction::RepositionLaser(laser_id, ox, oy, oz) => { | |
759 let mut enemy = self.enemy.borrow_mut(); | |
760 if enemy.laser_by_id.contains_key(&laser_id) { | |
761 let mut laser = enemy.laser_by_id.get(laser_id); | |
762 laser.cancel(); | |
763 } | |
764 } | |
765 */ | |
766 // 93 | |
767 // TODO: actually implement that hell | |
768 SubInstruction::SetSpellcard(face, number, name) => { | |
769 unimplemented!("spellcard start"); | |
770 | |
771 } | |
772 // 94 | |
773 SubInstruction::EndSpellcard() => { | |
774 unimplemented!("spellcard end"); | |
775 | |
776 } | |
777 | |
778 // 95 | |
779 SubInstruction::SpawnEnemy(sub, x, y, z, life, bonus, score) => { | |
780 let x = self.get_f32(x); | |
781 let y = self.get_f32(y); | |
782 let _z = self.get_f32(z); | |
783 let enemy = self.enemy.borrow_mut(); | |
784 let anm0 = enemy.anm0.upgrade().unwrap(); | |
785 let game = enemy.game.upgrade().unwrap(); | |
786 let enemy = Enemy::new(Position::new(x, y), life, bonus, score as u32, false, Rc::downgrade(&anm0), Rc::downgrade(&game)); | |
787 let ecl = self.ecl.clone().unwrap(); | |
788 let mut runner = EclRunner::new(&ecl, enemy, sub as u16); | |
789 runner.run_frame(); | |
790 } | |
791 | |
792 // 96 | |
793 /* | |
794 SubInstruction::KillEnemies() => { | |
795 let mut game = self.game.borrow_mut(); | |
796 game.kill_enemies(); | |
797 } | |
798 */ | |
799 | |
800 | |
801 | |
802 // 97 | |
803 SubInstruction::SetAnim(index) => { | |
804 // seems correct, game internally gets base_addr =(iVar13 + 0x1c934), pointer_addr = iVar14 * 4 | |
805 let mut enemy = self.enemy.borrow_mut(); | |
806 enemy.set_anim(index as u8); | |
807 } | |
808 // 98 | |
809 SubInstruction::SetMultipleAnims(default, end_left, end_right, left, right, _unused) => { | |
810 // _unused was supposed to set movement_dependant_sprites, but internally the game | |
811 // assigns it 0xff | |
812 // TODO: THIS DOES NOT CALL set_anim. this only assigns all parameters to their | |
813 // internal struct. To check if the anims are set somewhere else | |
814 let mut enemy = self.enemy.borrow_mut(); | |
815 enemy.movement_dependant_sprites = if left == -1 { | |
816 None | |
817 } else { | |
818 enemy.set_anim(default as u8); | |
819 Some((end_left as u8, end_right as u8, left as u8, right as u8)) | |
820 }; | |
821 } | |
822 // 99 | |
823 SubInstruction::SetAuxAnm(number, script) => { | |
824 assert!(number < 8); | |
825 let mut enemy = self.enemy.borrow_mut(); | |
826 enemy.set_aux_anm(number, script); | |
827 } | |
828 | |
829 // 100 | |
830 SubInstruction::SetDeathAnim(index) => { | |
831 // TODO: takes 3 parameters in game as u8 unlike our single u32. | |
832 // To reverse! | |
833 let mut enemy = self.enemy.borrow_mut(); | |
834 enemy.death_anim = index; | |
835 } | |
836 // 101 | |
837 SubInstruction::SetBossMode(value) => { | |
838 let enemy = self.enemy.borrow_mut(); | |
839 if value < 0 { | |
840 enemy.set_boss(false); | |
841 } | |
842 else { | |
843 // the boss pointer is written somewhere in memory and overwrote by a 0 when | |
844 // the boss mode is false, might want to look into that | |
845 enemy.set_boss(true); | |
846 } | |
847 } | |
848 | |
849 // 102 | |
850 // TODO: title says it all | |
851 /* | |
852 SubInstruction::ParticlesVoodooMagic(unk1, unk2, unk3, unk4, unk5) => { | |
853 } | |
854 */ | |
855 | |
856 // 103 | |
857 SubInstruction::SetHitbox(width, height, depth) => { | |
858 let mut enemy = self.enemy.borrow_mut(); | |
859 enemy.set_hitbox(width, height); | |
860 } | |
861 | |
862 // 104 | |
863 SubInstruction::SetCollidable(collidable) => { | |
864 // TODO: me and my siblings(105, 107, 117) are implemented as a single variable in the touhou 6 | |
865 // original engine. While our behaviour seems correct we might want to implement | |
866 // that as a single variable | |
867 // TODO[2]: THE BITFLAG MIGHT BE INCORRECT FOR OTHER SIBLING INSTRUCTIONS, the | |
868 // behavior was DEFINITELY incorrect in pytouhou for SetTouchable at the very least | |
869 let mut enemy = self.enemy.borrow_mut(); | |
870 enemy.collidable = (collidable&1) != 0; | |
871 } | |
872 | |
873 // 105 | |
874 SubInstruction::SetDamageable(damageable) => { | |
875 let mut enemy = self.enemy.borrow_mut(); | |
876 enemy.damageable = (damageable&1) != 0; | |
877 } | |
878 | |
879 // 106 | |
880 SubInstruction::PlaySound(index) => { | |
881 let enemy = self.enemy.borrow_mut(); | |
882 enemy.play_sound(index); | |
883 } | |
884 | |
885 // 107 | |
886 SubInstruction::SetDeathFlags(death_flags) => { | |
887 let mut enemy = self.enemy.borrow_mut(); | |
888 enemy.death_flags = death_flags; | |
889 } | |
890 // 108 | |
891 SubInstruction::SetDeathCallback(sub) => { | |
892 let mut enemy = self.enemy.borrow_mut(); | |
893 enemy.death_callback = Some(sub); | |
894 } | |
895 | |
896 // 109 | |
897 SubInstruction::MemoryWriteInt(value, index) => { | |
898 unimplemented!("not again that damn foe corrupted my ret\\x41\\x41\\x41\\x41"); | |
899 } | |
900 | |
901 // 110 | |
902 /* | |
903 SubInstruction::KillEnemy(enemy) => { | |
904 let mut game = self.game.borrow_mut(); | |
905 game.kill_enemy(enemy); | |
906 } | |
907 */ | |
908 | |
909 // 111 | |
910 /* | |
911 SubInstruction::SetLife(value) => { | |
912 let mut enemy = self.enemy.borrow_mut(); | |
913 let mut game = self.game.borrow_mut(); | |
914 enemy.life = value; | |
915 game.interface.set_boss_life(); | |
916 } | |
917 */ | |
918 // 112 | |
919 SubInstruction::SetElapsedTime(value) => { | |
920 let mut enemy = self.enemy.borrow_mut(); | |
921 enemy.frame = value as u32; | |
922 } | |
923 // 113 | |
924 /* | |
925 SubInstruction::SetLowLifeTrigger(value) => { | |
926 let mut enemy = self.enemy.borrow_mut(); | |
927 let mut game = self.game.borrow_mut(); | |
928 enemy.low_life_trigger = value; | |
929 game.interface.set_spell_life(); | |
930 } | |
931 */ | |
932 // 114 | |
933 /* | |
934 SubInstruction::SetLowLifeCallback(sub) => { | |
935 let mut enemy = self.enemy.borrow_mut(); | |
936 enemy.low_life_callback.enable(self.switch_to_sub, (sub,)); | |
937 } | |
938 */ | |
939 // 115 | |
940 /* | |
941 SubInstruction::SetTimeout(timeout) => { | |
942 let mut enemy = self.enemy.borrow_mut(); | |
943 enemy.frame = value; | |
944 enemy.timeout = timeout; | |
945 } | |
946 */ | |
947 // 116 | |
948 /* | |
949 SubInstruction::SetTimeoutCallback(sub) => { | |
950 let mut enemy = self.enemy.borrow_mut(); | |
951 enemy.timeout_callback.enable(self.switch_to_sub, (sub,)); | |
952 } | |
953 */ | |
954 | |
955 | |
956 // 117 | |
957 SubInstruction::SetTouchable(touchable) => { | |
958 let mut enemy = self.enemy.borrow_mut(); | |
959 enemy.touchable = touchable != 0; | |
960 } | |
961 | |
962 // 121 | |
963 // Here lies the Di Sword of sadness | |
964 SubInstruction::CallSpecialFunction(function, arg) => { | |
965 match function { | |
966 0 => { | |
967 let mut enemy = self.enemy.borrow_mut(); | |
968 let game = enemy.game.upgrade().unwrap(); | |
969 let mut game = game.borrow_mut(); | |
970 //game.drop_particle(12, enemy.pos, 1, 0xffffffff); | |
971 //game.iter_bullets(|mut bullet| { | |
972 for bullet in game.bullets.iter() { | |
973 //game.new_effect(bullet.sprite, TODO); | |
974 let mut bullet = bullet.borrow_mut(); | |
975 if arg == 0 { | |
976 bullet.speed = 0.; | |
977 bullet.dpos = [0., 0., 0.]; | |
978 } else if arg == 1 { | |
979 bullet.flags |= 0x10; | |
980 bullet.frame = 220; | |
981 let rand_angle = game.prng.borrow_mut().get_f64() * 2. * std::f64::consts::PI - std::f64::consts::PI; | |
982 bullet.attributes[0] = (rand_angle.cos() * 0.01) as f32; | |
983 bullet.attributes[1] = (rand_angle.sin() * 0.01) as f32; | |
984 } | |
985 } | |
986 } | |
987 1 => { | |
988 let range_x = arg as f64; | |
989 let range_y = (arg as f32 * 0.75) as f64; | |
990 let rand_x = self.get_prng().borrow_mut().get_f64(); | |
991 let rand_y = self.get_prng().borrow_mut().get_f64(); | |
992 let mut enemy = self.enemy.borrow_mut(); | |
993 let pos = [rand_x * range_x + enemy.pos.x as f64 - range_x / 2., | |
994 rand_y * range_y + enemy.pos.x as f64 - range_y / 2.]; | |
995 enemy.bullet_attributes.fire(); | |
996 } | |
997 3 => { // Patchouli’s dual sign spellcard selector | |
998 let mut enemy = self.enemy.borrow_mut(); | |
999 let mut knowledge: [[i32; 3]; 4] = | |
1000 [[0, 3, 1], | |
1001 [2, 3, 4], | |
1002 [1, 4, 0], | |
1003 [4, 2, 3]]; | |
1004 | |
1005 //TODO: implement select_player and replace character by the correct one | |
1006 //let character = enemy.select_player().character; | |
1007 let character = 0; | |
1008 for i in 1..=3 { | |
1009 self.frame.ints1[i] = knowledge[character][i]; | |
1010 } | |
1011 } | |
1012 4 => { // Sakuya random daggers and time stop | |
1013 /* | |
1014 if arg < 2 { | |
1015 drop_particle(&PARTICLES_ARRAY,0xc,enemy->pos,1,0xffffffff); | |
1016 //TODO: is that the timestop? | |
1017 LEVEL.field_0x2c = arg; | |
1018 return; | |
1019 } | |
1020 // this changes the orientation of random bullets | |
1021 let mut max_bullets = 0xe; | |
1022 if (LEVEL.rank >= 2) {max_bullets = 0x34;} | |
1023 i = 0; | |
1024 for bullet in game.bullets { | |
1025 if bullet->state != 0 && bullet->state != 5 && 30. <= (bullet->sprites[0].additional_infos)->height && bullet->field_0x5ba != 5 && (uVar3 = prng16(&PRNG_STATE), (uVar3 & 3) == 0) { | |
1026 bullet->field_0x5ba = 5; | |
1027 new_effect(GAME_OBJECT,(sprite *)bullet, (int)bullet->sprites[0].sometimes_copy_of_UNK1 + (int)bullet->field_0x5ba); | |
1028 x = bullet->pos[0] - PLAYER.pos[0]; | |
1029 y = bullet->pos[1] - PLAYER.pos[1]; | |
1030 if sqrt(x*x+y*y) > 128. { | |
1031 if LEVEL.rank >= 2 {bullet->automatic_angle = prng_double() * 2*pi;} | |
1032 else{bullet->automatic_angle = (prng_double() * ((pi/8)*6) + pi/4);} | |
1033 else { | |
1034 // TODO: check player_get_angle, might be what ST0 is | |
1035 player_get_angle_to(&PLAYER,bullet->pos,local_38); | |
1036 bullet->automatic_angle = (extraout_ST0_00 + pi/2 + (prng_double() * pi*2)); | |
1037 } | |
1038 bullet->dpos[0] = cos(bullet->automatic_angle) * bullet->speed; | |
1039 bullet->dpos[1] = sin(bullet->automatic_angle) * bullet->speed; | |
1040 max_bullets -= 1; | |
1041 if (max_bullets == 0) break; | |
1042 } | |
1043 } | |
1044 (enemy->ecl_frame).var_ints_1[2] = 0;*/ | |
1045 } | |
1046 7 => { // Remilia's lazer maze | |
1047 // so what this does is kinda complex: 2 rounds of 3 subrounds of 8 shots, either | |
1048 // laser or standard bullets depending on the argument passed. | |
1049 // it is done in 2 steps: first we precalculate coordinates of the 8 shots for the first subround | |
1050 // set the shot properties depending on difficulties and current round and then | |
1051 // edit the coordinates for the next round | |
1052 let rnd_pos = self.get_prng().borrow_mut().get_f64() * 2. * std::f64::consts::PI; | |
1053 let enemy = self.enemy.borrow(); | |
1054 for i in 0..2 { | |
1055 let mut pos: [f64; 8*3] = [0.; 8*3]; | |
1056 let mut offset = rnd_pos -((std::f64::consts::PI/8.)*7.); | |
1057 let mut next_offset = -std::f64::consts::PI/4.; | |
1058 if (i == 0) { | |
1059 offset = rnd_pos -std::f64::consts::PI; | |
1060 next_offset = std::f64::consts::PI/4.; | |
1061 } | |
1062 | |
1063 // we calculate angle, speed and offset for the 8 shots | |
1064 let mut offset_copy=offset; | |
1065 for y in 0..8 { | |
1066 pos[y * 3] = offset_copy.cos() * 32. + enemy.pos.x as f64; | |
1067 pos[y * 3 + 1] = offset_copy.sin() * 32. + enemy.pos.y as f64; | |
1068 pos[y * 3 + 2] = enemy.z as f64; | |
1069 offset_copy += std::f64::consts::PI/4.; | |
1070 } | |
1071 | |
1072 // 3 rounds of 8 shots | |
1073 for z in 0..3 { | |
1074 | |
1075 let mut length = 112.; | |
1076 // last subround | |
1077 if (z == 2) {length = 480.;} | |
1078 | |
1079 for y in 0..8 { | |
1080 /* | |
1081 if (arg == 0) { | |
1082 let (mut si, mut ged, mut ed) = (8, 20.,ed=430.); | |
1083 if (LEVEL.rank < 2) {si=2; ged=28.; ed=length;} | |
1084 laser_args.angle = pos[y * 3]; | |
1085 laser_args.speed = pos[y * 3 + 1]; | |
1086 laser_args.start_offset = pos[y * 3 + 2]; | |
1087 laser_args.type = 1; | |
1088 laser_args.sprite_index_offset = si; | |
1089 laser_args.end_offset = offset; | |
1090 laser_args.width = 0.; | |
1091 laser_args.duration = 0; | |
1092 laser_args.grazing_extra_duration = ged; | |
1093 laser_args.end_duration = ed; | |
1094 laser_args.UNK1 = z * 0x10 + 0x3c; | |
1095 laser_args.grazing_delay = laser_args.end_duration; | |
1096 fire_laser(&ETAMA_ARRAY,&laser_args); | |
1097 } | |
1098 else { | |
1099 (enemy->bullet_attributes).pos[0] = pos[y * 3]; | |
1100 (enemy->bullet_attributes).pos[1] = pos[y*3+1]; | |
1101 (enemy->bullet_attributes).pos[2] = pos[y*3+2]; | |
1102 bullet_fire(&enemy->bullet_attributes,&ETAMA_ARRAY); | |
1103 } | |
1104 */ | |
1105 pos[y * 3] = offset.cos() * length + pos[y * 3]; | |
1106 pos[y * 3 + 1] = offset.sin() * length + pos[y * 3 + 1]; | |
1107 offset = offset + std::f64::consts::PI/4.; | |
1108 } | |
1109 offset = (next_offset - 2.*std::f64::consts::PI) + offset; | |
1110 } | |
1111 } | |
1112 } | |
1113 8 => { // Vampire Fantasy | |
1114 let n = { | |
1115 let enemy = self.enemy.borrow(); | |
1116 let game = enemy.game.upgrade().unwrap(); | |
1117 let mut game = game.borrow_mut(); | |
1118 let mut n = 0; | |
1119 for bullet in game.bullets.iter() { | |
1120 let mut bullet = bullet.borrow(); | |
1121 // TODO: uncomment that one. | |
1122 if bullet.state != 0 && bullet.state != 5 /* && (30. <= (bullet.sprites[0].additional_infos).height) */ { | |
1123 let prng = enemy.prng.upgrade().unwrap(); | |
1124 let random = prng.borrow_mut().get_f64(); | |
1125 let launch_angle = (random * (2. * std::f64::consts::PI) - std::f64::consts::PI) as f32; | |
1126 let mut attribs = BulletAttributes { | |
1127 // TODO: check if the z value of this pos is really used. | |
1128 pos: bullet.pos, | |
1129 anim: 3, | |
1130 sprite_index_offset: 1, | |
1131 launch_angle, | |
1132 speed: 0., | |
1133 angle: 0., | |
1134 speed2: 0., | |
1135 bullets_per_shot: 1, | |
1136 number_of_shots: 1, | |
1137 flags: 8, | |
1138 bullet_type: 1, | |
1139 extended_attributes: Default::default(), | |
1140 sound: None, | |
1141 }; | |
1142 attribs.fire(); | |
1143 n += 1 | |
1144 } | |
1145 } | |
1146 n | |
1147 }; | |
1148 //TODO: this variable might not always be correct! it uses the argument in | |
1149 //th06: *(int *)(param_1 + 0x9b0) = local_60; | |
1150 self.set_i32(-10004, n); | |
1151 } | |
1152 | |
1153 9 => { | |
1154 let mut rnd = self.get_prng().borrow_mut().get_f64(); | |
1155 //TODO: the game does that | |
1156 //drop_particle(&PARTICLES_ARRAY,0xc,enemy->pos,1,0xffffffff); | |
1157 //self._game.new_effect((enemy.x, enemy.y), 17) | |
1158 /* | |
1159 for bullet in game.bullets { | |
1160 if bullet._bullet_type.state != 0 && bullet._bullet_type.state != 5 && (30. <= (bullet.sprites[0].additional_infos)->height) && bullet.speed == 0. { | |
1161 bullet.flags = bullet.flags | 0x10; | |
1162 //TODO: reverse this field and effect | |
1163 bullet->field_0x5ba = 2; | |
1164 new_effect(GAME_OBJECT,(sprite *)bullet, (int)bullet->sprites[0].sometimes_copy_of_UNK1 + (int)bullet->field_0x5ba); | |
1165 bullet.speed=0.01; | |
1166 bullet.frame=0x78; | |
1167 | |
1168 let mut dx = bullet.x - enemy.x; | |
1169 let mut distance = dx.hypot(bullet.y - enemy.y); | |
1170 | |
1171 if distance > 0.01 { | |
1172 distance = sqrt(distance); | |
1173 }else{distance = 0.;} | |
1174 let mut angle = (distance * std::f64::consts::PI) / 256. + (rnd * (2*std::f64::consts::PI) - std::f64::consts::PI); | |
1175 bullet->attributes[0] = cos(angle) * 0.01000000; | |
1176 bullet->attributes[1] = sin(angle) * 0.01000000; | |
1177 } | |
1178 } | |
1179 */ | |
1180 } | |
1181 11 => { | |
1182 self.get_prng().borrow_mut().get_f64(); | |
1183 //TODO: the game does that | |
1184 //drop_particle(&PARTICLES_ARRAY,0xc,enemy->pos,1,0xffffffff); | |
1185 //self._game.new_effect((enemy.x, enemy.y), 17) | |
1186 /* | |
1187 for bullet in game.bullets { | |
1188 if bullet._bullet_type.state != 0 && bullet._bullet_type.state != 5 && (30. <= (bullet.sprites[0].additional_infos)->height) && bullet.speed == 0. { | |
1189 bullet.flags = bullet.flags | 0x10; | |
1190 //TODO: reverse this field and effect | |
1191 bullet->field_0x5ba = 2; | |
1192 new_effect(GAME_OBJECT,(sprite *)bullet, (int)bullet->sprites[0].sometimes_copy_of_UNK1 + (int)bullet->field_0x5ba); | |
1193 bullet.speed=0.01; | |
1194 bullet.frame=0x78; | |
1195 let mut angle = self.get_prng().borrow_mut().get_f64() * (2*std::f64::consts::PI) - std::f64::consts::PI; | |
1196 bullet->attributes[0] = cos(angle) * 0.01000000; | |
1197 bullet->attributes[1] = sin(angle) * 0.01000000; | |
1198 | |
1199 | |
1200 } | |
1201 } | |
1202 */ | |
1203 } | |
1204 13 => { | |
1205 if self.frame.ints1[3] % 6 == 0 { | |
1206 let mut _angle=self.frame.floats[2]; | |
1207 /* | |
1208 (type_, anim, sprite_idx_offset, bullets_per_shot, number_of_shots, | |
1209 speed, speed2, launch_angle, angle, flags) = self._enemy.bullet_attributes | |
1210 for i in range(arg) { | |
1211 //TODO: distance is obtained directly by copying bullet attributes | |
1212 //in memory | |
1213 launch_pos = (192 + cos(_angle) * _distance, | |
1214 224 + sin(_angle) * _distance); | |
1215 | |
1216 bullet_attributes = (type_, anim, sprite_idx_offset, | |
1217 bullets_per_shot, number_of_shots, | |
1218 speed, speed2, | |
1219 _angle + self.frame.floats[1], angle, flags); | |
1220 enemy.fire(launch_pos=launch_pos,bullet_attributes=bullet_attributes); | |
1221 _angle += 2*std::f64::consts::PI/arg; | |
1222 }*/ | |
1223 } | |
1224 self.frame.ints1[3] += 1; | |
1225 } | |
1226 14 => { // Lävatein | |
1227 let mut enemy = self.enemy.borrow_mut(); | |
1228 self.frame.ints1[3] = 0; | |
1229 for laser in enemy.laser_by_id.values() { | |
1230 //for pos in laser.get_bullets_pos(){ | |
1231 //TODO: the game checks for laser end_offset before firing | |
1232 // enemy.fire(launch_pos=pos); | |
1233 //} | |
1234 self.frame.ints1[3] += 1; | |
1235 } | |
1236 } | |
1237 16 => { // QED: Ripples of 495 years | |
1238 let mut enemy = self.enemy.borrow_mut(); | |
1239 let game = enemy.game.upgrade().unwrap(); | |
1240 let mut game = game.borrow_mut(); | |
1241 if arg == 0 { | |
1242 self.frame.floats[3] = 2. - (enemy.life as f32) / 6000.; | |
1243 self.frame.ints2[1] = ((enemy.life * 240) / 6000 + 40) as i32; | |
1244 } else { | |
1245 let fx = (320. - ((enemy.life as f32) * 160.) / 6000.) as f64; | |
1246 let fy = (128. - ((enemy.life as f32) * 64.) / 6000.) as f64; | |
1247 let rand_x = game.prng.borrow_mut().get_f64(); | |
1248 let rand_y = game.prng.borrow_mut().get_f64(); | |
1249 self.frame.floats[2] = (rand_x * fx + (192. - fx / 2.)) as f32; | |
1250 self.frame.floats[3] = (rand_y * fy + (96. - fy / 2.)) as f32; | |
1251 } | |
1252 } | |
1253 _ => unimplemented!("Special function {:?} not found!", function) | |
1254 } | |
1255 } | |
1256 | |
1257 // 122 | |
1258 // Here lies the Di Sword of despair | |
1259 SubInstruction::SetSpecialFunctionCallback(function) => { | |
1260 //TODO: those functions are executed at each frame and needs to be written to the | |
1261 //callback function but so far i'm simply focusing on the implementation | |
1262 //NB: the original engine doesn't differenciate between function blocks for ins 121 | |
1263 //and 122 but we do here, since it wouldn't make sense the other way around. | |
1264 match function { | |
1265 12 => { | |
1266 for i in 0..8 { | |
1267 /* | |
1268 if ((enemy->lasers[i] != (laser *)0x0) && (enemy->lasers[i]->state != 0)) { | |
1269 (enemy->bullet_attributes).pos[0] = cos(enemy->lasers[i]->angle) * 64. + enemy.pos.x; | |
1270 // yes, it reads pos[0] after it has been modified and yes, this most | |
1271 // likely is a bug | |
1272 (enemy->bullet_attributes).pos[1] = cos(enemy->lasers[i]->angle) * (enemy->bullet_attributes).pos[0] + enemy.pos.y; | |
1273 (enemy->bullet_attributes).pos[2] = enemy.z; | |
1274 bullet_fire(&enemy->bullet_attributes,&ETAMA_ARRAY); | |
1275 }*/ | |
1276 } | |
1277 } | |
1278 _ => unimplemented!("Special function {:?} not found!", function) | |
1279 } | |
1280 } | |
1281 _ => unimplemented!("{:?}", instruction) | |
1282 } | |
1283 | |
1284 } | |
1285 } | |
1286 | |
1287 #[cfg(test)] | |
1288 mod tests { | |
1289 use super::*; | |
1290 use crate::th06::anm0::Anm0; | |
1291 use crate::th06::ecl::{Sub, CallSub, Rank}; | |
1292 use crate::th06::enemy::Game; | |
1293 use std::io::{self, Read}; | |
1294 use std::fs::File; | |
1295 | |
1296 fn setup() -> (Rc<RefCell<Game>>, Rc<RefCell<Enemy>>) { | |
1297 let file = File::open("EoSD/ST/stg1enm.anm").unwrap(); | |
1298 let mut file = io::BufReader::new(file); | |
1299 let mut buf = vec![]; | |
1300 file.read_to_end(&mut buf).unwrap(); | |
1301 let (_, mut anms) = Anm0::from_slice(&buf).unwrap(); | |
1302 let anm0 = anms.pop().unwrap(); | |
1303 let anm0 = Rc::new(RefCell::new(anm0)); | |
1304 let prng = Rc::new(RefCell::new(Prng::new(0))); | |
1305 let game = Game::new(prng, Rank::EASY); | |
1306 let game = Rc::new(RefCell::new(game)); | |
1307 let enemy = Enemy::new(Position::new(0., 0.), 500, 0, 640, Rc::downgrade(&anm0), Rc::downgrade(&game)); | |
1308 (game, enemy) | |
1309 } | |
1310 | |
1311 #[test] | |
1312 fn call_and_return() { | |
1313 let (game, enemy) = setup(); | |
1314 let ecl = Ecl { mains: vec![], subs: vec![ | |
1315 Sub { instructions: vec![ | |
1316 CallSub::new(0, Rank::EASY, SubInstruction::Call(1, 13, 12.)), | |
1317 ]}, | |
1318 Sub { instructions: vec![ | |
1319 CallSub::new(0, Rank::EASY, SubInstruction::Noop()), | |
1320 CallSub::new(1, Rank::EASY, SubInstruction::Return()), | |
1321 ]}, | |
1322 ]}; | |
1323 let mut ecl_runner = EclRunner::new(&ecl, enemy, 0); | |
1324 ecl_runner.run_frame(); | |
1325 assert_eq!(ecl_runner.frame.ints1[0], 13); | |
1326 assert_eq!(ecl_runner.frame.floats[0], 12.); | |
1327 assert_eq!(ecl_runner.stack.len(), 1); | |
1328 ecl_runner.run_frame(); | |
1329 assert_eq!(ecl_runner.frame.ints1[0], 0); | |
1330 assert_eq!(ecl_runner.frame.floats[0], 0.); | |
1331 assert_eq!(ecl_runner.stack.len(), 0); | |
1332 } | |
1333 } |