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 }