From b2ae2814835b43703621c123fb12508ff778a3d2 Mon Sep 17 00:00:00 2001 From: dawnDus <96957561+dawndus@users.noreply.github.com> Date: Thu, 10 Mar 2022 17:35:22 -0500 Subject: [PATCH] Co-op cutscene handling for NPCs --- src/engine_constants/npcs.rs | 7 +- src/npc/ai/balrog.rs | 17 +++- src/npc/ai/misc.rs | 40 +++++++- src/npc/ai/outer_wall.rs | 23 +++++ src/npc/ai/quote.rs | 176 ++++++++++++++++++++++++++++++++++- src/npc/mod.rs | 5 +- 6 files changed, 253 insertions(+), 15 deletions(-) diff --git a/src/engine_constants/npcs.rs b/src/engine_constants/npcs.rs index 9c74d58..c02a869 100644 --- a/src/engine_constants/npcs.rs +++ b/src/engine_constants/npcs.rs @@ -514,7 +514,7 @@ pub struct NPCConsts { pub n149_horizontal_moving_block: Rect, #[serde(default = "default_n150_quote")] - pub n150_quote: SafeNPCRect<18>, + pub n150_quote: SafeNPCRect<20>, #[serde(default = "default_n151_blue_robot_standing")] pub n151_blue_robot_standing: SafeNPCRect<4>, @@ -1115,6 +1115,7 @@ pub struct NPCConsts { #[serde(default = "default_n360_credits_thank_you")] pub n360_credits_thank_you: Rect, + // pub n370_second_quote: () // Same as n150_quote #[serde(default = "default_b01_omega")] pub b01_omega: SafeNPCRect<10>, @@ -2853,7 +2854,7 @@ fn default_n149_horizontal_moving_block() -> Rect { Rect { left: 16, top: 0, right: 48, bottom: 32 } } -fn default_n150_quote() -> SafeNPCRect<18> { +fn default_n150_quote() -> SafeNPCRect<20> { SafeNPCRect([ Rect { left: 0, top: 0, right: 16, bottom: 16 }, Rect { left: 48, top: 0, right: 64, bottom: 16 }, @@ -2864,6 +2865,7 @@ fn default_n150_quote() -> SafeNPCRect<18> { Rect { left: 0, top: 0, right: 16, bottom: 16 }, Rect { left: 160, top: 0, right: 176, bottom: 16 }, Rect { left: 112, top: 0, right: 128, bottom: 16 }, + Rect { left: 96, top: 0, right: 112, bottom: 16 }, Rect { left: 0, top: 16, right: 16, bottom: 32 }, Rect { left: 48, top: 16, right: 64, bottom: 32 }, Rect { left: 144, top: 16, right: 160, bottom: 32 }, @@ -2873,6 +2875,7 @@ fn default_n150_quote() -> SafeNPCRect<18> { Rect { left: 0, top: 16, right: 16, bottom: 32 }, Rect { left: 160, top: 16, right: 176, bottom: 32 }, Rect { left: 112, top: 16, right: 128, bottom: 32 }, + Rect { left: 96, top: 16, right: 112, bottom: 32 }, ]) } diff --git a/src/npc/ai/balrog.rs b/src/npc/ai/balrog.rs index bf06785..a15bbd2 100644 --- a/src/npc/ai/balrog.rs +++ b/src/npc/ai/balrog.rs @@ -462,6 +462,11 @@ impl NPC { npc.parent_id = self.id; let _ = npc_list.spawn(0x100, npc.clone()); + if players[1].cond.alive() { + npc.tsc_direction = 4; + let _ = npc_list.spawn(0x100, npc.clone()); + npc.tsc_direction = 0; + } npc.direction = Direction::Up; let _ = npc_list.spawn(0x100, npc); } @@ -1307,7 +1312,12 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n356_balrog_rescuing(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult { + pub(crate) fn tick_n356_balrog_rescuing( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { match self.action_num { 0 | 11 => { if self.action_num == 0 { @@ -1319,6 +1329,11 @@ impl NPC { let mut npc = NPC::create(355, &state.npc_table); npc.cond.set_alive(true); npc.parent_id = self.id; + if players[1].cond.alive() { + npc.tsc_direction = 5; + let _ = npc_list.spawn(0xAA, npc.clone()); + npc.tsc_direction = 6; + } npc.direction = Direction::Bottom; let _ = npc_list.spawn(0xAA, npc.clone()); npc.direction = Direction::Right; diff --git a/src/npc/ai/misc.rs b/src/npc/ai/misc.rs index eaf33e3..f829e4e 100644 --- a/src/npc/ai/misc.rs +++ b/src/npc/ai/misc.rs @@ -2532,8 +2532,38 @@ impl NPC { npc_list: &NPCList, ) -> GameResult { if self.action_num == 0 { - match self.direction { - Direction::Left => { + match (self.tsc_direction, self.direction) { + // Co-op + (4, _) => { + self.spritesheet_id = 16; + self.anim_num = 0; + + if let Some(npc) = self.get_parent_ref_mut(npc_list) { + self.x = npc.x; + self.y = npc.y + 0x1400; + } + } + (5, _) => { + self.spritesheet_id = 16; + self.anim_num = 2; + + if let Some(npc) = self.get_parent_ref_mut(npc_list) { + self.x = npc.x + 0x1600; + self.y = npc.y - 0x2200; + } + } + // Curly's position changes when 2P is present + (6, Direction::Bottom) => { + self.spritesheet_id = 23; + self.anim_num = 3; + + if let Some(npc) = self.get_parent_ref_mut(npc_list) { + self.x = npc.x + 0x400; + self.y = npc.y - 0x2600; + } + } + // Normal + (_, Direction::Left) => { self.spritesheet_id = 16; self.anim_num = 0; @@ -2542,7 +2572,7 @@ impl NPC { self.y = npc.y + 0x1400; } } - Direction::Up => { + (_, Direction::Up) => { self.spritesheet_id = 23; self.anim_num = 1; @@ -2551,7 +2581,7 @@ impl NPC { self.y = npc.y + 0x1400; } } - Direction::Right => { + (_, Direction::Right) => { self.spritesheet_id = 16; self.anim_num = 2; @@ -2560,7 +2590,7 @@ impl NPC { self.y = npc.y - 0x2600; } } - Direction::Bottom => { + (_, Direction::Bottom) => { self.spritesheet_id = 23; self.anim_num = 3; diff --git a/src/npc/ai/outer_wall.rs b/src/npc/ai/outer_wall.rs index 57c8048..8677383 100644 --- a/src/npc/ai/outer_wall.rs +++ b/src/npc/ai/outer_wall.rs @@ -32,6 +32,19 @@ impl NPC { self.target_x = self.x - 0xc00; self.vel_y = 0; self.npc_flags.set_ignore_solidity(true); + + // Co-op + if players[1].cond.alive() { + let mut npc = NPC::create(370, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + npc.x = self.x; + npc.y = self.y; + npc.action_num = 200; + npc.direction = Direction::Right; + + let _ = npc_list.spawn(0xAA, npc); + } } self.vel_x += if self.x >= self.target_x { -8 } else { 8 }; @@ -75,6 +88,16 @@ impl NPC { self.anim_rect.bottom += 40; } + // Switch uses the extra space on the sprite sheet for 2P's Curly + if state.constants.is_switch { + if self.anim_num <= 1 { + self.anim_rect.top += 8; + self.display_bounds.top = 0x2000; + } else { + self.display_bounds.top = 0x3000; + } + } + Ok(()) } diff --git a/src/npc/ai/quote.rs b/src/npc/ai/quote.rs index ce86549..47f7f7c 100644 --- a/src/npc/ai/quote.rs +++ b/src/npc/ai/quote.rs @@ -154,10 +154,6 @@ impl NPC { self.action_num = 1; self.anim_num = 0; - if self.npc_type == 370 { - self.cond.set_alive(players[1].cond.alive()); - } - if self.tsc_direction > 10 { let player = &players[state.textscript_vm.executor_player.index()]; self.x = player.x; @@ -291,7 +287,7 @@ impl NPC { _ => (), } - let dir_offset = if self.direction == Direction::Left { 0 } else { 9 }; + let dir_offset = if self.direction == Direction::Left { 0 } else { 10 }; self.anim_rect = state.constants.npc.n150_quote[self.anim_num as usize + dir_offset]; if self.action_num == 21 { @@ -305,4 +301,174 @@ impl NPC { Ok(()) } + + pub(crate) fn tick_n370_second_quote( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { + if !players[1].cond.alive() { + self.cond.set_alive(false); + return Ok(()); + } + match self.action_num { + 0 => { + self.action_num = 1; + self.anim_num = 0; + + if self.tsc_direction > 10 { + let player = &players[state.textscript_vm.executor_player.index() + 1 % 1]; + self.x = player.x; + self.y = player.y; + + self.direction = + Direction::from_int(self.tsc_direction.saturating_sub(10) as usize).unwrap_or(Direction::Left); + } else { + self.direction = Direction::from_int(self.tsc_direction as usize).unwrap_or(Direction::Left); + } + } + 2 => { + self.anim_num = 1; + } + 10 => { + self.action_num = 11; + self.anim_num = 2; + + state.sound_manager.play_sfx(71); + + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + npc.direction = Direction::Left; + npc.x = self.x; + npc.y = self.y; + + for _ in 0..4 { + npc.vel_x = self.rng.range(-0x155..0x155) as i32; + npc.vel_y = self.rng.range(-0x600..0) as i32; + + let _ = npc_list.spawn(0x100, npc.clone()); + } + } + 11 => { + self.anim_num = 2; + } + 20 => { + self.action_num = 21; + self.action_counter = 63; + + state.sound_manager.play_sfx(29); + } + 21 => { + if self.action_counter > 0 { + self.action_counter -= 1; + } else { + self.cond.set_alive(false); + } + } + 50 | 51 => { + if self.action_num == 50 { + self.action_num = 51; + self.anim_num = 3; + self.anim_counter = 0; + } + + self.anim_counter += 1; + if self.anim_counter > 4 { + self.anim_counter = 0; + self.anim_num += 1; + if self.anim_num > 6 { + self.anim_num = 3; + } + } + + self.x += self.direction.vector_x() * 0x200; + } + 60 | 61 => { + if self.action_num == 60 { + self.action_num = 61; + self.anim_num = 7; + self.target_x = self.x; + self.target_y = self.y; + } + + self.target_y += 0x100; + self.x = self.target_x + self.rng.range(-1..1) as i32 * 0x200; + self.y = self.target_y + self.rng.range(-1..1) as i32 * 0x200; + } + 70 | 71 => { + if self.action_num == 70 { + self.action_num = 71; + self.action_counter = 0; + self.anim_num = 3; + self.anim_counter = 0; + } + + self.x += (self.direction.vector_x() as i32 | 1) * 0x100; + + self.anim_counter += 1; + if self.anim_counter > 8 { + self.anim_counter = 0; + self.anim_num += 1; + if self.anim_num > 6 { + self.anim_num = 3; + } + } + } + 80 => { + self.anim_num = 8; + } + 99 | 100 | 101 => { + if self.action_num == 99 || self.action_num == 100 { + self.action_num = 101; + self.anim_num = 3; + self.anim_counter = 0; + } + + self.vel_y += 0x40; + if self.vel_y > 0x5ff { + self.vel_y = 0x5ff; + } + + if self.flags.hit_bottom_wall() { + self.vel_y = 0; + self.action_num = 102; + } + + self.y += self.vel_y; + } + 102 => { + self.anim_counter += 1; + if self.anim_counter > 8 { + self.anim_counter = 0; + self.anim_num += 1; + if self.anim_num > 6 { + self.anim_num = 3; + } + } + } + 200 => { + self.anim_num = 9; + if let Some(parent) = self.get_parent_ref_mut(npc_list) { + self.x = parent.x + parent.vel_x + 0xA00; + self.y = parent.y + parent.vel_y - 0x1C00; + } + } + _ => (), + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 10 }; + self.anim_rect = state.constants.npc.n150_quote[self.anim_num as usize + dir_offset]; + + if self.action_num == 21 { + self.anim_rect.bottom = self.anim_rect.top + self.action_counter / 4; + } + + let offset = players[state.textscript_vm.executor_player.index() + 1 % 1].get_texture_offset() + + (state.get_skinsheet_offset() * state.tile_size.as_int() as u16 * 2); + self.anim_rect.top += offset; + self.anim_rect.bottom += offset; + + Ok(()) + } } diff --git a/src/npc/mod.rs b/src/npc/mod.rs index 4dec87b..288c25c 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -390,7 +390,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu 147 => self.tick_n147_critter_purple(state, players, npc_list), 148 => self.tick_n148_critter_purple_projectile(state), 149 => self.tick_n149_horizontal_moving_block(state, players, npc_list), - 150 | 370 => self.tick_n150_quote(state, players, npc_list), + 150 => self.tick_n150_quote(state, players, npc_list), 151 => self.tick_n151_blue_robot_standing(state), 152 => self.tick_n152_shutter_stuck(), 153 => self.tick_n153_gaudi(state, players), @@ -596,11 +596,12 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu 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, npc_list), + 356 => self.tick_n356_balrog_rescuing(state, players, 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), 360 => self.tick_n360_credits_thank_you(state), + 370 => self.tick_n370_second_quote(state, players, npc_list), _ => { #[cfg(feature = "hooks")] {