view interpreters/src/th06/ecl.rs @ 791:a29122662cde

utils: Simplify translate_2d and align Mat4 to 16 bytes This lowers the amount of instructions from 61 to 32 on PowerPC with AltiVec, and from 25 to 14 on amd64 with AVX2.
author Link Mauve <linkmauve@linkmauve.fr>
date Sat, 17 Jan 2026 14:19:58 +0100
parents 21b186be2590
children
line wrap: on
line source

//! ECL runner.

use touhou_formats::th06::ecl::{Ecl, SubInstruction};
use crate::th06::enemy::{Enemy, Offset, BulletAttributes, Position};
use touhou_utils::prng::Prng;
use std::cell::RefCell;
use std::rc::Rc;

macro_rules! gen_SetBulletAttributes {
    ($self:ident, $opcode:tt, $anim:ident, $sprite_index_offset:ident, $bullets_per_shot:ident,
     $number_of_shots:ident, $speed:ident, $speed2:ident, $launch_angle:ident, $angle:ident,
     $flags:ident) => {{
        let sprite_index_offset = $self.get_i32($sprite_index_offset as i32) as i16;
        let bullets_per_shot = $self.get_i32($bullets_per_shot) as i16;
        let number_of_shots = $self.get_i32($number_of_shots) as i16;
        let speed = $self.get_f32($speed);
        let speed2 = $self.get_f32($speed2);
        let launch_angle = $self.get_f32($launch_angle);
        let angle = $self.get_f32($angle);

        let mut enemy = $self.enemy.borrow_mut();
        enemy.set_bullet_attributes($opcode, $anim, sprite_index_offset, bullets_per_shot,
                                    number_of_shots, speed, speed2, launch_angle, angle, $flags);
    }};
}

#[derive(Clone, Default)]
struct StackFrame {
    frame: i32,
    ip: i32,
    //ins122_callback: Option<Box<FnMut(Enemy)>>,
    ints1: [i32; 4],
    floats: [f32; 4],
    ints2: [i32; 4],
    comparison_reg: i32,
    sub: u16,
}

/// Interpreter for enemy scripts.
#[derive(Default)]
pub struct EclRunner {
    /// XXX
    pub enemy: Rc<RefCell<Enemy>>,

    ecl: Option<Ecl>,
    /// XXX
    pub running: bool,
    frame: StackFrame,
    // TODO: there are only 8 of these.
    stack: Vec<StackFrame>,
}

impl EclRunner {
    /// Create a new ECL runner.
    pub fn new(ecl: &Ecl, enemy: Rc<RefCell<Enemy>>, sub: u16) -> EclRunner {
        let mut ecl_runner = EclRunner {
            enemy,
            // XXX: no clone.
            ecl: Some(ecl.clone()),
            running: true,
            ..Default::default()
        };
        ecl_runner.frame.sub = sub;
        ecl_runner
    }

    /// Advance the ECL of a single frame.
    pub fn run_frame(&mut self) {
        while self.running {
            let ecl = self.ecl.clone().unwrap();
            let sub = &ecl.subs[self.frame.sub as usize];
            let call = match sub.instructions.get(self.frame.ip as usize) {
                Some(call) => call,
                None => {
                    self.running = false;
                    break;
                }
            };

            if call.time > self.frame.frame {
                break;
            }
            self.frame.ip += 1;

            let rank = self.enemy.borrow().get_rank();
            if (call.rank_mask & rank).is_empty() {
                continue;
            }

            if call.time == self.frame.frame {
                self.run_instruction(call.instr.clone());
            }
        }
        self.frame.frame += 1;
    }

    fn get_i32(&self, var: i32) -> i32 {
        let enemy = self.enemy.borrow();
        match var {
            -10001 => self.frame.ints1[0],
            -10002 => self.frame.ints1[1],
            -10003 => self.frame.ints1[2],
            -10004 => self.frame.ints1[3],
            -10005 => self.frame.floats[0] as i32,
            -10006 => self.frame.floats[1] as i32,
            -10007 => self.frame.floats[2] as i32,
            -10008 => self.frame.floats[3] as i32,
            -10009 => self.frame.ints2[0],
            -10010 => self.frame.ints2[1],
            -10011 => self.frame.ints2[2],
            -10012 => self.frame.ints2[3],
            -10013 => enemy.get_rank().bits() as i32,
            -10014 => enemy.get_difficulty(),
            -10015 => enemy.pos.x as i32,
            -10016 => enemy.pos.y as i32,
            -10017 => enemy.z as i32,
            -10018 => unimplemented!(),
            -10019 => unimplemented!(),
            -10020 => unreachable!(),
            -10021 => unimplemented!(),
            -10022 => enemy.frame as i32,
            -10023 => unreachable!(),
            -10024 => enemy.life as i32,
            -10025 => unimplemented!(),
            _ => var
        }
    }

    fn get_f32(&self, var: f32) -> f32 {
        let enemy = self.enemy.borrow();
        match var {
            -10001.0 => self.frame.ints1[0] as f32,
            -10002.0 => self.frame.ints1[1] as f32,
            -10003.0 => self.frame.ints1[2] as f32,
            -10004.0 => self.frame.ints1[3] as f32,
            -10005.0 => self.frame.floats[0],
            -10006.0 => self.frame.floats[1],
            -10007.0 => self.frame.floats[2],
            -10008.0 => self.frame.floats[3],
            -10009.0 => self.frame.ints2[0] as f32,
            -10010.0 => self.frame.ints2[1] as f32,
            -10011.0 => self.frame.ints2[2] as f32,
            -10012.0 => self.frame.ints2[3] as f32,
            -10013.0 => enemy.get_rank().bits() as f32,
            -10014.0 => enemy.get_difficulty() as f32,
            -10015.0 => enemy.pos.x,
            -10016.0 => enemy.pos.y,
            -10017.0 => enemy.z,
            -10018.0 => unimplemented!(),
            -10019.0 => unimplemented!(),
            -10020.0 => unreachable!(),
            -10021.0 => unimplemented!(),
            -10022.0 => enemy.frame as f32,
            -10023.0 => unreachable!(),
            -10024.0 => enemy.life as f32,
            -10025.0 => unimplemented!(),
            _ => var
        }
    }

    fn set_i32(&mut self, var: i32, value: i32) {
        let mut enemy = self.enemy.borrow_mut();
        match var {
            -10001 => self.frame.ints1[0] = value,
            -10002 => self.frame.ints1[1] = value,
            -10003 => self.frame.ints1[2] = value,
            -10004 => self.frame.ints1[3] = value,
            -10005 => unimplemented!(),
            -10006 => unimplemented!(),
            -10007 => unimplemented!(),
            -10008 => unimplemented!(),
            -10009 => self.frame.ints2[0] = value,
            -10010 => self.frame.ints2[1] = value,
            -10011 => self.frame.ints2[2] = value,
            -10012 => self.frame.ints2[3] = value,
            -10013 => unreachable!(),
            -10014 => unreachable!(),
            -10015 => unimplemented!(),
            -10016 => unimplemented!(),
            -10017 => unimplemented!(),
            -10018 => unreachable!(),
            -10019 => unreachable!(),
            -10020 => unreachable!(),
            -10021 => unreachable!(),
            -10022 => enemy.frame = value as u32,
            -10023 => unreachable!(),
            -10024 => enemy.life = value as u32,
            -10025 => unreachable!(),
            _ => panic!("Unknown variable {}", var)
        }
    }

    fn set_f32(&mut self, var: f32, value: f32) {
        let mut enemy = self.enemy.borrow_mut();
        match var {
            -10001.0 => unimplemented!(),
            -10002.0 => unimplemented!(),
            -10003.0 => unimplemented!(),
            -10004.0 => unimplemented!(),
            -10005.0 => self.frame.floats[0] = value,
            -10006.0 => self.frame.floats[1] = value,
            -10007.0 => self.frame.floats[2] = value,
            -10008.0 => self.frame.floats[3] = value,
            -10009.0 => unimplemented!(),
            -10010.0 => unimplemented!(),
            -10011.0 => unimplemented!(),
            -10012.0 => unimplemented!(),
            -10013.0 => unreachable!(),
            -10014.0 => unreachable!(),
            -10015.0 => enemy.pos.x = value,
            -10016.0 => enemy.pos.y = value,
            -10017.0 => enemy.z = value,
            -10018.0 => unreachable!(),
            -10019.0 => unreachable!(),
            -10020.0 => unreachable!(),
            -10021.0 => unreachable!(),
            -10022.0 => unimplemented!(),
            -10023.0 => unreachable!(),
            -10024.0 => unimplemented!(),
            -10025.0 => unreachable!(),
            _ => panic!("Unknown variable {}", var)
        }
    }

    fn get_prng(&mut self) -> Rc<RefCell<Prng>> {
        let enemy = self.enemy.borrow();
        enemy.prng.upgrade().unwrap()
    }

    fn run_instruction(&mut self, instruction: SubInstruction) {
        println!("Running instruction {:?}", instruction);
        match instruction {
            SubInstruction::Noop() => {
                // really
            }
            // 1
            SubInstruction::Destroy(_unused) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.removed = true;
            }
            // 2
            SubInstruction::RelativeJump(frame, ip) => {
                self.frame.frame = frame;
                // ip = ip + flag in th06
                self.frame.ip = ip;
                // we jump back to the main of the interpreter
            }
            // 3
            // GHIDRA SAYS THERE IS A COMPARISON_REG BUFFER BUT THERE IS NOT!!!
            //
            // MOV        ECX,dword ptr [EBP + 0x8]                     jumptable 00407544 case 31
            // CMP        dword ptr [0x9d4 + ECX],0x0
            // JLE        LAB_00407abb
            // aka ECX = enemy pointer
            // ECX->9d4 (aka enemy_pointer_copy->comparison_reg) == 0
            // only the pointer is copied, not the value, thus we are safe
            SubInstruction::RelativeJumpEx(frame, ip, var_id) => {
                // TODO: counter_value is a field of "enemy" in th06, to check
                let counter_value = self.get_i32(var_id) - 1;
                if counter_value > 0 {
                    self.run_instruction(SubInstruction::RelativeJump(frame, ip));
                }
            }
            // 4
            SubInstruction::SetInt(var_id, value) => {
                self.set_i32(var_id, value);
            }
            // 5
            SubInstruction::SetFloat(var_id, value) => {
                self.set_f32(var_id as f32, value);
            }
            // 6
            SubInstruction::SetRandomInt(var_id, maxval) => {
                let random = self.get_prng().borrow_mut().get_u32() as i32;
                self.set_i32(var_id, random % self.get_i32(maxval));
            }
            // 7
            SubInstruction::SetRandomIntMin(var_id, maxval, minval) => {
                let random = self.get_prng().borrow_mut().get_u32() as i32;
                self.set_i32(var_id, (random % self.get_i32(maxval)) + self.get_i32(minval));
            }
            // 8
            SubInstruction::SetRandomFloat(var_id, maxval) => {
                let random = self.get_prng().borrow_mut().get_f64() as f32;
                self.set_f32(var_id as f32, self.get_f32(maxval) * random)
            }
            // 9
            SubInstruction::SetRandomFloatMin(var_id, maxval, minval) => {
                let random = self.get_prng().borrow_mut().get_f64() as f32;
                self.set_f32(var_id as f32, self.get_f32(maxval) * random + self.get_f32(minval))
            }
            // 10
            SubInstruction::StoreX(var_id) => {
                let x = {
                    let enemy = self.enemy.borrow();
                    enemy.pos.x
                };
                // TODO: is this really an i32?
                self.set_i32(var_id, x as i32);
            }
            // 11
            SubInstruction::StoreY(var_id) => {
                let y = {
                    let enemy = self.enemy.borrow();
                    enemy.pos.y
                };
                self.set_i32(var_id, y as i32);
            }
            // 12
            SubInstruction::StoreZ(var_id) => {
                let z = {
                    let enemy = self.enemy.borrow();
                    enemy.z
                };
                self.set_i32(var_id, z as i32);
            }
            // 13(int), 20(float), same impl in th06
            SubInstruction::AddInt(var_id, a, b) => {
                self.set_i32(var_id, self.get_i32(a) + self.get_i32(b));
            }
            SubInstruction::AddFloat(var_id, a, b) => {
                self.set_f32(var_id as f32, self.get_f32(a) + self.get_f32(b));
            }
            // 14(int), 21(float), same impl in th06
            SubInstruction::SubstractInt(var_id, a, b) => {
                self.set_i32(var_id, self.get_i32(a) - self.get_i32(b));
            }
            SubInstruction::SubstractFloat(var_id, a, b) => {
                self.set_f32(var_id as f32, self.get_f32(a) - self.get_f32(b));
            }
            // 15(int), 22(unused)
            SubInstruction::MultiplyInt(var_id, a, b) => {
                self.set_i32(var_id, self.get_i32(a) * self.get_i32(b));
            }
            /*
            SubInstruction::MultiplyFloat(var_id, a, b) => {
                self.set_f32(var_id as f32, self.get_f32(a) * self.get_f32(b));
            }
            */
             // 16(int), 23(unused)
            SubInstruction::DivideInt(var_id, a, b) => {
                self.set_i32(var_id, self.get_i32(a) / self.get_i32(b));
            }

            SubInstruction::DivideFloat(var_id, a, b) => {
                self.set_f32(var_id as f32, self.get_f32(a) / self.get_f32(b));
            }

            // 17(int) 24(unused)
            SubInstruction::ModuloInt(var_id, a, b) => {
                self.set_i32(var_id, self.get_i32(a) % self.get_i32(b));
            }

            SubInstruction::ModuloFloat(var_id, a, b) => {
                self.set_f32(var_id as f32, self.get_f32(a) % self.get_f32(b));
            }

            // 18
            // setval used by pytouhou, but not in game(???)
            SubInstruction::Increment(var_id) => {
                self.set_i32(var_id, self.get_i32(var_id) + 1);
            }

            // 19
            SubInstruction::Decrement(var_id) => {
                self.set_i32(var_id, self.get_i32(var_id) - 1);
            }

            //25
            SubInstruction::GetDirection(var_id, x1, y1, x2, y2) => {
                //__ctrandisp2 in ghidra, let's assume from pytouhou it's atan2
                self.set_f32(var_id as f32, (self.get_f32(y2) - self.get_f32(y1)).atan2(self.get_f32(x2) - self.get_f32(x1)));
            }

            // 26
            SubInstruction::FloatToUnitCircle(var_id) => {
                // TODO: atan2(var_id, ??) is used by th06, maybe ?? is pi?
                // we suck at trigonometry so let's use pytouhou for now
                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);
            }

            // 27(int), 28(float)
            SubInstruction::CompareInts(a, b) => {
                let a = self.get_i32(a);
                let b = self.get_i32(b);
                if a < b {
                    self.frame.comparison_reg = -1;
                }
                else if  a == b {
                    self.frame.comparison_reg = 0;
                }
                else {
                    self.frame.comparison_reg = 1;
                }
            }
            SubInstruction::CompareFloats(a, b) => {
                let a = self.get_f32(a);
                let b = self.get_f32(b);
                if a < b {
                    self.frame.comparison_reg = -1;
                }
                else if  a == b {
                    self.frame.comparison_reg = 0;
                }
                else {
                    self.frame.comparison_reg = 1;
                }
            }
            // 29
            SubInstruction::RelativeJumpIfLowerThan(frame, ip) => {
                if self.frame.comparison_reg == -1 {
                    self.run_instruction(SubInstruction::RelativeJump(frame, ip));
                }
            }
            // 30
            SubInstruction::RelativeJumpIfLowerOrEqual(frame, ip) => {
                if self.frame.comparison_reg != 1 {
                    self.run_instruction(SubInstruction::RelativeJump(frame, ip));
                }
            }
            // 31
            SubInstruction::RelativeJumpIfEqual(frame, ip) => {
                if self.frame.comparison_reg == 0 {
                    self.run_instruction(SubInstruction::RelativeJump(frame, ip));
                }
            }
            // 32
            SubInstruction::RelativeJumpIfGreaterThan(frame, ip) => {
                if self.frame.comparison_reg == 1 {
                    self.run_instruction(SubInstruction::RelativeJump(frame, ip));
                }
            }
            // 33
            SubInstruction::RelativeJumpIfGreaterOrEqual(frame, ip) => {
                if self.frame.comparison_reg != -1 {
                    self.run_instruction(SubInstruction::RelativeJump(frame, ip));
                }
            }
            // 34
            SubInstruction::RelativeJumpIfNotEqual(frame, ip) => {
                if self.frame.comparison_reg != 0 {
                    self.run_instruction(SubInstruction::RelativeJump(frame, ip));
                }
            }
            // 35
            SubInstruction::Call(sub, param1, param2) => {
                self.stack.push(self.frame.clone());
                self.frame.sub = sub as u16;
                self.frame.ints1[0] = param1;
                self.frame.floats[0] = param2;
                self.frame.frame = 0;
                self.frame.ip = 0;
            }

            // 36
            SubInstruction::Return() => {
                self.frame = self.stack.pop().unwrap();
            }
            // 37
            SubInstruction::CallIfSuperior(sub, param1, param2, a, b) => {
                if self.get_i32(a) < self.get_i32(b) {
                    self.run_instruction(SubInstruction::Call(sub, param1, param2));
                }
            }
            // 38
            SubInstruction::CallIfSuperiorOrEqual(sub, param1, param2, a, b) => {
                if self.get_i32(a) <= self.get_i32(b) {
                    self.run_instruction(SubInstruction::Call(sub, param1, param2));
                }
            }
            // 39
            SubInstruction::CallIfEqual(sub, param1, param2, a, b) => {
                if self.get_i32(a) == self.get_i32(b) {
                    self.run_instruction(SubInstruction::Call(sub, param1, param2));
                }
            }
            // 40
            SubInstruction::CallIfInferior(sub, param1, param2, a, b) => {
                if self.get_i32(b) < self.get_i32(a) {
                    self.run_instruction(SubInstruction::Call(sub, param1, param2));
                }
            }

            // 41
            SubInstruction::CallIfInferiorOrEqual(sub, param1, param2, a, b) => {
                if self.get_i32(b) <= self.get_i32(a) {
                    self.run_instruction(SubInstruction::Call(sub, param1, param2));
                }
            }
            //42
            SubInstruction::CallIfNotEqual(sub, param1, param2, a, b) => {
                if self.get_i32(a) != self.get_i32(b) {
                    self.run_instruction(SubInstruction::Call(sub, param1, param2));
                }
            }

            // 43
            SubInstruction::SetPosition(x, y, z) => {
                let (x, y, z) = (self.get_f32(x), self.get_f32(y), self.get_f32(z));
                let mut enemy = self.enemy.borrow_mut();
                enemy.set_pos(x, y, z);
            }
            // 44
            /*
            SubInstruction::SetAngularSpeed(x, y, z) => {
                // same as above, except for angular speed
                let mut enemy = self.enemy.borrow_mut();
                enemy.set_angular_speed(self.get_f32(x), self.get_f32(y), self.get_f32(z));
            }
            */
            // 45
            SubInstruction::SetAngleAndSpeed(angle, speed) => {
                let angle = self.get_f32(angle);
                let speed = self.get_f32(speed);
                let mut enemy = self.enemy.borrow_mut();
                enemy.update_mode = 0;
                enemy.angle = angle;
                enemy.speed = speed;
            }
            // 46
            SubInstruction::SetRotationSpeed(speed) => {
                let rotation_speed = self.get_f32(speed);
                let mut enemy = self.enemy.borrow_mut();
                enemy.update_mode = 0;
                enemy.rotation_speed = rotation_speed;
            }
            // 47
            SubInstruction::SetSpeed(speed) => {
                let speed = self.get_f32(speed);
                let mut enemy = self.enemy.borrow_mut();
                enemy.update_mode = 0;
                enemy.speed = speed;
            }
            // 48
            SubInstruction::SetAcceleration(acceleration) => {
                let acceleration = self.get_f32(acceleration);
                let mut enemy = self.enemy.borrow_mut();
                enemy.update_mode = 0;
                enemy.acceleration = acceleration;
            }
            // 49
            SubInstruction::SetRandomAngle(min_angle, max_angle) => {
                let angle = self.get_prng().borrow_mut().get_f64() as f32 * (max_angle - min_angle) + min_angle;
                let mut enemy = self.enemy.borrow_mut();
                enemy.angle = angle;
            }
            // 51
            SubInstruction::TargetPlayer(delta_angle, speed) => {
                let speed = self.get_f32(speed);
                let mut enemy = self.enemy.borrow_mut();
                let game = enemy.game.upgrade().unwrap();
                let player = game.borrow().get_player();
                enemy.update_mode = 0;
                enemy.speed = speed;
                enemy.angle = enemy.get_angle_to(player) + delta_angle;
            }

            // 52 to 64 are different interlacing fields

            // 65
            // to note: in game a flag is set to enable the screenbox and is set by 66 to disable
            // it on top of setting our values. But we have a good engine and can detect if that's
            // changed without setting a flag :)
            SubInstruction::SetScreenBox(xmin, ymin, xmax, ymax) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.screen_box = Some((xmin, ymin, xmax, ymax));
            }
             // 66
            SubInstruction::ClearScreenBox() => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.screen_box = None;
            }

            // 67 to 75 are set bullet attributes and it seems a pain to reverse rn
            SubInstruction::SetBulletAttributes1(anim, sprite_index_offset, bullets_per_shot,
                                                 number_of_shots, speed, speed2, launch_angle,
                                                 angle, flags) => {
                gen_SetBulletAttributes!(self, 67, anim, sprite_index_offset, bullets_per_shot,
                                         number_of_shots, speed, speed2, launch_angle, angle,
                                         flags);
            }
            SubInstruction::SetBulletAttributes2(anim, sprite_index_offset, bullets_per_shot,
                                                 number_of_shots, speed, speed2, launch_angle,
                                                 angle, flags) => {
                gen_SetBulletAttributes!(self, 68, anim, sprite_index_offset, bullets_per_shot,
                                         number_of_shots, speed, speed2, launch_angle, angle,
                                         flags);
            }
            SubInstruction::SetBulletAttributes3(anim, sprite_index_offset, bullets_per_shot,
                                                 number_of_shots, speed, speed2, launch_angle,
                                                 angle, flags) => {
                gen_SetBulletAttributes!(self, 69, anim, sprite_index_offset, bullets_per_shot,
                                         number_of_shots, speed, speed2, launch_angle, angle,
                                         flags);
            }
            SubInstruction::SetBulletAttributes4(anim, sprite_index_offset, bullets_per_shot,
                                                 number_of_shots, speed, speed2, launch_angle,
                                                 angle, flags) => {
                gen_SetBulletAttributes!(self, 70, anim, sprite_index_offset, bullets_per_shot,
                                         number_of_shots, speed, speed2, launch_angle, angle,
                                         flags);
            }
            SubInstruction::SetBulletAttributes5(anim, sprite_index_offset, bullets_per_shot,
                                                 number_of_shots, speed, speed2, launch_angle,
                                                 angle, flags) => {
                gen_SetBulletAttributes!(self, 71, anim, sprite_index_offset, bullets_per_shot,
                                         number_of_shots, speed, speed2, launch_angle, angle,
                                         flags);
            }
            SubInstruction::SetBulletAttributes6(anim, sprite_index_offset, bullets_per_shot,
                                                 number_of_shots, speed, speed2, launch_angle,
                                                 angle, flags) => {
                gen_SetBulletAttributes!(self, 74, anim, sprite_index_offset, bullets_per_shot,
                                         number_of_shots, speed, speed2, launch_angle, angle,
                                         flags);
            }
            SubInstruction::SetBulletAttributes7(anim, sprite_index_offset, bullets_per_shot,
                                                 number_of_shots, speed, speed2, launch_angle,
                                                 angle, flags) => {
                gen_SetBulletAttributes!(self, 75, anim, sprite_index_offset, bullets_per_shot,
                                         number_of_shots, speed, speed2, launch_angle, angle,
                                         flags);
            }

            // 76
            SubInstruction::SetBulletInterval(interval) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.set_bullet_launch_interval(0, interval);
            }

            // 77
            SubInstruction::SetBulletIntervalEx(interval) => {
                let rand_start = self.get_prng().borrow_mut().get_u32();

                let mut enemy = self.enemy.borrow_mut();
                enemy.set_bullet_launch_interval(rand_start, interval);
            }

            // 78-79 are more interpolation flags
            // 78
            SubInstruction::DelayAttack() => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.delay_attack = true;
            }
            // 79
            SubInstruction::NoDelayAttack() => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.delay_attack = false;
            }
            // 80
            /*
            SubInstruction::NoClue() => {
                let mut enemy = self.enemy.borrow_mut();
                //bullet_pos = launch offset
                (enemy->bullet_attributes).bullets_per_shot = enemy.pos.x + enemy->bullet_pos.pos.x;
                (enemy->bullet_attributes).number_of_shots = enemy.pos.pos.y + enemy.bullet_pos.pos.y;
                (enemy->bullet_attributes).speed = enemy.z + bullet_pos.z;
                enemy.fire(bullet_attributes=bullet_attributes)
            }
            */

            // 81
            SubInstruction::SetBulletLaunchOffset(dx, dy, dz) => {
                let (dx, dy, dz) = (self.get_f32(dx), self.get_f32(dy), self.get_f32(dz));
                let mut enemy = self.enemy.borrow_mut();
                enemy.bullet_offset = Offset { dx, dy };
            }

            // 82
            SubInstruction::SetExtendedBulletAttributes(a, b, c, d, e, f, g, h) => {
                let (a, b, c, d) = (self.get_i32(a), self.get_i32(b), self.get_i32(c), self.get_i32(d));
                let (e, f, g, h) = (self.get_f32(e), self.get_f32(f), self.get_f32(g), self.get_f32(h));
                let mut enemy = self.enemy.borrow_mut();
                enemy.bullet_attributes.extended_attributes = (a, b, c, d, e, f, g, h);
            }

            // 83
            /*
            SubInstruction::ChangeBulletsIntoStarBonus() => {
                let mut game = self.game.borrow_mut();
                game.change_bullets_into_star_items();
            }
            */

            // 84
            SubInstruction::SetBulletSound(sound) => {
                let mut enemy = self.enemy.borrow_mut();
                if sound < 0 {
                    enemy.bullet_attributes.sound = None;
                } else {
                    // This assert isn’t part of the original engine, but it would crash on high
                    // values anyway.
                    assert!(sound <= 255);
                    enemy.bullet_attributes.sound = Some(sound as u8);
                }
            }

            // 85-86 ire newlaser functions

            // 87
            SubInstruction::SetUpcomingLaserId(laser_id) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.current_laser_id = laser_id;
            }

            // 88
            /*
            SubInstruction::AlterLaserAngle(laser_id, delta) => {
                let mut enemy = self.enemy.borrow_mut();
                if enemy.laser_by_id.contains_key(&laser_id) {
                    let mut laser = enemy.laser_by_id.get(&laser_id);
                    laser.angle += self.get_f32(delta);
                }
            }
            */

            // 89
            /*
            SubInstruction::AlterLaserAnglePlayer(laser_id, delta) => {
                let mut enemy = self.enemy.borrow_mut();
                if enemy.laser_by_id.contains_key(&laser_id) {
                    let mut laser = enemy.laser_by_id.get(laser_id);
                    let player = enemy.select_player();
                    laser.angle = enemy.get_angle(player) + angle;
                }
            }
            */

            // 90
            /*
            SubInstruction::RepositionLaser(laser_id, ox, oy, oz) => {
                let mut enemy = self.enemy.borrow_mut();
                if enemy.laser_by_id.contains_key(&laser_id) {
                    let mut laser = enemy.laser_by_id.get(&laser_id);
                    laser.set_base_pos(enemy.pos.x + ox, enemy.pos.y + oy, enemy.z + oz)
                }
            }
            */
            // 91
            // wat
            SubInstruction::LaserSetCompare(laser_id) => {
                let enemy = self.enemy.borrow_mut();
                // in game it checks if either the laser exists OR if one of its member is set to 0
                // which, uhhhh, we are not going to reimplement for obvious reasons
                // the correct implementation would be: if this laser does not exist have a
                // 1/100000 chance to continue, otherwise crash
                if enemy.laser_by_id.contains_key(&laser_id) {
                    // let's assume we gud
                    self.frame.comparison_reg = 1;
                }
                else{
                    self.frame.comparison_reg = 0;
                }
            }

            // 92
            /*
            SubInstruction::RepositionLaser(laser_id, ox, oy, oz) => {
                let mut enemy = self.enemy.borrow_mut();
                if enemy.laser_by_id.contains_key(&laser_id) {
                    let mut laser = enemy.laser_by_id.get(laser_id);
                    laser.cancel();
                }
            }
            */
            // 93
            // TODO: actually implement that hell
            SubInstruction::SetSpellcard(face, number, name) => {
                unimplemented!("spellcard start");

            }
            // 94
            SubInstruction::EndSpellcard() => {
                unimplemented!("spellcard end");

            }

            // 95
            SubInstruction::SpawnEnemy(sub, x, y, z, life, bonus, score) => {
                let x = self.get_f32(x);
                let y = self.get_f32(y);
                let _z = self.get_f32(z);
                let enemy = self.enemy.borrow_mut();
                let anm0 = enemy.anm0.upgrade().unwrap();
                let game = enemy.game.upgrade().unwrap();
                let enemy = Enemy::new(Position::new(x, y), life, bonus, score as u32, false, Rc::downgrade(&anm0), Rc::downgrade(&game));
                let ecl = self.ecl.clone().unwrap();
                let mut runner = EclRunner::new(&ecl, enemy, sub as u16);
                runner.run_frame();
            }

            // 96
            /*
            SubInstruction::KillEnemies() => {
                let mut game = self.game.borrow_mut();
                game.kill_enemies();
            }
            */



            // 97
            SubInstruction::SetAnim(index) => {
                // seems correct, game internally gets base_addr =(iVar13 + 0x1c934), pointer_addr = iVar14 * 4
                let mut enemy = self.enemy.borrow_mut();
                enemy.set_anim(index as u8);
            }
            // 98
            SubInstruction::SetMultipleAnims(default, end_left, end_right, left, right, _unused) => {
                // _unused was supposed to set movement_dependant_sprites, but internally the game
                // assigns it 0xff
                // TODO: THIS DOES NOT CALL set_anim. this only assigns all parameters to their
                // internal struct. To check if the anims are set somewhere else
                let mut enemy = self.enemy.borrow_mut();
                enemy.movement_dependant_sprites = if left == -1 {
                    None
                } else {
                    enemy.set_anim(default as u8);
                    Some((end_left as u8, end_right as u8, left as u8, right as u8))
                };
            }
            // 99
            SubInstruction::SetAuxAnm(number, script) => {
                assert!(number < 8);
                let mut enemy = self.enemy.borrow_mut();
                enemy.set_aux_anm(number, script);
            }

            // 100
            SubInstruction::SetDeathAnim(index) => {
                // TODO: takes 3 parameters in game as u8 unlike our single u32.
                // To reverse!
                let mut enemy = self.enemy.borrow_mut();
                enemy.death_anim = index;
            }
            // 101
            SubInstruction::SetBossMode(value) => {
                let enemy = self.enemy.borrow_mut();
                if value < 0 {
                    enemy.set_boss(false);
                }
                else {
                    // the boss pointer is written somewhere in memory and overwrote by a 0 when
                    // the boss mode is false, might want to look into that
                    enemy.set_boss(true);
                }
            }

            // 102
            // TODO: title says it all
            /*
            SubInstruction::ParticlesVoodooMagic(unk1, unk2, unk3, unk4, unk5) => {
            }
            */

            // 103
            SubInstruction::SetHitbox(width, height, depth) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.set_hitbox(width, height);
            }

            // 104
            SubInstruction::SetCollidable(collidable) => {
                // TODO: me and my siblings(105, 107, 117) are implemented as a single variable in the touhou 6
                // original engine. While our behaviour seems correct we might want to implement
                // that as a single variable
                // TODO[2]: THE BITFLAG MIGHT BE INCORRECT FOR OTHER SIBLING INSTRUCTIONS, the
                // behavior was DEFINITELY incorrect in pytouhou for SetTouchable at the very least
                let mut enemy = self.enemy.borrow_mut();
                enemy.collidable = (collidable&1) != 0;
            }

            // 105
            SubInstruction::SetDamageable(damageable) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.damageable = (damageable&1) != 0;
            }

            // 106
            SubInstruction::PlaySound(index) => {
                let enemy = self.enemy.borrow_mut();
                enemy.play_sound(index);
            }

            // 107
            SubInstruction::SetDeathFlags(death_flags) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.death_flags = death_flags;
            }
            // 108
            SubInstruction::SetDeathCallback(sub) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.death_callback = Some(sub);
            }

            // 109
            SubInstruction::MemoryWriteInt(value, index) => {
                unimplemented!("not again that damn foe corrupted my ret\\x41\\x41\\x41\\x41");
            }

            // 110
            /*
            SubInstruction::KillEnemy(enemy) => {
                let mut game = self.game.borrow_mut();
                game.kill_enemy(enemy);
            }
            */

            // 111
            /*
            SubInstruction::SetLife(value) => {
                let mut enemy = self.enemy.borrow_mut();
                let mut game = self.game.borrow_mut();
                enemy.life = value;
                game.interface.set_boss_life();
            }
            */
            // 112
            SubInstruction::SetElapsedTime(value) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.frame = value as u32;
            }
            // 113
            /*
            SubInstruction::SetLowLifeTrigger(value) => {
                let mut enemy = self.enemy.borrow_mut();
                let mut game = self.game.borrow_mut();
                enemy.low_life_trigger = value;
                game.interface.set_spell_life();
            }
            */
            // 114
            /*
             SubInstruction::SetLowLifeCallback(sub) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.low_life_callback.enable(self.switch_to_sub, (sub,));
            }
            */
            // 115
            /*
            SubInstruction::SetTimeout(timeout) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.frame = value;
                enemy.timeout = timeout;
            }
            */
            // 116
            /*
             SubInstruction::SetTimeoutCallback(sub) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.timeout_callback.enable(self.switch_to_sub, (sub,));
            }
            */


            // 117
            SubInstruction::SetTouchable(touchable) => {
                let mut enemy = self.enemy.borrow_mut();
                enemy.touchable = touchable != 0;
            }

            // 121
            // Here lies the Di Sword of sadness
            SubInstruction::CallSpecialFunction(function, arg) => {
                match function {
                    0 => {
                        let mut enemy = self.enemy.borrow_mut();
                        let game = enemy.game.upgrade().unwrap();
                        let mut game = game.borrow_mut();
                        //game.drop_particle(12, enemy.pos, 1, 0xffffffff);
                        //game.iter_bullets(|mut bullet| {
                        for bullet in game.bullets.iter() {
                            //game.new_effect(bullet.sprite, TODO);
                            let mut bullet = bullet.borrow_mut();
                            if arg == 0 {
                                bullet.speed = 0.;
                                bullet.dpos = [0., 0., 0.];
                            } else if arg == 1 {
                                bullet.flags |= 0x10;
                                bullet.frame = 220;
                                let rand_angle = game.prng.borrow_mut().get_f64() * 2. * std::f64::consts::PI - std::f64::consts::PI;
                                bullet.attributes[0] = (rand_angle.cos() * 0.01) as f32;
                                bullet.attributes[1] = (rand_angle.sin() * 0.01) as f32;
                            }
                        }
                    }
                    1 => {
                        let range_x = arg as f64;
                        let range_y = (arg as f32 * 0.75) as f64;
                        let rand_x = self.get_prng().borrow_mut().get_f64();
                        let rand_y = self.get_prng().borrow_mut().get_f64();
                        let mut enemy = self.enemy.borrow_mut();
                        let pos = [rand_x * range_x + enemy.pos.x as f64 - range_x / 2.,
                                   rand_y * range_y + enemy.pos.x as f64 - range_y / 2.];
                        enemy.bullet_attributes.fire();
                    }
                    3 => { // Patchouli’s dual sign spellcard selector
                        let mut enemy = self.enemy.borrow_mut();
                        let mut knowledge: [[i32; 3]; 4] =
                            [[0, 3, 1],
                             [2, 3, 4],
                             [1, 4, 0],
                             [4, 2, 3]];

                        //TODO: implement select_player and replace character by the correct one
                        //let character = enemy.select_player().character;
                        let character = 0;
                        for i in 1..=3 {
                            self.frame.ints1[i] = knowledge[character][i];
                        }
                    }
                    4 => { // Sakuya random daggers and time stop
                        /*
                        if arg < 2 {
                            drop_particle(&PARTICLES_ARRAY,0xc,enemy->pos,1,0xffffffff);
                            //TODO: is that the timestop?
                            LEVEL.field_0x2c = arg;
                            return;
                        }
                        // this changes the orientation of random bullets
                        let mut max_bullets = 0xe;
                        if (LEVEL.rank >= 2) {max_bullets = 0x34;}
                        i = 0;
                        for bullet in game.bullets {
                            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) {
                                bullet->field_0x5ba = 5;
                                new_effect(GAME_OBJECT,(sprite *)bullet, (int)bullet->sprites[0].sometimes_copy_of_UNK1 + (int)bullet->field_0x5ba);
                                x = bullet->pos[0] - PLAYER.pos[0];
                                y = bullet->pos[1] - PLAYER.pos[1];
                                if sqrt(x*x+y*y) > 128. {
                                    if LEVEL.rank >= 2 {bullet->automatic_angle = prng_double() * 2*pi;}
                                    else{bullet->automatic_angle = (prng_double() * ((pi/8)*6) + pi/4);}
                                    else {
                                        // TODO: check player_get_angle, might be what ST0 is
                                        player_get_angle_to(&PLAYER,bullet->pos,local_38);
                                        bullet->automatic_angle = (extraout_ST0_00 + pi/2 + (prng_double() * pi*2));
                                    }
                                    bullet->dpos[0] = cos(bullet->automatic_angle) * bullet->speed;
                                    bullet->dpos[1] = sin(bullet->automatic_angle) * bullet->speed;
                                    max_bullets -= 1;
                                    if (max_bullets == 0) break;
                                }
                            }
                            (enemy->ecl_frame).var_ints_1[2] = 0;*/
                        }
                    7 => { // Remilia's lazer maze
                        // so what this does is kinda complex: 2 rounds of 3 subrounds of 8 shots, either
                        // laser or standard bullets depending on the argument passed.
                        // it is done in 2 steps: first we precalculate coordinates of the 8 shots for the first subround
                        // set the shot properties depending on difficulties and current round and then
                        // edit  the coordinates for the next round
                        let rnd_pos = self.get_prng().borrow_mut().get_f64() * 2. * std::f64::consts::PI;
                        let enemy = self.enemy.borrow();
                        for i in 0..2 {
                            let mut pos: [f64; 8*3] = [0.; 8*3];
                            let mut offset = rnd_pos -((std::f64::consts::PI/8.)*7.);
                            let mut next_offset = -std::f64::consts::PI/4.;
                            if (i == 0) {
                                offset = rnd_pos -std::f64::consts::PI;
                                next_offset = std::f64::consts::PI/4.;
                            }

                            // we calculate angle, speed and offset for the 8 shots
                            let mut offset_copy=offset;
                            for y in 0..8 {
                                pos[y * 3] = offset_copy.cos() * 32. + enemy.pos.x as f64;
                                pos[y * 3 + 1] = offset_copy.sin() * 32. + enemy.pos.y as f64;
                                pos[y * 3 + 2] = enemy.z as f64;
                                offset_copy += std::f64::consts::PI/4.;
                            }

                            // 3 rounds of 8 shots
                            for z in 0..3 {

                                let mut length = 112.;
                                // last subround
                                if (z == 2) {length = 480.;}

                                for y in 0..8 {
                                    /*
                                    if (arg == 0) {
                                        let (mut si, mut ged, mut ed) = (8, 20.,ed=430.);
                                        if (LEVEL.rank < 2) {si=2; ged=28.; ed=length;}
                                        laser_args.angle = pos[y * 3];
                                        laser_args.speed = pos[y * 3 + 1];
                                        laser_args.start_offset = pos[y * 3 + 2];
                                        laser_args.type = 1;
                                        laser_args.sprite_index_offset = si;
                                        laser_args.end_offset = offset;
                                        laser_args.width = 0.;
                                        laser_args.duration = 0;
                                        laser_args.grazing_extra_duration = ged;
                                        laser_args.end_duration = ed;
                                        laser_args.UNK1 = z * 0x10 + 0x3c;
                                        laser_args.grazing_delay = laser_args.end_duration;
                                        fire_laser(&ETAMA_ARRAY,&laser_args);
                                    }
                                    else {
                                        (enemy->bullet_attributes).pos[0] = pos[y * 3];
                                        (enemy->bullet_attributes).pos[1] = pos[y*3+1];
                                        (enemy->bullet_attributes).pos[2] = pos[y*3+2];
                                        bullet_fire(&enemy->bullet_attributes,&ETAMA_ARRAY);
                                    }
                                    */
                                    pos[y * 3] = offset.cos() * length + pos[y * 3];
                                    pos[y * 3 + 1] = offset.sin() * length + pos[y * 3 + 1];
                                    offset = offset + std::f64::consts::PI/4.;
                                }
                                offset = (next_offset - 2.*std::f64::consts::PI) + offset;
                            }
                        }
                    }
                    8 => { // Vampire Fantasy
                        let n = {
                            let enemy = self.enemy.borrow();
                            let game = enemy.game.upgrade().unwrap();
                            let mut game = game.borrow_mut();
                            let mut n = 0;
                            for bullet in game.bullets.iter() {
                                let mut bullet = bullet.borrow();
                                // TODO: uncomment that one.
                                if bullet.state != 0 && bullet.state != 5 /* && (30. <= (bullet.sprites[0].additional_infos).height) */ {
                                    let prng = enemy.prng.upgrade().unwrap();
                                    let random = prng.borrow_mut().get_f64();
                                    let launch_angle = (random * (2. * std::f64::consts::PI) - std::f64::consts::PI) as f32;
                                    let mut attribs = BulletAttributes {
                                        // TODO: check if the z value of this pos is really used.
                                        pos: bullet.pos,
                                        anim: 3,
                                        sprite_index_offset: 1,
                                        launch_angle,
                                        speed: 0.,
                                        angle: 0.,
                                        speed2: 0.,
                                        bullets_per_shot: 1,
                                        number_of_shots: 1,
                                        flags: 8,
                                        bullet_type: 1,
                                        extended_attributes: Default::default(),
                                        sound: None,
                                    };
                                    attribs.fire();
                                    n += 1
                                }
                            }
                            n
                        };
                        //TODO: this variable might not always be correct! it uses the argument in
                        //th06: *(int *)(param_1 + 0x9b0) = local_60;
                        self.set_i32(-10004, n);
                    }

                    9 => {
                        let mut rnd = self.get_prng().borrow_mut().get_f64();
                        //TODO: the game does that
                        //drop_particle(&PARTICLES_ARRAY,0xc,enemy->pos,1,0xffffffff);
                        //self._game.new_effect((enemy.x, enemy.y), 17)
                        /*
                        for bullet in game.bullets {
                            if bullet._bullet_type.state != 0 && bullet._bullet_type.state != 5 && (30. <= (bullet.sprites[0].additional_infos)->height) && bullet.speed == 0. {
                                bullet.flags = bullet.flags | 0x10;
                                //TODO: reverse this field and effect
                                bullet->field_0x5ba = 2;
                                new_effect(GAME_OBJECT,(sprite *)bullet, (int)bullet->sprites[0].sometimes_copy_of_UNK1 + (int)bullet->field_0x5ba);
                                bullet.speed=0.01;
                                bullet.frame=0x78;

                                let mut dx = bullet.x - enemy.x;
                                let mut distance = dx.hypot(bullet.y - enemy.y);

                                if distance > 0.01 {
                                    distance = sqrt(distance);
                                }else{distance = 0.;}
                                let mut angle = (distance * std::f64::consts::PI) / 256. + (rnd * (2*std::f64::consts::PI) - std::f64::consts::PI);
                                bullet->attributes[0] = cos(angle) * 0.01000000;
                                bullet->attributes[1] = sin(angle) * 0.01000000;
                            }
                        }
                        */
                    }
                    11 => {
                        self.get_prng().borrow_mut().get_f64();
                        //TODO: the game does that
                        //drop_particle(&PARTICLES_ARRAY,0xc,enemy->pos,1,0xffffffff);
                        //self._game.new_effect((enemy.x, enemy.y), 17)
                        /*
                        for bullet in game.bullets {
                            if bullet._bullet_type.state != 0 && bullet._bullet_type.state != 5 && (30. <= (bullet.sprites[0].additional_infos)->height) && bullet.speed == 0. {
                                bullet.flags = bullet.flags | 0x10;
                                //TODO: reverse this field and effect
                                bullet->field_0x5ba = 2;
                                new_effect(GAME_OBJECT,(sprite *)bullet, (int)bullet->sprites[0].sometimes_copy_of_UNK1 + (int)bullet->field_0x5ba);
                                bullet.speed=0.01;
                                bullet.frame=0x78;
                                let mut angle = self.get_prng().borrow_mut().get_f64() * (2*std::f64::consts::PI) - std::f64::consts::PI;
                                bullet->attributes[0] = cos(angle) * 0.01000000;
                                bullet->attributes[1] = sin(angle) * 0.01000000;


                            }
                        }
                        */
                    }
                    13 => {
                        if self.frame.ints1[3] % 6 == 0 {
                            let mut _angle=self.frame.floats[2];
                            /*
                            (type_, anim, sprite_idx_offset, bullets_per_shot, number_of_shots,
                            speed, speed2, launch_angle, angle, flags) = self._enemy.bullet_attributes
                            for i in range(arg) {
                                //TODO: distance is obtained directly by copying bullet attributes
                                //in memory
                                launch_pos = (192 + cos(_angle) * _distance,
                                    224 + sin(_angle) * _distance);

                                bullet_attributes = (type_, anim, sprite_idx_offset,
                                     bullets_per_shot, number_of_shots,
                                     speed, speed2,
                                     _angle + self.frame.floats[1], angle, flags);
                                enemy.fire(launch_pos=launch_pos,bullet_attributes=bullet_attributes);
                                _angle += 2*std::f64::consts::PI/arg;
                            }*/
                        }
                        self.frame.ints1[3] += 1;
                    }
                    14 => { // Lävatein
                        let mut enemy = self.enemy.borrow_mut();
                        self.frame.ints1[3] = 0;
                        for laser in enemy.laser_by_id.values() {
                            //for pos in laser.get_bullets_pos(){
                            //TODO: the game checks for laser end_offset before firing
                            //  enemy.fire(launch_pos=pos);
                            //}
                            self.frame.ints1[3] += 1;
                        }
                    }
                    16 => { // QED: Ripples of 495 years
                        let mut enemy = self.enemy.borrow_mut();
                        let game = enemy.game.upgrade().unwrap();
                        let mut game = game.borrow_mut();
                        if arg == 0 {
                            self.frame.floats[3] = 2. - (enemy.life as f32) / 6000.;
                            self.frame.ints2[1] = ((enemy.life * 240) / 6000 + 40) as i32;
                        } else {
                            let fx = (320. - ((enemy.life as f32) * 160.) / 6000.) as f64;
                            let fy = (128. - ((enemy.life as f32) * 64.) / 6000.) as f64;
                            let rand_x = game.prng.borrow_mut().get_f64();
                            let rand_y = game.prng.borrow_mut().get_f64();
                            self.frame.floats[2] = (rand_x * fx + (192. - fx / 2.)) as f32;
                            self.frame.floats[3] = (rand_y * fy + (96. - fy / 2.)) as f32;
                        }
                    }
                    _ => unimplemented!("Special function {:?} not found!", function)
                }
            }

            // 122
            // Here lies the Di Sword of despair
            SubInstruction::SetSpecialFunctionCallback(function) => {
                //TODO: those functions are executed at each frame and needs to be written to the
                //callback function but so far i'm simply focusing on the implementation
                //NB: the original engine doesn't differenciate between function blocks for ins 121
                //and 122 but we do here, since it wouldn't make sense the other way around.
                match function {
                    12 => {
                        for i in 0..8 {
                            /*
                            if ((enemy->lasers[i] != (laser *)0x0) && (enemy->lasers[i]->state != 0)) {
                                (enemy->bullet_attributes).pos[0] = cos(enemy->lasers[i]->angle) * 64. + enemy.pos.x;
                                // yes, it reads pos[0] after it has been modified and yes, this most
                                // likely is a bug
                                (enemy->bullet_attributes).pos[1] = cos(enemy->lasers[i]->angle) * (enemy->bullet_attributes).pos[0] + enemy.pos.y;
                                (enemy->bullet_attributes).pos[2] = enemy.z;
                                bullet_fire(&enemy->bullet_attributes,&ETAMA_ARRAY);
                            }*/
                        }
                    }
                    _ => unimplemented!("Special function {:?} not found!", function)
                }
            }
            _ => unimplemented!("{:?}", instruction)
        }

    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::th06::anm0::Anm0;
    use crate::th06::ecl::{Sub, CallSub, Rank};
    use crate::th06::enemy::Game;
    use std::io::{self, Read};
    use std::fs::File;

    fn setup() -> (Rc<RefCell<Game>>, Rc<RefCell<Enemy>>) {
        let file = File::open("EoSD/ST/stg1enm.anm").unwrap();
        let mut file = io::BufReader::new(file);
        let mut buf = vec![];
        file.read_to_end(&mut buf).unwrap();
        let (_, mut anms) = Anm0::from_slice(&buf).unwrap();
        let anm0 = anms.pop().unwrap();
        let anm0 = Rc::new(RefCell::new(anm0));
        let prng = Rc::new(RefCell::new(Prng::new(0)));
        let game = Game::new(prng, Rank::EASY);
        let game = Rc::new(RefCell::new(game));
        let enemy = Enemy::new(Position::new(0., 0.), 500, 0, 640, Rc::downgrade(&anm0), Rc::downgrade(&game));
        (game, enemy)
    }

    #[test]
    fn call_and_return() {
        let (game, enemy) = setup();
        let ecl = Ecl { mains: vec![], subs: vec![
            Sub { instructions: vec![
                CallSub::new(0, Rank::EASY, SubInstruction::Call(1, 13, 12.)),
            ]},
            Sub { instructions: vec![
                CallSub::new(0, Rank::EASY, SubInstruction::Noop()),
                CallSub::new(1, Rank::EASY, SubInstruction::Return()),
            ]},
        ]};
        let mut ecl_runner = EclRunner::new(&ecl, enemy, 0);
        ecl_runner.run_frame();
        assert_eq!(ecl_runner.frame.ints1[0], 13);
        assert_eq!(ecl_runner.frame.floats[0], 12.);
        assert_eq!(ecl_runner.stack.len(), 1);
        ecl_runner.run_frame();
        assert_eq!(ecl_runner.frame.ints1[0], 0);
        assert_eq!(ecl_runner.frame.floats[0], 0.);
        assert_eq!(ecl_runner.stack.len(), 0);
    }
}