diff --git a/src/engine_constants/npcs.rs b/src/engine_constants/npcs.rs index b60ec9f..eb484c3 100644 --- a/src/engine_constants/npcs.rs +++ b/src/engine_constants/npcs.rs @@ -772,8 +772,8 @@ pub struct NPCConsts { #[serde(default = "default_n258_mimiga_sleeping")] pub n258_mimiga_sleeping: Rect, - #[serde(default = "default_n259_curly_unconcious")] - pub n259_curly_unconcious: [Rect; 2], + #[serde(default = "default_n259_curly_unconscious")] + pub n259_curly_unconscious: [Rect; 2], #[serde(default = "default_n260_shovel_brigade_caged")] pub n260_shovel_brigade_caged: [Rect; 6], @@ -3794,7 +3794,7 @@ fn default_n258_mimiga_sleeping() -> Rect { Rect { left: 48, top: 32, right: 64, bottom: 48 } } -fn default_n259_curly_unconcious() -> [Rect; 2] { +fn default_n259_curly_unconscious() -> [Rect; 2] { [ Rect { left: 224, top: 96, right: 240, bottom: 112 }, Rect { left: 224, top: 112, right: 240, bottom: 128 }, diff --git a/src/npc/ai/balrog.rs b/src/npc/ai/balrog.rs index 85722f5..7cfddd2 100644 --- a/src/npc/ai/balrog.rs +++ b/src/npc/ai/balrog.rs @@ -326,7 +326,7 @@ impl NPC { } self.action_counter2 += 1; - self.x += if self.action_counter2 / 2 % 2 != 0 { 0x200 } else { -0x200 }; + self.x += if self.action_counter2 & 0x02 != 0 { 0x200 } else { -0x200 }; if self.action_counter > 100 { self.action_num = 11; @@ -365,7 +365,7 @@ impl NPC { } self.anim_counter += 1; - self.anim_num = if self.anim_counter / 2 % 2 != 0 { 5 } else { 6 }; + self.anim_num = if self.anim_counter & 0x02 != 0 { 5 } else { 6 }; } 42 | 43 => { if self.action_num == 42 { @@ -385,7 +385,7 @@ impl NPC { } self.anim_counter += 1; - self.anim_num = if self.anim_counter / 2 % 2 != 0 { 7 } else { 6 }; + self.anim_num = if self.anim_counter & 0x02 != 0 { 7 } else { 6 }; } 50 => { self.anim_num = 8; @@ -438,7 +438,7 @@ impl NPC { } self.action_counter2 += 1; - self.x += if self.action_counter2 / 2 % 2 != 0 { 0x200 } else { -0x200 }; + self.x += if self.action_counter2 & 0x02 != 0 { 0x200 } else { -0x200 }; self.anim_num = 5; self.vel_x = 0; diff --git a/src/npc/ai/booster.rs b/src/npc/ai/booster.rs index 9b4fd70..d18f87c 100644 --- a/src/npc/ai/booster.rs +++ b/src/npc/ai/booster.rs @@ -88,7 +88,7 @@ impl NPC { if self.action_num == 31 { self.anim_rect.bottom = self.action_counter / 4 + self.anim_rect.top; - if self.action_counter / 2 % 2 != 0 { + if self.action_counter & 0x02 != 0 { self.anim_rect.left += 1; } } diff --git a/src/npc/ai/curly.rs b/src/npc/ai/curly.rs index cff9399..4061934 100644 --- a/src/npc/ai/curly.rs +++ b/src/npc/ai/curly.rs @@ -785,4 +785,54 @@ impl NPC { Ok(()) } + + pub(crate) fn tick_n259_curly_unconcious( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.npc_flags.set_interactable(false); + self.action_num = 1; + } + + let player = &players[0]; + + self.direction = player.direction; + self.x = player.x + 0x600 * self.direction.opposite().vector_x(); + self.y = player.y - 0x800; + + let dir_offset = if self.direction == Direction::Left { 0 } else { 1 }; + + self.anim_rect = state.constants.npc.n259_curly_unconscious[dir_offset]; + + if (player.anim_num & 1) != 0 { + self.anim_rect.top += 1; + } + } + 10 => { + self.action_num = 11; + self.vel_x = 0x40; + self.vel_y = -0x20; + self.anim_rect = state.constants.npc.n259_curly_unconscious[0]; + } + 11 => { + if self.y <= 0x7FFF { + self.vel_y = 0x20; + } + self.x += self.vel_x; + self.y += self.vel_y; + } + 20 => { + self.vanish(state); + npc_list.create_death_smoke_up(self.x, self.y, 0x2000, 64, state, &self.rng); + } + _ => (), + } + + Ok(()) + } } diff --git a/src/npc/ai/doctor.rs b/src/npc/ai/doctor.rs index a955b30..e5d9e99 100644 --- a/src/npc/ai/doctor.rs +++ b/src/npc/ai/doctor.rs @@ -111,4 +111,48 @@ impl NPC { Ok(()) } + + pub(crate) fn tick_n257_red_crystal(&mut self, state: &mut SharedGameState) -> GameResult { + if self.action_num == 0 { + self.action_num = 1; + } + + if self.action_num == 1 { + if state.npc_super_pos.0 != 0 { + self.action_num = 10; + } + } else if self.action_num == 10 { + if self.x < state.npc_super_pos.0 { + self.vel_x += 0x55; + } + if self.x > state.npc_super_pos.0 { + self.vel_x -= 0x55; + } + if self.y < state.npc_super_pos.1 { + self.vel_y += 0x55; + } + if self.y > state.npc_super_pos.1 { + self.vel_y -= 0x55; + } + + 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; + } + + self.animate(3, 0, 1); + + if self.direction == Direction::Left && self.vel_x > 0 { + self.anim_num = 2; + } + if self.direction == Direction::Right && self.vel_x < 0 { + self.anim_num = 2; + } + + self.anim_rect = state.constants.npc.n257_red_crystal[self.anim_num as usize]; + + Ok(()) + } } diff --git a/src/npc/ai/egg_corridor.rs b/src/npc/ai/egg_corridor.rs index 5f08235..1182c8e 100644 --- a/src/npc/ai/egg_corridor.rs +++ b/src/npc/ai/egg_corridor.rs @@ -675,7 +675,7 @@ impl NPC { } } - if self.action_counter > 120 && self.action_counter / 2 % 2 == 1 && self.anim_num == 1 { + if self.action_counter > 120 && self.action_counter & 0x02 == 1 && self.anim_num == 1 { self.anim_num = 2; } @@ -1335,7 +1335,7 @@ impl NPC { } } - if self.action_counter > 120 && self.action_counter / 2 % 2 == 1 && self.anim_num == 1 { + if self.action_counter > 120 && self.action_counter & 0x02 == 1 && self.anim_num == 1 { self.anim_num = 2; } diff --git a/src/npc/ai/grasstown.rs b/src/npc/ai/grasstown.rs index e0d2a26..f80fef6 100644 --- a/src/npc/ai/grasstown.rs +++ b/src/npc/ai/grasstown.rs @@ -928,7 +928,7 @@ impl NPC { self.action_counter = 0; } - if self.action_counter / 2 % 2 != 0 { + if self.action_counter & 0x02 != 0 { self.x += 0x200; state.sound_manager.play_sfx(11); } else { diff --git a/src/npc/ai/igor.rs b/src/npc/ai/igor.rs index 01007d1..10fbeb9 100644 --- a/src/npc/ai/igor.rs +++ b/src/npc/ai/igor.rs @@ -250,7 +250,7 @@ impl NPC { state.sound_manager.play_sfx(12); } - self.anim_num = if self.action_counter > 50 && (self.action_counter / 2 % 2) != 0 { 11 } else { 10 }; + self.anim_num = if self.action_counter > 50 && (self.action_counter & 0x02) != 0 { 11 } else { 10 }; if self.action_counter > 132 { self.action_num = 0; @@ -319,13 +319,13 @@ impl NPC { let dir_offset = if self.direction == Direction::Left { 0 } else { 4 }; self.anim_rect = state.constants.npc.n089_igor_dead[dir_offset]; - if (self.action_counter / 2 % 2) != 0 { + if (self.action_counter & 0x02) != 0 { self.anim_rect.left -= 1; } } 2 => { self.action_counter += 1; - if (self.action_counter / 2 % 2) != 0 && self.action_counter < 100 { + if (self.action_counter & 0x02) != 0 && self.action_counter < 100 { self.anim_num = 0; self.display_bounds.left = 20 * 0x200; self.display_bounds.right = 20 * 0x200; diff --git a/src/npc/ai/maze.rs b/src/npc/ai/maze.rs index f1ea636..941e7d0 100644 --- a/src/npc/ai/maze.rs +++ b/src/npc/ai/maze.rs @@ -763,7 +763,7 @@ impl NPC { self.anim_rect.top += self.action_counter3 / 8; self.y = ((self.action_counter3 as i32 / 8) * 0x200) + self.target_y; - self.anim_rect.left -= self.action_counter3 / 2 % 2; + self.anim_rect.left -= if (self.action_counter3 & 0x02) != 0 { 1 } else { 0 }; } if self.action_counter3 % 3 == 2 { let mut npc = NPC::create(161, &state.npc_table); diff --git a/src/npc/ai/misc.rs b/src/npc/ai/misc.rs index 5838b7b..7ee3227 100644 --- a/src/npc/ai/misc.rs +++ b/src/npc/ai/misc.rs @@ -4,8 +4,8 @@ use crate::caret::CaretType; use crate::common::{Direction, Rect}; use crate::components::flash::Flash; use crate::framework::error::GameResult; +use crate::npc::{NPC, NPCLayer}; use crate::npc::list::NPCList; -use crate::npc::{NPCLayer, NPC}; use crate::player::Player; use crate::rng::RNG; use crate::shared_game_state::SharedGameState; @@ -1679,6 +1679,105 @@ impl NPC { Ok(()) } + pub(crate) fn tick_n238_press_sideways( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { + let player = self.get_closest_player_ref(&players); + + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.target_x = self.x; + self.target_y = self.y; + self.display_bounds.left = 0x2000; + self.display_bounds.right = 0x1000; + } + + if self.direction == Direction::Left + && player.x < self.x + && player.x > self.x - 0x18000 + && player.y > self.y - 0x800 + && player.y < self.y + 0x1000 + { + self.action_num = 10; + self.action_counter = 0; + self.anim_num = 2; + } + + if self.direction == Direction::Right + && player.x > self.x + && player.x < self.x + 0x18000 + && player.y > self.y - 0x800 + && player.y < self.y + 0x1000 + { + self.action_num = 10; + self.action_counter = 0; + self.anim_num = 2; + } + } + 10 => { + self.damage = 127; + self.x += if self.direction != Direction::Left { 0xC00 } else { -0xC00 }; + + self.action_counter += 1; + if self.action_counter == 8 { + self.action_num = 20; + self.action_counter = 0; + + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + + for _ in 0..4 { + npc.x = self.x + self.rng.range(-16..16) * 0x200; + npc.y = self.y + self.rng.range(-8..8) * 0x200; + let _ = npc_list.spawn(0x100, npc.clone()); + + state.sound_manager.play_sfx(12); + } + } + } + 20 => { + self.damage = 0; + self.action_counter += 1; + if self.action_counter > 50 { + self.action_counter = 0; + self.action_num = 30; + } + } + 30 => { + self.damage = 0; + self.anim_num = 1; + self.action_counter += 1; + if self.action_counter == 12 { + self.action_num = 1; + self.action_counter = 0; + self.anim_num = 0; + } + + self.x += if self.direction != Direction::Left { -0x800 } else { 0x800 }; + } + _ => (), + } + + if self.direction != Direction::Left || player.x >= self.x { + if self.direction == Direction::Right && player.x > self.x { + self.hit_bounds.right = 0x2000; + } else { + self.hit_bounds.right = 0x1000; + } + } else { + self.hit_bounds.right = 0x2000; + } + + self.anim_rect = state.constants.npc.n238_press_sideways[self.anim_num as usize]; + + Ok(()) + } + pub(crate) fn tick_n239_cage_bars(&mut self, state: &mut SharedGameState) -> GameResult { if self.action_num == 0 { self.action_num = 1; @@ -1699,6 +1798,31 @@ impl NPC { Ok(()) } + pub(crate) fn tick_n253_experience_capsule( + &mut self, + state: &mut SharedGameState, + npc_list: &NPCList, + ) -> GameResult { + if self.action_num == 0 { + self.action_num = 1; + } + + self.animate(4, 0, 1); + + if self.life <= 100 { + self.cond.set_alive(false); + + self.create_xp_drop_custom(self.x, self.y, self.flag_num, state, npc_list); + npc_list.create_death_smoke(self.x, self.y, self.display_bounds.right as usize, 8, state, &self.rng); + + state.sound_manager.play_sfx(25); + } + + self.anim_rect = state.constants.npc.n253_experience_capsule[self.anim_num as usize]; + + Ok(()) + } + pub(crate) fn tick_n258_mimiga_sleeping(&mut self, state: &mut SharedGameState) -> GameResult { if self.action_num == 0 { self.action_num = 1; diff --git a/src/npc/ai/misery.rs b/src/npc/ai/misery.rs index 5906791..8b36ada 100644 --- a/src/npc/ai/misery.rs +++ b/src/npc/ai/misery.rs @@ -1,9 +1,14 @@ +use std::hint::unreachable_unchecked; + use num_traits::clamp; -use crate::common::Direction; +use crate::caret::CaretType; +use crate::common::{Direction, CDEG_RAD}; +use crate::components::flash::Flash; use crate::framework::error::GameResult; use crate::npc::list::NPCList; use crate::npc::NPC; +use crate::player::Player; use crate::rng::RNG; use crate::shared_game_state::SharedGameState; @@ -18,7 +23,7 @@ impl NPC { self.target_y = npc.y; let angle = ((self.y - self.target_y) as f64 / (self.x - self.target_x) as f64).atan(); - self.vel_x = (angle.cos() * 1024.0) as i32; // 2.0fix9 + self.vel_x = (angle.cos() * 1024.0) as i32; self.vel_y = (angle.sin() * 1024.0) as i32; } @@ -66,7 +71,12 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n067_misery_floating(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult { + pub(crate) fn tick_n067_misery_floating( + &mut self, + state: &mut SharedGameState, + npc_list: &NPCList, + flash: &mut Flash, + ) -> GameResult { match self.action_num { 0 | 1 => { if self.action_num == 0 { @@ -172,7 +182,7 @@ impl NPC { self.action_counter += 1; if self.action_counter == 30 { state.sound_manager.play_sfx(101); - // todo flash + flash.set_blink(); self.action_num = 27; self.anim_num = 7; } @@ -193,7 +203,7 @@ impl NPC { let (frame1, frame2) = match self.action_num { 11 => (0, 1), 14 => (2, 3), - _ => unreachable!(), + _ => unsafe { unreachable_unchecked() }, }; if self.anim_counter > 0 { @@ -209,6 +219,7 @@ impl NPC { } let dir_offset = if self.direction == Direction::Left { 0 } else { 8 }; + self.anim_rect = state.constants.npc.n067_misery_floating[self.anim_num as usize + dir_offset]; if self.action_num == 1 && self.anim_counter < 32 { @@ -219,7 +230,12 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n082_misery_standing(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult { + pub(crate) fn tick_n082_misery_standing( + &mut self, + state: &mut SharedGameState, + npc_list: &NPCList, + flash: &mut Flash, + ) -> GameResult { match self.action_num { 0 | 1 => { if self.action_num == 0 { @@ -292,10 +308,11 @@ impl NPC { self.action_counter += 1; if self.action_counter == 30 { - state.sound_manager.play_sfx(101); - // todo flash self.action_num = 27; self.anim_num = 7; + + state.sound_manager.play_sfx(101); + flash.set_blink(); } } 27 => { @@ -356,10 +373,8 @@ impl NPC { self.x += self.vel_x; self.y += self.vel_y; - if self.action_num == 11 - { - if self.anim_counter != 0 - { + if self.action_num == 11 { + if self.anim_counter != 0 { self.anim_counter -= 1; self.anim_num = 1; } else { @@ -371,10 +386,8 @@ impl NPC { } } - if self.action_num == 14 - { - if self.action_counter != 0 - { + if self.action_num == 14 { + if self.action_counter != 0 { self.action_counter -= 1; self.anim_num = 3; } else { @@ -387,11 +400,322 @@ impl NPC { } let dir_offset = if self.direction == Direction::Left { 0 } else { 9 }; + self.anim_rect = state.constants.npc.n082_misery_standing[self.anim_num as usize + dir_offset]; Ok(()) } + pub(crate) fn tick_n247_misery_boss( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.y += 0xc00; + self.target_y = 0x8000; + } + + if self.rng.range(0..120) == 10 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 1; + } + } + 2 => { + self.action_counter += 1; + if self.action_counter > 8 { + self.action_num = 1; + self.anim_num = 0; + } + } + 20 => { + self.vel_x = 0; + self.vel_y += 0x40; + + if self.flags.hit_bottom_wall() { + self.action_num = 21; + self.anim_num = 2; + } + } + 21 => { + if self.rng.range(0..120) == 10 { + self.action_num = 22; + self.action_counter = 0; + self.anim_num = 3; + } + } + 22 => { + self.action_counter += 1; + if self.action_counter > 8 { + self.action_num = 21; + self.anim_num = 2; + } + } + 100 | 101 => { + if self.action_num == 100 { + self.action_num = 101; + self.action_counter = 0; + self.anim_num = 0; + self.vel_x = 0; + self.npc_flags.set_shootable(true); + self.action_counter2 = self.life; + } + + let player = self.get_closest_player_ref(&players); + + if player.x >= self.x { + self.direction = Direction::Right; + } else { + self.direction = Direction::Left; + } + + self.vel_y += if self.y >= self.target_y { -0x20 } else { 0x20 }; + self.vel_y = self.vel_y.clamp(-0x200, 0x200); + + self.action_counter += 1; + if self.action_counter > 200 || (self.life + 80) <= self.action_counter2 { + self.action_counter = 0; + self.action_num = 110; + } + } + 110 | 111 => { + if self.action_num == 110 { + self.action_num = 111; + self.action_counter = 0; + self.vel_x = 0; + self.vel_y = 0; + self.npc_flags.set_shootable(false); + } + + self.action_counter += 1; + self.anim_num = if (self.action_counter & 1) != 0 { 5 } else { 6 }; + + if self.action_counter > 30 { + self.action_counter3 += 1; + self.action_num = if self.action_counter3 % 3 == 0 { 113 } else { 112 }; + + self.action_counter = 0; + self.anim_num = 4; + } + } + 112 => { + self.action_counter += 1; + if self.action_counter % 6 == 0 { + let player = self.get_closest_player_ref(&players); + let angle = f64::atan2((self.y - player.y) as f64, (self.x - player.x) as f64) + + self.rng.range(-4..4) as f64 * CDEG_RAD; + + let mut npc = NPC::create(248, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x; + npc.y = self.y + 0x800; + npc.vel_x = (angle.cos() * -2048.0) as i32; + npc.vel_y = (angle.sin() * -2048.0) as i32; + + let _ = npc_list.spawn(0x100, npc); + state.sound_manager.play_sfx(34); + } + + if self.action_counter > 30 { + self.action_num = 150; + self.action_counter = 0; + } + } + 113 => { + self.action_counter += 1; + if self.action_counter == 10 { + let player = self.get_closest_player_ref(&players); + + let mut npc = NPC::create(279, &state.npc_table); + npc.cond.set_alive(true); + npc.x = player.x; + npc.y = player.y - 0x8000; + npc.direction = Direction::Up; + let _ = npc_list.spawn(0x100, npc); + } + + if self.action_counter > 30 { + self.action_num = 150; + self.action_counter = 0; + } + } + 150 | 151 => { + if self.action_num == 150 { + self.action_num = 151; + self.action_counter = 0; + self.anim_num = 7; + + let mut npc = NPC::create(249, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x; + npc.y = self.y; + let _ = npc_list.spawn(0x100, npc.clone()); + + npc.direction = Direction::Right; + let _ = npc_list.spawn(0x100, npc); + + self.target_x = self.rng.range(9..31) * 0x2000; + self.target_y = self.rng.range(5..7) * 0x2000; + + state.sound_manager.play_sfx(29); + } + + self.action_counter += 1; + if self.action_counter == 42 { + let mut npc = NPC::create(249, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.target_x + 0x2000; + npc.y = self.target_y; + let _ = npc_list.spawn(0x100, npc.clone()); + + npc.x = self.x - 0x2000; + npc.direction = Direction::Right; + let _ = npc_list.spawn(0x100, npc); + } + + if self.action_counter > 50 { + self.action_counter = 0; + self.vel_y = -0x200; + self.npc_flags.set_shootable(true); + self.x = self.target_x; + self.y = self.target_y; + + if self.life < 340 { + let mut npc = NPC::create(252, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + let _ = npc_list.spawn(0x100, npc.clone()); + + npc.tsc_direction = 0x80; + let _ = npc_list.spawn(0x100, npc); + } + + if self.life < 180 { + let mut npc = NPC::create(252, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + npc.tsc_direction = 0x40; + let _ = npc_list.spawn(0x100, npc.clone()); + + npc.tsc_direction = 0xc0; + let _ = npc_list.spawn(0x100, npc); + } + + let player = self.get_closest_player_ref(&players); + self.action_num = if player.x > self.x - 0xe000 && player.x <= self.x + 0xe000 { 100 } else { 160 }; + } + } + 160 | 161 => { + if self.action_num == 160 { + self.action_num = 161; + self.action_counter = 0; + self.anim_num = 4; + + let player = self.get_closest_player_ref(&players); + if player.x >= self.x { + self.direction = Direction::Right; + } else { + self.direction = Direction::Left; + } + } + + self.vel_y += if self.y >= self.target_y { -0x20 } else { 0x20 }; + self.vel_y = self.vel_y.clamp(-0x200, 0x200); + + self.action_counter += 1; + if self.action_counter % 24 == 0 { + let mut npc = NPC::create(250, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x; + npc.y = self.y + 0x800; + let _ = npc_list.spawn(0x100, npc); + + state.sound_manager.play_sfx(34); + } + + if self.action_counter > 72 { + self.action_counter = 0; + self.action_num = 100; + } + } + + 1000 | 1001 => { + if self.action_num == 1000 { + self.npc_flags.set_shootable(false); + self.action_num = 1001; + self.action_counter = 0; + self.anim_num = 4; + + self.target_x = self.x; + self.target_y = self.y; + + self.vel_x = 0; + self.vel_y = 0; + + npc_list.remove_by_type(252, state); + + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x; + npc.y = self.y; + for _ in 0..3 { + let _ = npc_list.spawn(0x100, npc.clone()); + } + } + + self.action_counter += 1; + self.x = if (self.action_counter & 0x02) != 0 { self.target_x + 0x200 } else { self.target_x }; + } + 1010 => { + self.vel_y += 0x10; + + if self.flags.hit_bottom_wall() { + self.action_num = 1020; + self.anim_num = 8; + } + } + _ => (), + } + + self.vel_x = self.vel_x.clamp(-0x200, 0x200); + 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 { 9 }; + + self.anim_rect = state.constants.npc.n247_misery_boss[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n248_misery_boss_vanishing(&mut self, state: &mut SharedGameState) -> GameResult { + if self.flags.any_flag() { + self.cond.set_alive(false); + state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left); + } + + self.y += self.vel_y; + self.x += self.vel_x; + + self.animate(1, 0, 2); + self.anim_rect = state.constants.npc.n248_misery_boss_vanishing[self.anim_num as usize]; + + self.action_counter3 += 1; + if self.action_counter3 > 300 { + self.cond.set_alive(false); + state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left); + } + + Ok(()) + } + pub(crate) fn tick_n249_misery_boss_energy_shot(&mut self, state: &mut SharedGameState) -> GameResult { self.action_counter2 += 1; if self.action_counter2 > 8 { @@ -409,4 +733,3 @@ impl NPC { Ok(()) } } - diff --git a/src/npc/ai/pickups.rs b/src/npc/ai/pickups.rs index 5362e9e..5d61de3 100644 --- a/src/npc/ai/pickups.rs +++ b/src/npc/ai/pickups.rs @@ -128,7 +128,7 @@ impl NPC { return Ok(()); } - if self.action_counter > 400 && (self.action_counter / 2 % 2) != 0 { + if self.action_counter > 400 && (self.action_counter & 0x02) != 0 { self.anim_rect.left = 0; self.anim_rect.top = 0; self.anim_rect.right = 0; @@ -194,7 +194,7 @@ impl NPC { self.cond.set_alive(false); } - if self.action_counter2 > 500 && self.action_counter2 / 2 % 2 != 0 { + if self.action_counter2 > 500 && self.action_counter2 & 0x02 != 0 { self.anim_rect.right = self.anim_rect.left; self.anim_rect.bottom = self.anim_rect.top; } @@ -262,7 +262,7 @@ impl NPC { self.cond.set_alive(false); } - if self.action_counter2 > 500 && self.action_counter2 / 2 % 2 != 0 { + if self.action_counter2 > 500 && self.action_counter2 & 0x02 != 0 { self.anim_rect.right = self.anim_rect.left; self.anim_rect.bottom = self.anim_rect.top; } diff --git a/src/npc/ai/plantation.rs b/src/npc/ai/plantation.rs index b179bc7..a41d06c 100644 --- a/src/npc/ai/plantation.rs +++ b/src/npc/ai/plantation.rs @@ -1,5 +1,7 @@ +use crate::caret::CaretType; use crate::common::Direction; use crate::framework::error::GameResult; +use crate::npc::list::NPCList; use crate::npc::NPC; use crate::player::Player; use crate::rng::RNG; @@ -28,7 +30,6 @@ impl NPC { self.anim_num = 0; } } - _ => (), } @@ -284,6 +285,200 @@ impl NPC { self.y += self.vel_y; self.anim_rect = state.constants.npc.n226_kanpachi_plantation[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n228_droll(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.y -= 0x1000; + } + self.vel_x = 0; + self.action_num = 2; + self.anim_num = 0; + } + 2 => { + let player = self.get_closest_player_ref(&players); + if self.x <= player.x { + self.direction = Direction::Right; + } else { + self.direction = Direction::Left; + } + self.anim_counter += 1; + if self.anim_counter > 50 { + self.anim_counter = 0; + self.anim_num += 1; + } + if self.anim_num > 1 { + self.anim_num = 0; + } + } + 10 | 11 => { + if self.action_num == 10 { + self.action_num = 11; + self.anim_num = 2; + self.action_counter = 0; + } + + self.action_counter += 1; + if self.action_counter > 10 { + self.action_num = 12; + self.anim_num = 3; + self.vel_y = -0x600; + if self.direction != Direction::Left { + self.vel_x = 0x200; + } else { + self.vel_x = -0x200; + } + } + } + 12 => { + if self.flags.hit_bottom_wall() { + self.anim_num = 2; + self.action_num = 13; + self.action_counter = 0; + } + } + 13 => { + self.vel_x /= 2; + self.action_counter += 1; + if self.action_counter > 10 { + self.action_num = 1; + } + } + _ => (), + } + + self.vel_y += 0x40; + if self.vel_y > 0x5FF { + self.vel_y = 0x5FF; + } + + self.x += self.vel_x; + self.y += self.vel_y; + + let dir_offset = if self.direction == Direction::Left { 0 } else { 4 }; + + self.anim_rect = state.constants.npc.n228_droll[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n231_rocket( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + } + self.anim_num = 0; + } + 10 | 11 => { + if self.action_num == 10 { + self.action_num = 11; + self.action_counter = 0; + } + + self.action_counter += 1; + self.vel_y += 8; + if self.flags.hit_bottom_wall() { + if self.action_counter > 9 { + self.action_num = 1; + } else { + self.action_num = 12; + } + } + } + 12 | 13 => { + if self.action_num == 12 { + self.npc_flags.set_interactable(false); + self.action_num = 13; + self.action_counter = 0; + self.anim_num = 1; + + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + + for _ in 0..10 { + npc.x = self.x + self.rng.range(-16..16) * 0x200; + npc.y = self.y + self.rng.range(-8..8) * 0x200; + let _ = npc_list.spawn(0x100, npc.clone()); + + state.sound_manager.play_sfx(12); + } + } + + self.vel_y -= 8; + self.action_counter += 1; + + if (self.action_counter & 1) == 0 { + state.create_caret(self.x - 0x1400, self.y + 0x1000, CaretType::Exhaust, Direction::Bottom); + } + if self.action_counter % 2 == 1 { + state.create_caret(self.x + 0x1400, self.y + 0x1000, CaretType::Exhaust, Direction::Bottom); + } + if self.action_counter % 4 == 1 { + state.sound_manager.play_sfx(34); + } + + let player = self.get_closest_player_ref(&players); + + if self.flags.hit_top_wall() || player.flags.hit_top_wall() || self.action_counter > 450 { + if self.flags.hit_top_wall() || player.flags.hit_top_wall() { + self.vel_y = 0; + } + self.action_num = 15; + + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + + for _ in 0..6 { + npc.x = self.x + self.rng.range(-16..16) * 0x200; + npc.y = self.y + self.rng.range(-8..8) * 0x200; + let _ = npc_list.spawn(0x100, npc.clone()); + + state.sound_manager.play_sfx(12); + } + } + } + 15 => { + self.vel_y += 8; + self.action_counter += 1; + + if self.vel_y < 0 { + if (self.action_counter & 7) == 0 { + state.create_caret(self.x - 0x1400, self.y + 0x1000, CaretType::Exhaust, Direction::Bottom); + } + if self.action_counter % 8 == 4 { + state.create_caret(self.x + 0x1400, self.y + 0x1000, CaretType::Exhaust, Direction::Bottom); + } + + if self.action_counter % 16 == 1 { + state.sound_manager.play_sfx(34); + } + } + + if self.flags.hit_bottom_wall() { + self.npc_flags.set_interactable(true); + self.action_num = 1; + self.anim_num = 0; + } + } + _ => (), + } + + self.vel_y = self.vel_y.clamp(-0x5ff, 0x5ff); + self.y += self.vel_y; + + self.anim_rect = state.constants.npc.n231_rocket[self.anim_num as usize]; + Ok(()) } } diff --git a/src/npc/ai/quote.rs b/src/npc/ai/quote.rs index 7bdd871..1f1e76d 100644 --- a/src/npc/ai/quote.rs +++ b/src/npc/ai/quote.rs @@ -68,7 +68,7 @@ impl NPC { if self.action_num == 4 { self.anim_rect.bottom = self.anim_rect.top + self.action_counter / 4; - if self.action_counter / 2 % 2 != 0 { + if self.action_counter & 0x02 != 0 { self.anim_rect.left += 1; } } @@ -125,7 +125,7 @@ impl NPC { if self.action_num == 1 { self.anim_rect.bottom = self.anim_rect.top + self.action_counter / 4; - if self.action_counter / 2 % 2 != 0 { + if self.action_counter & 0x02 != 0 { self.anim_rect.left += 1; } } diff --git a/src/npc/boss/ironhead.rs b/src/npc/boss/ironhead.rs index d69dcbf..579bb2a 100644 --- a/src/npc/boss/ironhead.rs +++ b/src/npc/boss/ironhead.rs @@ -40,7 +40,7 @@ impl NPC { self.damage = 3; self.animate(0, 2, 3); - if self.x <= 0x5FFF { + if self.x <= 0x5fff { // npc->destroy_voice = 0; // todo self.cond.set_explode_die(true); } diff --git a/src/npc/mod.rs b/src/npc/mod.rs index 089b3c4..9975edc 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -272,7 +272,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu 64 => self.tick_n064_first_cave_critter(state, players), 65 => self.tick_n065_first_cave_bat(state, players), 66 => self.tick_n066_misery_bubble(state, npc_list), - 67 => self.tick_n067_misery_floating(state, npc_list), + 67 => self.tick_n067_misery_floating(state, npc_list, flash), 68 => self.tick_n068_balrog_running(state, players, npc_list), 69 => self.tick_n069_pignon(state), 70 => self.tick_n070_sparkle(state), @@ -287,7 +287,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu 79 => self.tick_n079_mahin(state, players), 80 => self.tick_n080_gravekeeper(state, players), 81 => self.tick_n081_giant_pignon(state, players), - 82 => self.tick_n082_misery_standing(state, npc_list), + 82 => self.tick_n082_misery_standing(state, npc_list, flash), 83 => self.tick_n083_igor_cutscene(state), 84 => self.tick_n084_basu_projectile(state), 85 => self.tick_n085_terminal(state, players), @@ -433,13 +433,21 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu 225 => self.tick_n225_megane(state), 226 => self.tick_n226_kanpachi_plantation(state), 227 => self.tick_n227_bucket(state), + 228 => self.tick_n228_droll(state, players), 229 => self.tick_n229_red_flowers_sprouts(state), 230 => self.tick_n230_red_flowers_blooming(state), + 231 => self.tick_n231_rocket(state, players, npc_list), 234 => self.tick_n234_red_flowers_picked(state), + 238 => self.tick_n238_press_sideways(state, players, npc_list), 239 => self.tick_n239_cage_bars(state), 241 => self.tick_n241_critter_red(state, players), + 247 => self.tick_n247_misery_boss(state, players, npc_list), + 248 => self.tick_n248_misery_boss_vanishing(state), 249 => self.tick_n249_misery_boss_energy_shot(state), + 253 => self.tick_n253_experience_capsule(state, npc_list), + 257 => self.tick_n257_red_crystal(state), 258 => self.tick_n258_mimiga_sleeping(state), + 259 => self.tick_n259_curly_unconcious(state, players, npc_list), 292 => self.tick_n292_quake(state), 297 => self.tick_n297_sue_dragon_mouth(state, npc_list), 298 => self.tick_n298_intro_doctor(state), diff --git a/src/npc/utils.rs b/src/npc/utils.rs index 666de5f..9eb4f63 100644 --- a/src/npc/utils.rs +++ b/src/npc/utils.rs @@ -177,14 +177,20 @@ impl NPC { } /// Creates experience drop for this NPC. + #[inline] pub fn create_xp_drop(&self, state: &SharedGameState, npc_list: &NPCList) { - let mut exp = self.exp; + self.create_xp_drop_custom(self.x, self.y, self.exp, state, npc_list) + } + + /// Creates experience drop using specified parameters + pub fn create_xp_drop_custom(&self, x: i32, y: i32, amount: u16, state: &SharedGameState, npc_list: &NPCList) { + let mut exp = amount; let mut xp_npc = NPC::create(1, &state.npc_table); xp_npc.cond.set_alive(true); xp_npc.direction = Direction::Left; - xp_npc.x = self.x; - xp_npc.y = self.y; + xp_npc.x = x; + xp_npc.y = y; while exp > 0 { let exp_piece = if exp >= 20 { @@ -310,7 +316,7 @@ impl NPCList { } /// Removes NPCs whose event number matches the provided one. - pub fn remove_by_event(&mut self, event_num: u16, state: &mut SharedGameState) { + pub fn remove_by_event(&self, event_num: u16, state: &mut SharedGameState) { for npc in self.iter_alive() { if npc.event_num == event_num { npc.cond.set_alive(false); @@ -320,7 +326,7 @@ impl NPCList { } /// Removes NPCs (and creates a smoke effect) whose type IDs match the provided one. - pub fn remove_by_type(&mut self, npc_type: u16, state: &mut SharedGameState) { + pub fn remove_by_type(&self, npc_type: u16, state: &mut SharedGameState) { for npc in self.iter_alive() { if npc.npc_type == npc_type { npc.cond.set_alive(false); diff --git a/src/player/mod.rs b/src/player/mod.rs index 1ac3bce..48fc414 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -728,7 +728,6 @@ impl GameEntity<&NPCList> for Player { if self.shock_counter != 0 { self.shock_counter -= 1; } else if self.damage_taken != 0 { - // todo: damage popup self.damage_taken = 0; }