diff --git a/src/engine_constants/npcs.rs b/src/engine_constants/npcs.rs index 10b4c62..c0f732f 100644 --- a/src/engine_constants/npcs.rs +++ b/src/engine_constants/npcs.rs @@ -993,14 +993,14 @@ pub struct NPCConsts { #[serde(default = "default_n341_ballos_1_head")] pub n341_ballos_1_head: [Rect; 3], - #[serde(default = "default_n342_ballos_1_eye")] - pub n342_ballos_1_eye: [Rect; 3], + #[serde(default = "default_n342_ballos_orbiting_eye")] + pub n342_ballos_orbiting_eye: [Rect; 3], - #[serde(default = "default_n343_ballos_2_cutscene")] - pub n343_ballos_2_cutscene: Rect, + #[serde(default = "default_n343_ballos_3_cutscene")] + pub n343_ballos_3_cutscene: Rect, - #[serde(default = "default_n344_ballos_2_eyes")] - pub n344_ballos_2_eyes: [Rect; 2], + #[serde(default = "default_n344_ballos_3_eyes")] + pub n344_ballos_3_eyes: [Rect; 2], #[serde(default = "default_n345_ballos_skull_projectile")] pub n345_ballos_skull_projectile: [Rect; 4], @@ -4453,7 +4453,7 @@ fn default_n341_ballos_1_head() -> [Rect; 3] { ] } -fn default_n342_ballos_1_eye() -> [Rect; 3] { +fn default_n342_ballos_orbiting_eye() -> [Rect; 3] { [ Rect { left: 240, top: 48, right: 280, bottom: 88 }, Rect { left: 240, top: 88, right: 280, bottom: 128 }, @@ -4461,11 +4461,11 @@ fn default_n342_ballos_1_eye() -> [Rect; 3] { ] } -fn default_n343_ballos_2_cutscene() -> Rect { +fn default_n343_ballos_3_cutscene() -> Rect { Rect { left: 0, top: 0, right: 120, bottom: 120 } } -fn default_n344_ballos_2_eyes() -> [Rect; 2] { +fn default_n344_ballos_3_eyes() -> [Rect; 2] { [Rect { left: 272, top: 0, right: 296, bottom: 16 }, Rect { left: 296, top: 0, right: 320, bottom: 16 }] } @@ -4567,10 +4567,12 @@ fn default_n352_ending_characters() -> [Rect; 28] { fn default_n353_bute_sword_flying() -> [Rect; 8] { [ + // Entering Rect { left: 168, top: 160, right: 184, bottom: 184 }, Rect { left: 184, top: 160, right: 200, bottom: 184 }, Rect { left: 168, top: 184, right: 184, bottom: 208 }, Rect { left: 184, top: 184, right: 200, bottom: 208 }, + // Flying Rect { left: 200, top: 160, right: 216, bottom: 176 }, Rect { left: 216, top: 160, right: 232, bottom: 176 }, Rect { left: 200, top: 176, right: 216, bottom: 192 }, diff --git a/src/npc/ai/balrog.rs b/src/npc/ai/balrog.rs index 9428b3e..cefa9d9 100644 --- a/src/npc/ai/balrog.rs +++ b/src/npc/ai/balrog.rs @@ -1312,4 +1312,58 @@ impl NPC { Ok(()) } + + pub(crate) fn tick_n356_balrog_rescuing(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult { + match self.action_num { + 0 | 11 => { + if self.action_num == 0 { + self.action_num = 11; + self.anim_counter = 0; + self.target_x = self.x - 0xC00; + self.target_y = self.y - 0x2000; + self.vel_y = 0; + let mut npc = NPC::create(355, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + npc.direction = Direction::Bottom; + let _ = npc_list.spawn(0xAA, npc.clone()); + npc.direction = Direction::Right; + let _ = npc_list.spawn(0xAA, npc); + } + + self.vel_x += 8 * if self.x < self.target_x { 1 } else { -1 }; + self.vel_y += 8 * if self.y < self.target_y { 1 } else { -1 }; + + self.x += self.vel_x; + self.y += self.vel_y; + } + 20 | 21 => { + if self.action_num == 20 { + self.action_num = 21; + self.vel_x = -0x400; + self.vel_y = 0x200; + } + self.anim_counter += 1; + self.vel_x += 16; + self.vel_y -= 8; + self.x += self.vel_x; + self.y += self.vel_y; + + if self.x > 0x78000 { + self.action_num = 22; + } + } + 22 => { + self.vel_x = 0; + self.vel_y = 0; + } + _ => (), + } + + self.animate(4, 0, 1); + + self.anim_rect = state.constants.npc.n356_balrog_rescuing[self.anim_num as usize]; + + Ok(()) + } } diff --git a/src/npc/ai/misc.rs b/src/npc/ai/misc.rs index b000e71..8faf5c9 100644 --- a/src/npc/ai/misc.rs +++ b/src/npc/ai/misc.rs @@ -2364,6 +2364,38 @@ impl NPC { Ok(()) } + pub(crate) fn tick_n334_sweat(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult { + let player = self.get_closest_player_mut(players); + + match self.action_num { + 0 | 10 => { + if self.action_num == 0 { + self.action_num = 10; + if self.direction == Direction::Left { + self.x += 0x1400; + self.y -= 0x2400; + } else { + self.x = player.x - 0x1400; + self.y = player.y - 0x400; + } + } + + self.action_counter += 1; + self.anim_num = if self.action_counter / 8 % 2 != 0 { 0 } else { 1 }; + + if self.action_counter >= 64 { + self.cond.set_alive(false); + } + } + _ => (), + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; + self.anim_rect = state.constants.npc.n334_sweat[self.anim_num as usize + dir_offset]; + + Ok(()) + } + pub(crate) fn tick_n349_statue(&mut self, state: &mut SharedGameState) -> GameResult { if self.action_num == 0 { self.action_num = 1; diff --git a/src/npc/boss/ballos.rs b/src/npc/boss/ballos.rs index f98caaf..cf8b84d 100644 --- a/src/npc/boss/ballos.rs +++ b/src/npc/boss/ballos.rs @@ -432,8 +432,7 @@ impl NPC { self.vel_y = 0x5FF; } - self.anim_counter += 1; - self.anim_num = if self.anim_counter & 0x02 != 0 { 4 } else { 5 }; + self.animate(1, 4, 5); if self.flags.hit_bottom_wall() { self.action_num = 242; @@ -515,8 +514,7 @@ impl NPC { self.action_num = 240; } - self.anim_counter += 1; - self.anim_num = if self.anim_counter & 0x02 != 0 { 4 } else { 5 }; + self.animate(1, 4, 5); } 1000 | 1001 => { if self.action_num == 1000 { @@ -570,9 +568,7 @@ impl NPC { flash.set_blink(); state.sound_manager.play_sfx(29); } - - self.anim_counter += 1; - self.anim_num = if self.anim_counter & 0x02 != 0 { 8 } else { 9 }; + self.animate(1, 8, 9); } _ => (), } @@ -609,7 +605,7 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n342_ballos_1_eye( + pub(crate) fn tick_n342_ballos_orbiting_eye( &mut self, state: &mut SharedGameState, players: [&mut Player; 2], @@ -890,10 +886,514 @@ impl NPC { self.x += self.vel_x; self.y += self.vel_y; - self.anim_rect = state.constants.npc.n342_ballos_1_eye[self.anim_num as usize]; + self.anim_rect = state.constants.npc.n342_ballos_orbiting_eye[self.anim_num as usize]; Ok(()) } + + pub(crate) fn tick_n343_ballos_3_cutscene( + &mut self, + state: &mut SharedGameState, + boss: &mut BossNPC, + ) -> GameResult { + self.action_counter += 1; + if self.action_counter > 100 { + self.cond.set_alive(false); + } + + self.x = boss.parts[0].x; + self.y = boss.parts[0].y; + + self.anim_rect = state.constants.npc.n343_ballos_3_cutscene; + + Ok(()) + } + + pub(crate) fn tick_n344_ballos_3_eyes(&mut self, state: &mut SharedGameState, boss: &mut BossNPC) -> GameResult { + self.action_counter += 1; + if self.action_counter > 100 { + self.cond.set_alive(false); + } + + self.x = boss.parts[0].x + (0x3000 * self.direction.vector_x()); + self.y = boss.parts[0].y - 0x4800; + + self.anim_rect = state.constants.npc.n344_ballos_3_eyes[if self.direction == Direction::Left { 0 } else { 1 }]; + + Ok(()) + } + + pub(crate) fn tick_n345_ballos_skull_projectile( + &mut self, + state: &mut SharedGameState, + npc_list: &NPCList, + stage: &mut Stage, + ) -> GameResult { + match self.action_num { + 0 | 100 => { + if self.action_num == 0 { + self.action_num = 100; + self.anim_num = (self.rng.range(0..16) % 4) as u16; + } + self.vel_y += 0x40; + if self.vel_y > 0x700 { + self.vel_y = 0x700; + } + + if self.y > 0x10000 { + self.npc_flags.set_ignore_solidity(false); + } + + self.action_counter += 1; + if self.action_counter & 0x02 != 0 { + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x; + npc.y = self.y; + let _ = npc_list.spawn(0x100, npc); + } + + if self.flags.hit_bottom_wall() { + self.action_num = 110; + self.vel_y = -0x200; + self.npc_flags.set_ignore_solidity(true); + state.sound_manager.play_sfx(12); + state.quake_counter = 10; + + for _ in 0..4 { + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x + self.rng.range(-12..12) * 0x200; + npc.y = self.y + 0x2000; + npc.vel_x = self.rng.range(-0x155..0x155); + npc.vel_y = self.rng.range(-0x600..0); + let _ = npc_list.spawn(0x100, npc); + } + } + } + 110 => { + self.vel_y += 0x40; + + if self.y > (stage.map.height as i32) * state.tile_size.as_int() * 0x200 + 0x4000 { + self.cond.set_alive(false); + return Ok(()); + } + } + _ => (), + } + + self.animate(8, 0, 3); + + self.y += self.vel_y; + + self.anim_rect = state.constants.npc.n345_ballos_skull_projectile[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n346_ballos_orbiting_platform( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + stage: &mut Stage, + boss: &mut BossNPC, + ) -> GameResult { + if self.action_num < 1000 && boss.parts[0].action_num >= 1000 { + self.action_num = 1000; + } + + match self.action_num { + 0 | 10 => { + if self.action_num == 0 { + self.action_num = 10; + self.action_counter3 = 192; + } + if self.action_counter3 >= 448 { + self.action_num = 11; + } else { + self.action_counter3 += 8; + } + } + 11 => { + if boss.parts[0].action_num == 411 { + self.action_num = 20; + } + } + 20 => { + if self.action_counter2 > 0 { + self.action_counter2 -= 1; + } else { + self.action_counter2 += 0x400; + } + + if boss.parts[0].action_num == 421 { + self.action_num = 40; + } else if boss.parts[0].action_num == 423 { + self.action_num = 100; + } + } + 30 => { + self.action_counter2 += 1; + self.action_counter2 %= 0x400; + + if boss.parts[0].action_num == 425 { + self.action_num = 50; + } else if boss.parts[0].action_num == 427 { + self.action_num = 100; + } + } + 40 => { + if self.action_counter2 > 1 { + self.action_counter2 -= 2; + } else { + self.action_counter2 += 0x400; + } + + if boss.parts[0].action_num == 422 { + self.action_num = 20; + } + } + 50 => { + self.action_counter2 += 2; + self.action_counter2 %= 0x400; + + if boss.parts[0].action_num == 426 { + self.action_num = 30; + } + } + 100 => { + if boss.parts[0].action_num == 424 { + self.action_num = 30; + } else if boss.parts[0].action_num == 428 { + self.action_num = 20; + } + } + 1000 | 1001 => { + if self.action_num == 1000 { + self.action_num = 1001; + self.vel_x = 0; + self.vel_y = 0; + self.npc_flags.set_solid_hard(false); + } + self.vel_y += 0x40; + + if self.y > (stage.map.height as i32) * state.tile_size.as_int() * 0x200 { + self.cond.set_alive(false); + } + } + _ => (), + } + + if self.action_num < 1000 { + let player = self.get_closest_player_mut(players); + + if player.y > self.y - 0x1000 && player.vel_y < 0 { + self.npc_flags.set_solid_hard(false); + } else { + self.npc_flags.set_solid_hard(true); + } + + let deg = (self.action_counter2 as f64 / 4.0) * CDEG_RAD; + self.target_x = boss.parts[0].x + (self.action_counter3 as f64 * deg.cos() * 128.0) as i32; + self.target_y = boss.parts[0].y + (self.action_counter3 as f64 * deg.sin() * 128.0) as i32 + 0x2000; + + self.vel_x = self.target_x - self.x; + + match self.action_num { + 20 | 30 => { + if self.action_counter2 % 4 == 0 { + self.vel_y = (self.target_y - self.y) / 4; + } + } + 40 | 50 => { + if self.action_counter2 & 0x02 == 0 { + self.vel_y = (self.target_y - self.y) / 2; + } + } + _ => { + self.vel_y = self.target_y - self.y; + } + } + } else { + self.vel_y = 0; + } + + self.x += self.vel_x; + self.y += self.vel_y; + + self.anim_rect = state.constants.npc.n346_ballos_orbiting_platform; + + Ok(()) + } + + pub(crate) fn tick_n348_ballos_4_spikes(&mut self, state: &mut SharedGameState) -> GameResult { + match self.action_num { + 0 | 1 => { + self.action_num = 1; + + self.action_counter += 1; + if self.action_counter < 128 { + self.y -= 0x80; + + self.animate(1, 0, 1); + } else { + self.action_num = 10; + self.anim_num = 0; + self.damage = 2; + } + } + _ => (), + } + self.anim_rect = state.constants.npc.n348_ballos_4_spikes[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n350_flying_bute_archer( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + stage: &mut Stage, + ) -> GameResult { + let player = self.get_closest_player_mut(players); + + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.target_x = self.x + 0x10000 * self.direction.vector_x(); + self.target_y = self.y; + self.vel_x = self.rng.range(-0x200..0x200) * 2; + self.vel_y = self.rng.range(-0x200..0x200) * 2; + } + + self.animate(1, 0, 1); + + // The equal to target case is missing + if self.direction == Direction::Left && self.x < self.target_x { + self.action_num = 20; + } + if self.direction != Direction::Left && self.x > self.target_x { + self.action_num = 20; + } + } + 20 | 21 => { + if self.action_num == 20 { + self.action_num = 21; + self.action_counter = self.rng.range(0..150) as u16; + self.anim_num = 2; + self.anim_counter = 0; + } + + self.animate(2, 2, 3); + + self.action_counter += 1; + if self.action_num > 300 { + self.action_num = 30; + } + if player.x < self.x + 0xE000 + && player.x > self.x - 0xE000 + && player.y < self.y + 0x2000 + && player.y > self.y - 0x2000 + { + self.action_num = 30; + } + } + 30 | 31 => { + if self.action_num == 30 { + self.action_num = 31; + self.action_counter = 0; + self.anim_counter = 0; + } + + self.animate(1, 3, 4); + + self.action_counter += 1; + if self.action_counter > 30 { + self.action_num = 40; + self.anim_num = 5; + + let mut npc = NPC::create(312, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x; + npc.y = self.y; + npc.vel_x = 0x800 * self.direction.vector_x(); + let _ = npc_list.spawn(0x100, npc); + } + } + 40 | 41 => { + if self.action_num == 40 { + self.action_num = 41; + self.action_counter = 0; + self.anim_counter = 0; + } + + self.animate(2, 5, 6); + + self.action_counter += 1; + if self.action_counter > 40 { + self.action_num = 50; + self.anim_num = 0; + self.vel_x = 0; + self.vel_y = 0; + } + } + 50 => { + self.animate(1, 0, 1); + + self.vel_x += 0x20 * self.direction.vector_x(); + + if self.x < 0 || self.x > (stage.map.width as i32) * state.tile_size.as_int() * 0x200 { + self.vanish(state); + return Ok(()); + } + } + _ => (), + } + + if self.action_num < 50 { + let direction_x = self.target_x - self.x; + let direction_y = self.target_y - self.y; + + self.vel_x += 0x2A * direction_x.signum(); + self.vel_y += 0x2A * direction_y.signum(); + + self.vel_x = self.vel_x.clamp(-0x400, 0x400); + self.vel_y = self.vel_y.clamp(-0x400, 0x400); + } + + self.x += self.vel_x; + self.y += self.vel_y; + + let dir_offset = if self.direction == Direction::Left { 0 } else { 7 }; + self.anim_rect = state.constants.npc.n350_flying_bute_archer[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n353_bute_sword_flying( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + ) -> GameResult { + let player = self.get_closest_player_mut(players); + + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.vel_x = 0x600 * self.direction.vector_x(); + self.vel_y = 0x600 * self.direction.vector_y(); + } + + self.action_counter += 1; + if self.action_counter == 8 { + self.npc_flags.set_ignore_solidity(false); + } + + self.x += self.vel_x; + self.y += self.vel_y; + + if self.action_counter == 16 { + self.action_num = 10; + } + + self.animate(2, 0, 3); + } + 10 | 11 => { + if self.action_num == 10 { + self.action_num = 11; + self.anim_num = 0; + self.npc_flags.set_shootable(true); + self.npc_flags.set_ignore_solidity(false); + self.damage = 5; + self.display_bounds.top = 0x1000; + } + + self.direction = if self.x > player.x { Direction::Left } else { Direction::Right }; + + self.vel_x2 += 0x10 * self.direction.vector_x() * if player.y - 0x3000 > self.y { -1 } else { 1 }; + self.vel_y2 += 0x10 * if self.y > player.y { -1 } else { 1 }; + + if self.vel_x2 < 0 && self.flags.hit_left_wall() { + self.vel_x2 *= -1 + }; + if self.vel_x2 > 0 && self.flags.hit_right_wall() { + self.vel_x2 *= -1 + }; + if self.vel_y2 < 0 && self.flags.hit_top_wall() { + self.vel_y2 *= -1 + }; + if self.vel_y2 < 0 && self.flags.hit_bottom_wall() { + self.vel_y2 *= -1 + }; + + self.vel_x2 = self.vel_x2.clamp(-0x5FF, 0x5FF); + self.vel_y2 = self.vel_y2.clamp(-0x5FF, 0x5FF); + + self.x += self.vel_x2; + self.y += self.vel_y2; + + self.animate(1, 4, 5); + } + _ => (), + } + + if self.action_num < 10 { + self.anim_rect = state.constants.npc.n353_bute_sword_flying[self.anim_num as usize]; + } else { + let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; + self.anim_rect = state.constants.npc.n353_bute_sword_flying[self.anim_num as usize + dir_offset]; + } + + Ok(()) + } + + pub(crate) fn tick_n354_invisible_deathtrap_wall( + &mut self, + state: &mut SharedGameState, + npc_list: &NPCList, + stage: &mut Stage, + ) -> GameResult { + match self.action_num { + 0 => { + self.hit_bounds.bottom = 0x23000; + } + 10 => { + self.action_num = 11; + self.action_counter = 0; + self.x += 0x2000 * self.direction.vector_x() * -1; + } + 11 => { + self.action_counter += 1; + if self.action_counter > 100 { + self.action_counter = 0; + state.quake_counter = 20; + state.sound_manager.play_sfx(26); + state.sound_manager.play_sfx(12); + + self.x += 0x2000 * self.direction.vector_x(); + + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + + for iter in 0..20 { + let x = self.x / (state.tile_size.as_int() * 0x200); + let y = iter + (self.y / (state.tile_size.as_int() * 0x200)); + + if stage.change_tile(x as usize, y as usize, 109) { + npc.x = x * state.tile_size.as_int() * 0x200; + npc.y = y * state.tile_size.as_int() * 0x200; + let _ = npc_list.spawn(0x100, npc.clone()); + } + } + } + } + _ => (), + } + Ok(()) + } } impl BossNPC { @@ -1002,7 +1502,7 @@ impl BossNPC { for _ in 0..16 { let mut npc = NPC::create(4, &state.npc_table); npc.cond.set_alive(true); - npc.x = self.parts[0].rng.range(-40..40) * 0x200; + npc.x = self.parts[0].x + self.parts[0].rng.range(-40..40) * 0x200; npc.y = self.parts[0].y + 0x5000; let _ = npc_list.spawn(0x100, npc); } @@ -1256,7 +1756,7 @@ impl BossNPC { self.parts[0].action_num = 410; // platforms - for iter in 0..5 { + for iter in 0..8 { let mut npc = NPC::create(346, &state.npc_table); npc.cond.set_alive(true); npc.x = self.parts[0].x; @@ -1417,6 +1917,7 @@ impl BossNPC { * 0x2000) / 4; npc.y = (self.parts[0].rng.range(8..68) * 0x2000) / 4; + npc.direction = if player.x > self.parts[0].x { Direction::Left } else { Direction::Right }; let _ = npc_list.spawn(0x100, npc); } } diff --git a/src/npc/mod.rs b/src/npc/mod.rs index fcd1207..5e00b59 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -574,7 +574,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu 331 => self.tick_n331_ballos_bone_projectile(state), 332 => self.tick_n332_ballos_shockwave(state, npc_list), 333 => self.tick_n333_ballos_lightning(state, players, npc_list), - // 334 => self.tick_n334_sweat(state), + 334 => self.tick_n334_sweat(state, players), 335 => self.tick_n335_ikachan(state), 336 => self.tick_n336_ikachan_generator(state, players, npc_list), 337 => self.tick_n337_numahachi(state), @@ -582,21 +582,21 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu 339 => self.tick_n339_green_devil_generator(state, npc_list), 340 => self.tick_n340_ballos(state, players, npc_list, flash), 341 => self.tick_n341_ballos_1_head(state, npc_list), - 342 => self.tick_n342_ballos_1_eye(state, players, npc_list, boss), - // 343 => self.tick_n343_ballos_2_cutscene(state), - // 344 => self.tick_n344_ballos_2_eyes(state), - // 345 => self.tick_n345_ballos_skull_projectile(state), - // 346 => self.tick_n346_ballos_orbiting_platform(state), + 342 => self.tick_n342_ballos_orbiting_eye(state, players, npc_list, boss), + 343 => self.tick_n343_ballos_3_cutscene(state, boss), + 344 => self.tick_n344_ballos_3_eyes(state, boss), + 345 => self.tick_n345_ballos_skull_projectile(state, npc_list, stage), + 346 => self.tick_n346_ballos_orbiting_platform(state, players, stage, boss), 347 => self.tick_n347_hoppy(state, players), - // 348 => self.tick_n348_ballos_4_spikes(state), + 348 => self.tick_n348_ballos_4_spikes(state), 349 => self.tick_n349_statue(state), - // 350 => self.tick_n350_flying_bute_archer(state), + 350 => self.tick_n350_flying_bute_archer(state, players, npc_list, stage), 351 => self.tick_n351_statue_shootable(state, npc_list), 352 => self.tick_n352_ending_characters(state, npc_list), - // 353 => self.tick_n353_bute_red(state), - // 354 => self.tick_n354_invisible_deathtrap_wall(state), + 353 => self.tick_n353_bute_sword_flying(state, players), + 354 => self.tick_n354_invisible_deathtrap_wall(state, npc_list, stage), 355 => self.tick_n355_quote_and_curly_on_balrog(state, npc_list), - // 356 => self.tick_n356_balrog_rescuing(state), + 356 => self.tick_n356_balrog_rescuing(state, npc_list), 357 => self.tick_n357_puppy_ghost(state), 358 => self.tick_n358_misery_credits(state), 359 => self.tick_n359_water_droplet_generator(state, players, npc_list),