Mercurial > touhou
view src/th06/ecl_vm.rs @ 695:f5b34a1c2707
ecl_vm: add a test for Call and Return.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Fri, 23 Aug 2019 02:30:57 +0200 |
parents | 14fddc27e6f5 |
children | 7ae576a418ff |
line wrap: on
line source
//! ECL runner. use crate::th06::ecl::{Ecl, SubInstruction}; use crate::th06::enemy::{Enemy, BulletAttributes, Offset}; use crate::util::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); }}; } type Variables = ([i32; 4], [f32; 4], [i32; 4]); /// Interpreter for enemy scripts. #[derive(Default)] pub struct EclRunner { enemy: Rc<RefCell<Enemy>>, ecl: Option<Ecl>, sub: u8, /// XXX pub running: bool, /// XXX pub frame: i32, ip: i32, variables: Variables, comparison_reg: i8, stack: Vec<Variables>, } impl EclRunner { /// Create a new ECL runner. pub fn new(ecl: &Ecl, enemy: Rc<RefCell<Enemy>>, sub: u8) -> EclRunner { EclRunner { enemy, // XXX: no clone. ecl: Some(ecl.clone()), sub, running: true, ..Default::default() } } /// 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.sub as usize]; let call = match sub.instructions.get(self.ip as usize) { Some(call) => call, None => { self.running = false; break; } }; if call.time > self.frame { break; } self.ip += 1; let rank = self.enemy.borrow().get_rank(); if (call.rank_mask & rank).is_empty() { continue; } if call.time == self.frame { self.run_instruction(call.instr.clone()); } } self.frame += 1; } fn get_i32(&self, var: i32) -> i32 { let enemy = self.enemy.borrow(); match var { -10001 => self.variables.0[0], -10002 => self.variables.0[1], -10003 => self.variables.0[2], -10004 => self.variables.0[3], -10005 => self.variables.1[0] as i32, -10006 => self.variables.1[1] as i32, -10007 => self.variables.1[2] as i32, -10008 => self.variables.1[3] as i32, -10009 => self.variables.2[0], -10010 => self.variables.2[1], -10011 => self.variables.2[2], -10012 => self.variables.2[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.variables.0[0] as f32, -10002.0 => self.variables.0[1] as f32, -10003.0 => self.variables.0[2] as f32, -10004.0 => self.variables.0[3] as f32, -10005.0 => self.variables.1[0], -10006.0 => self.variables.1[1], -10007.0 => self.variables.1[2], -10008.0 => self.variables.1[3], -10009.0 => self.variables.2[0] as f32, -10010.0 => self.variables.2[1] as f32, -10011.0 => self.variables.2[2] as f32, -10012.0 => self.variables.2[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.variables.0[0] = value, -10002 => self.variables.0[1] = value, -10003 => self.variables.0[2] = value, -10004 => self.variables.0[3] = value, -10005 => unimplemented!(), -10006 => unimplemented!(), -10007 => unimplemented!(), -10008 => unimplemented!(), -10009 => self.variables.2[0] = value, -10010 => self.variables.2[1] = value, -10011 => self.variables.2[2] = value, -10012 => self.variables.2[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.variables.1[0] = value, -10006.0 => self.variables.1[1] = value, -10007.0 => self.variables.1[2] = value, -10008.0 => self.variables.1[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; // ip = ip + flag in th06 self.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 { 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.comparison_reg = -1; } else if a == b { self.comparison_reg = 0; } else { self.comparison_reg = 1; } } SubInstruction::CompareFloats(a, b) => { let a = self.get_f32(a); let b = self.get_f32(b); if a < b { self.comparison_reg = -1; } else if a == b { self.comparison_reg = 0; } else { self.comparison_reg = 1; } } // 29 SubInstruction::RelativeJumpIfLowerThan(frame, ip) => { if self.comparison_reg == -1 { SubInstruction::RelativeJump(frame, ip); } } // 30 SubInstruction::RelativeJumpIfLowerOrEqual(frame, ip) => { if self.comparison_reg != 1 { SubInstruction::RelativeJump(frame, ip); } } // 31 SubInstruction::RelativeJumpIfEqual(frame, ip) => { if self.comparison_reg == 0 { SubInstruction::RelativeJump(frame, ip); } } // 32 SubInstruction::RelativeJumpIfGreaterThan(frame, ip) => { if self.comparison_reg == 1 { SubInstruction::RelativeJump(frame, ip); } } // 33 SubInstruction::RelativeJumpIfGreaterOrEqual(frame, ip) => { if self.comparison_reg != -1 { SubInstruction::RelativeJump(frame, ip); } } // 34 SubInstruction::RelativeJumpIfNotEqual(frame, ip) => { if self.comparison_reg != 0 { SubInstruction::RelativeJump(frame, ip); } } // 35 SubInstruction::Call(sub, param1, param2) => { // does insane stuff with the stack, not implemented unimplemented!() } // 36 SubInstruction::Return() => { // does insane stuff with the stack, not implemented unimplemented!() } // 37 SubInstruction::CallIfSuperior(sub, param1, param2, a, b) => { if self.get_i32(a) < self.get_i32(b) { SubInstruction::Call(sub, param1, param2); } } // 38 SubInstruction::CallIfSuperiorOrEqual(sub, param1, param2, a, b) => { if self.get_i32(a) <= self.get_i32(b) { SubInstruction::Call(sub, param1, param2); } } // 39 SubInstruction::CallIfEqual(sub, param1, param2, a, b) => { if self.get_i32(a) == self.get_i32(b) { SubInstruction::Call(sub, param1, param2); } } // 40 SubInstruction::CallIfInferior(sub, param1, param2, a, b) => { if self.get_i32(b) < self.get_i32(a) { SubInstruction::Call(sub, param1, param2); } } // 41 SubInstruction::CallIfInferiorOrEqual(sub, param1, param2, a, b) => { if self.get_i32(b) <= self.get_i32(a) { SubInstruction::Call(sub, param1, param2); } } //42 SubInstruction::CallIfNotEqual(sub, param1, param2, a, b) => { if self.get_i32(a) != self.get_i32(b) { SubInstruction::Call(sub, param1, param2); } } // 43 SubInstruction::SetPosition(x, y, z) => { let mut enemy = self.enemy.borrow_mut(); enemy.set_pos(self.get_f32(x), self.get_f32(y), self.get_f32(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 // WARNING: dead code. If the parameter is < 0 it unsets a bullet_flag, otherwise it // sets it, never using the parameter ever /* SubInstruction::UNK_ins84() => { let mut enemy = self.enemy.borrow_mut(); enemy.bullet_flag_something } */ // 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 mut 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.comparison_reg = 1; } else{ self.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::PopEnemy(sub, x, y, z, life, bonus_dropped, die_score) => { self._pop_enemy(sub, 0, self.get_f32(x), self.get_f32(y), self.get_f32(z), life, bonus_dropped, die_score) } */ // 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::SetAuxAnims(number, script) => { assert!(7 < number); 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 mut 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 mut 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.enable(self.switch_to_sub, (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) => { unimplemented!("spellcards are a bitch and a half"); } _ => 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, Position}; 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 anm0 = Anm0::from_slice(&buf).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); } }