diff --git a/Cargo.toml b/Cargo.toml index 44790d5..614420a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,13 +69,13 @@ lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "e0b2ff596 num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" -pretty_env_logger = "0.4.0" sdl2 = { version = "=0.34.2", optional = true, features = ["unsafe_textures", "bundled", "static-link"] } sdl2-sys = { version = "=0.34.2", optional = true, features = ["bundled", "static-link"] } serde = { version = "1", features = ["derive"] } serde_derive = "1" serde_json = "1.0" serde_yaml = "0.8" +simple_logger = { version = "1.13" } strum = "0.20" strum_macros = "0.20" # remove and replace when drain_filter is in stable diff --git a/src/bmfont_renderer.rs b/src/bmfont_renderer.rs index 8fdec39..db97dda 100644 --- a/src/bmfont_renderer.rs +++ b/src/bmfont_renderer.rs @@ -90,6 +90,21 @@ impl BMFontRenderer { self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx) } + pub fn draw_colored_text_with_shadow_scaled + Clone>( + &self, + iter: I, + x: f32, + y: f32, + scale: f32, + color: (u8, u8, u8, u8), + constants: &EngineConstants, + texture_set: &mut TextureSet, + ctx: &mut Context, + ) -> GameResult { + self.draw_colored_text_scaled(iter.clone(), x + scale, y + scale, scale, (0, 0, 0, 150), constants, texture_set, ctx)?; + self.draw_colored_text_scaled(iter, x, y, scale, color, constants, texture_set, ctx) + } + pub fn draw_colored_text_scaled>( &self, iter: I, diff --git a/src/builtin/lightmap/direct.png b/src/builtin/lightmap/direct.png deleted file mode 100644 index af79222..0000000 Binary files a/src/builtin/lightmap/direct.png and /dev/null differ diff --git a/src/builtin/lightmap/spot.png b/src/builtin/lightmap/spot.png index 640e30f..297f8a6 100644 Binary files a/src/builtin/lightmap/spot.png and b/src/builtin/lightmap/spot.png differ diff --git a/src/builtin/touch.png b/src/builtin/touch.png index 750980e..d135dea 100644 Binary files a/src/builtin/touch.png and b/src/builtin/touch.png differ diff --git a/src/builtin_fs.rs b/src/builtin_fs.rs index 16911de..7883e75 100644 --- a/src/builtin_fs.rs +++ b/src/builtin_fs.rs @@ -120,7 +120,6 @@ impl BuiltinFS { ]), FSNode::Directory("lightmap", vec![ FSNode::File("spot.png", include_bytes!("builtin/lightmap/spot.png")), - FSNode::File("direct.png", include_bytes!("builtin/lightmap/direct.png")), ]), ]) ], diff --git a/src/common.rs b/src/common.rs index 2ea6afa..6057b31 100644 --- a/src/common.rs +++ b/src/common.rs @@ -9,9 +9,10 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use crate::bitfield; use crate::texture_set::G_MAG; -/// Multiply cave story degrees (0-255, which corresponds to 0°-360°) with this to get +/// Multiply cave story degrees (0-255, which corresponds to 0°-360°) with this constant to get /// respective value in radians. pub const CDEG_RAD: f64 = std::f64::consts::PI / 128.0; + lazy_static! { pub static ref VERSION_BANNER: String = { let version = option_env!("DRS_BUILD_VERSION_OVERRIDE").unwrap_or(env!("CARGO_PKG_VERSION")); @@ -57,6 +58,10 @@ bitfield! { } impl Flag { + pub fn any_flag(&self) -> bool { + self.0 != 0 + } + pub fn hit_anything(&self) -> bool { (self.0 & 0x2ff) != 0 } @@ -121,7 +126,6 @@ bitfield! { // engine specific flags pub friendly_fire, set_friendly_fire: 14; - pub wind, set_wind: 15; } bitfield! { diff --git a/src/engine_constants/npcs.rs b/src/engine_constants/npcs.rs index 4a161b3..b60ec9f 100644 --- a/src/engine_constants/npcs.rs +++ b/src/engine_constants/npcs.rs @@ -585,8 +585,8 @@ pub struct NPCConsts { #[serde(default = "default_n195_background_grate")] pub n195_background_grate: Rect, - #[serde(default = "default_n196_ironhead_motion_wall")] - pub n196_ironhead_motion_wall: [Rect; 2], + #[serde(default = "default_n196_ironhead_wall")] + pub n196_ironhead_wall: [Rect; 2], #[serde(default = "default_n197_porcupine_fish")] pub n197_porcupine_fish: [Rect; 4], @@ -627,8 +627,8 @@ pub struct NPCConsts { #[serde(default = "default_n209_basu_projectile_destroyed_egg_corridor")] pub n209_basu_projectile_destroyed_egg_corridor: [Rect; 4], - #[serde(default = "default_n210_destroyed_egg_corridor")] - pub n210_destroyed_egg_corridor: [Rect; 4], + #[serde(default = "default_n210_beetle_destroyed_egg_corridor")] + pub n210_beetle_destroyed_egg_corridor: [Rect; 4], #[serde(default = "default_n211_small_spikes")] pub n211_small_spikes: [Rect; 4], @@ -1074,6 +1074,9 @@ pub struct NPCConsts { #[serde(default = "default_b03_monster_x")] pub b03_monster_x: [Rect; 29], + + #[serde(default = "default_b04_core")] + pub b04_core: [Rect; 10], } fn default_n001_experience() -> [Rect; 6] { @@ -3211,7 +3214,7 @@ fn default_n195_background_grate() -> Rect { Rect { left: 112, top: 64, right: 128, bottom: 80 } } -fn default_n196_ironhead_motion_wall() -> [Rect; 2] { +fn default_n196_ironhead_wall() -> [Rect; 2] { [ Rect { left: 112, top: 64, right: 144, bottom: 80 }, Rect { left: 112, top: 80, right: 144, bottom: 96 }, @@ -3340,7 +3343,7 @@ fn default_n209_basu_projectile_destroyed_egg_corridor() -> [Rect; 4] { ] } -fn default_n210_destroyed_egg_corridor() -> [Rect; 4] { +fn default_n210_beetle_destroyed_egg_corridor() -> [Rect; 4] { [ Rect { left: 0, top: 112, right: 16, bottom: 128 }, Rect { left: 16, top: 112, right: 32, bottom: 128 }, @@ -4850,3 +4853,20 @@ fn default_b03_monster_x() -> [Rect; 29] { Rect { left: 48, top: 208, right: 64, bottom: 224 }, ] } + +fn default_b04_core() -> [Rect; 10] { + [ + Rect { left: 0, top: 0, right: 72, bottom: 112 }, // face + Rect { left: 0, top: 112, right: 72, bottom: 224 }, + Rect { left: 160, top: 0, right: 232, bottom: 112 }, + Rect { left: 0, top: 0, right: 0, bottom: 0 }, + + Rect { left: 72, top: 0, right: 160, bottom: 112 }, // tail + Rect { left: 72, top: 112, right: 160, bottom: 224 }, + Rect { left: 0, top: 0, right: 0, bottom: 0 }, + + Rect { left: 256, top: 0, right: 320, bottom: 40 }, // small head + Rect { left: 256, top: 40, right: 320, bottom: 80 }, + Rect { left: 256, top: 80, right: 320, bottom: 120 }, + ] +} diff --git a/src/framework/backend_glutin.rs b/src/framework/backend_glutin.rs index f26399c..89e6197 100644 --- a/src/framework/backend_glutin.rs +++ b/src/framework/backend_glutin.rs @@ -155,7 +155,6 @@ impl BackendEventLoop for GlutinEventLoop { state_ref.shutdown(); } Event::Resumed => { - println!("resumed!"); { let mut mutex = GAME_SUSPENDED.lock().unwrap(); *mutex = false; @@ -171,7 +170,6 @@ impl BackendEventLoop for GlutinEventLoop { } } Event::Suspended => { - println!("suspended!"); { let mut mutex = GAME_SUSPENDED.lock().unwrap(); *mutex = true; diff --git a/src/lib.rs b/src/lib.rs index 9bb1e30..95a85a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ use std::time::Instant; use directories::ProjectDirs; use lazy_static::lazy_static; -use pretty_env_logger::env_logger::Env; use crate::builtin_fs::BuiltinFS; use crate::framework::context::Context; @@ -188,9 +187,7 @@ impl Game { } pub fn init() -> GameResult { - pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info")) - //.filter(Some("ndk_glue"), LevelFilter::Trace) - .init(); + let _ = simple_logger::init_with_level(log::Level::Info); #[cfg(not(target_os = "android"))] let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") { diff --git a/src/npc/ai/balrog.rs b/src/npc/ai/balrog.rs index 4d6f552..85722f5 100644 --- a/src/npc/ai/balrog.rs +++ b/src/npc/ai/balrog.rs @@ -62,7 +62,7 @@ impl NPC { self.anim_counter = 0; } } - _ => {} + _ => (), } self.vel_y = clamp(self.vel_y, -0x5ff, 0x5ff); @@ -162,7 +162,7 @@ impl NPC { self.action_counter = 0; } } - _ => {} + _ => (), } self.vel_y += 0x20; @@ -501,13 +501,12 @@ impl NPC { } } - println!("y: {}", self.y as f64 / 512.0); if self.y < -32 * 0x200 { self.npc_type = 0; state.quake_counter = 30; } } - _ => {} + _ => (), } if self.target_x != 0 && self.rng.range(0..10) == 0 { @@ -603,7 +602,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } self.vel_y = clamp(self.vel_y, -0x5ff, 0x5ff); @@ -793,7 +792,7 @@ impl NPC { self.action_counter = 0; } } - _ => {} + _ => (), } if self.action_num != 5 { @@ -977,7 +976,7 @@ impl NPC { self.action_num = 0; } } - _ => {} + _ => (), } self.vel_x = clamp(self.vel_x, -0x400, 0x400); @@ -1196,7 +1195,7 @@ impl NPC { self.action_num = 0; } } - _ => {} + _ => (), } self.vel_y += 0x20; diff --git a/src/npc/ai/booster.rs b/src/npc/ai/booster.rs index cf7bc09..9b4fd70 100644 --- a/src/npc/ai/booster.rs +++ b/src/npc/ai/booster.rs @@ -76,7 +76,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } self.vel_y += 0x40; diff --git a/src/npc/ai/chaco.rs b/src/npc/ai/chaco.rs index ee857f1..ea2c75c 100644 --- a/src/npc/ai/chaco.rs +++ b/src/npc/ai/chaco.rs @@ -58,7 +58,7 @@ impl NPC { state.create_caret(self.x, self.y, CaretType::Zzz, Direction::Left); } } - _ => {} + _ => (), } let dir_offset = if self.direction == Direction::Left { 0 } else { 7 }; diff --git a/src/npc/ai/characters.rs b/src/npc/ai/characters.rs index 44c2a66..8ab8b50 100644 --- a/src/npc/ai/characters.rs +++ b/src/npc/ai/characters.rs @@ -58,7 +58,7 @@ impl NPC { self.x += self.direction.vector_x() * 0x200; } 5 => self.anim_num = 5, - _ => {} + _ => (), } self.vel_y += 0x20; @@ -225,7 +225,7 @@ impl NPC { self.action_counter2 = 0; } } - _ => {} + _ => (), } if self.action_num < 30 || self.action_num >= 40 { @@ -302,7 +302,7 @@ impl NPC { self.anim_rect = state.constants.npc.n062_kazuma_computer[self.anim_num as usize]; } } - _ => {} + _ => (), } Ok(()) @@ -342,7 +342,7 @@ impl NPC { self.vel_x = self.direction.vector_x() * 0x200; } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -412,7 +412,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; @@ -467,11 +467,103 @@ impl NPC { self.cond.set_alive(false); } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n167_booster_falling[self.anim_num as usize]; Ok(()) } + + pub(crate) fn tick_n217_itoh(&mut self, state: &mut SharedGameState) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.anim_num = 0; + self.anim_counter = 0; + self.vel_x = 0; + } + 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; + } + } + 10 => { + self.anim_num = 2; + self.vel_x = 0; + } + 20 => { + self.action_num = 21; + self.anim_num = 2; + self.vel_x += 0x200; + self.vel_y -= 0x400; + } + 21 => { + if self.flags.hit_bottom_wall() { + self.anim_num = 3; + self.action_num = 30; + self.action_counter = 0; + self.vel_x = 0; + self.target_x = self.x; + } + } + 30 => { + self.anim_num = 3; + self.action_counter += 1; + self.x = if ((self.action_counter / 2) & 1) != 0 { self.target_x + 512 } else { self.target_x } + } + + 40 | 41 => { + if self.action_num == 40 { + self.action_num = 41; + self.vel_y = -512; + self.anim_num = 2; + } + if self.flags.hit_bottom_wall() { + self.action_num = 42; + self.anim_num = 4; + } + } + 42 => { + self.vel_x = 0; + self.anim_num = 4; + } + + 50 | 51 => { + if self.action_num == 50 { + self.action_num = 51; + self.action_counter = 0; + } + self.action_counter += 1; + if self.action_counter > 32 { + self.action_num = 42; + } + self.vel_x = 512; + + self.animate(3, 4, 7); + } + _ => (), + } + + self.vel_y += 0x40; + if self.vel_y > 0x5ff { + self.vel_y = 0x5ff; + } + + self.x += self.vel_x; + self.y += self.vel_y; + + self.anim_rect = state.constants.npc.n217_itoh[self.anim_num as usize]; + + Ok(()) + } } diff --git a/src/npc/ai/curly.rs b/src/npc/ai/curly.rs index 2e57ca5..cff9399 100644 --- a/src/npc/ai/curly.rs +++ b/src/npc/ai/curly.rs @@ -1,11 +1,11 @@ use num_traits::{abs, clamp}; use crate::caret::CaretType; -use crate::common::Direction; +use crate::common::{Direction, Rect}; use crate::framework::error::GameResult; use crate::npc::list::NPCList; use crate::npc::NPC; -use crate::player::Player; +use crate::player::{Player, TargetPlayer}; use crate::rng::RNG; use crate::shared_game_state::SharedGameState; use crate::weapon::bullet::BulletManager; @@ -111,7 +111,7 @@ impl NPC { self.animate(8, 1, 4); self.x += self.direction.vector_x() * 0x100; } - _ => {} + _ => (), } if self.vel_y > 0x5ff { @@ -244,7 +244,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } if self.action_num > 10 && self.action_num < 30 && bullet_manager.count_bullets_type_idx_all(6) > 0 { @@ -349,11 +349,440 @@ impl NPC { 0 }; } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n165_curly_collapsed[self.anim_num as usize]; Ok(()) } + + pub(crate) fn tick_n180_curly_ai( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { + let player = self.get_closest_player_ref(&players); + + if self.y >= player.y - 0x14000 { + if state.npc_curly_counter > 0 { + self.target_x = state.npc_curly_target.0; + self.target_y = state.npc_curly_target.1; + } else { + self.target_x = player.x; + self.target_y = player.y; + } + } else { + self.target_x = if self.y > 0x1FFFF { 0 } else { 0x280000 }; + self.target_y = self.y; + } + + if (self.vel_x < 0 && self.flags.hit_left_wall()) || (self.vel_x > 0 && self.flags.hit_right_wall()) { + self.vel_x = 0; + } + + match self.action_num { + 20 => { + self.action_num = 100; + self.anim_num = 0; + self.x = player.x; + self.y = player.y; + + let mut npc = NPC::create(183, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + let _ = npc_list.spawn(0x100, npc); + + if !state.get_flag(563) { + let mut npc = NPC::create(181, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + let _ = npc_list.spawn(0x100, npc); + } else { + let mut npc = NPC::create(182, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + let _ = npc_list.spawn(0x100, npc); + } + } + 40 | 41 => { + if self.action_num == 40 { + self.action_num = 41; + self.action_counter = 0; + self.anim_num = 10; + } + + self.action_counter += 1; + if self.action_counter == 750 { + self.npc_flags.set_interactable(false); + self.anim_num = 0; + } + if self.action_counter > 1000 { + self.action_num = 100; + self.anim_num = 0; + + let mut npc = NPC::create(183, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + let _ = npc_list.spawn(0x100, npc); + + if !state.get_flag(563) { + let mut npc = NPC::create(181, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + let _ = npc_list.spawn(0x100, npc); + } else { + let mut npc = NPC::create(182, &state.npc_table); + npc.cond.set_alive(true); + npc.parent_id = self.id; + let _ = npc_list.spawn(0x100, npc); + } + } + } + 100 => { + self.anim_num = 0; + self.vel_x = 7 * self.vel_x / 8; + self.action_counter3 = 0; + + if self.x <= self.target_x + 0x2000 { + if self.x < self.target_x - 0x2000 { + self.action_num = 300; + self.anim_num = 1; + self.direction = Direction::Right; + self.action_counter = self.rng.range(20..60) as u16; + } + } else { + self.action_num = 200; + self.anim_num = 1; + self.direction = Direction::Left; + self.action_counter = self.rng.range(20..60) as u16; + } + } + 200 => { + self.vel_x -= 0x20; + self.direction = Direction::Left; + if self.flags.hit_left_wall() { + self.action_counter3 += 1; + } else { + self.action_counter3 = 0; + } + } + 210 => { + self.vel_x -= 0x20; + self.direction = Direction::Left; + if self.flags.hit_bottom_wall() { + self.action_num = 100; + } + } + 300 => { + self.vel_x += 0x20; + self.direction = Direction::Right; + if self.flags.hit_right_wall() { + self.action_counter3 += 1; + } else { + self.action_counter3 = 0; + } + } + 310 => { + self.vel_x += 0x20; + self.direction = Direction::Right; + if self.flags.hit_bottom_wall() { + self.action_num = 100; + } + } + _ => (), + } + + if state.npc_curly_counter > 0 { + state.npc_curly_counter -= 1; + } + + if state.npc_curly_counter == 70 { + self.action_counter2 = 10; + } else if state.npc_curly_counter == 60 && self.flags.hit_bottom_wall() && self.rng.range(0..2) != 0 { + self.action_num = if self.x <= self.target_x { 310 } else { 210 }; + self.anim_num = 1; + self.action_counter3 = 0; + self.vel_y = -0x600; + state.sound_manager.play_sfx(15); + } + + let mut delx = self.x - self.target_x; + let dely = self.y - self.target_y; + if delx < 0 { + delx = -delx; + } + + if self.action_num == 100 { + self.anim_num = if delx + 0x400 >= dely { 0 } else { 5 } + } + + if self.action_num == 210 || self.action_num == 310 { + self.anim_num = if delx + 0x400 >= dely { 1 } else { 6 } + } + + if self.action_num == 200 || self.action_num == 300 { + self.anim_counter += 1; + self.anim_num = (self.anim_counter / 4 % 4) + if delx + 0x400 >= dely { 1 } else { 6 }; + + if self.action_counter > 0 { + self.action_counter -= 1; + if self.flags.any_flag() && self.action_counter3 > 10 { + self.action_num += 10; + self.anim_num = 1; + self.action_counter3 = 0; + self.vel_y = -0x600; + state.sound_manager.play_sfx(15); + } + } else { + self.action_num = 100; + self.anim_num = 0; + } + } + + if self.action_num >= 100 && self.action_num < 500 { + if self.x >= player.x - 0xA000 && self.x <= player.x + 0xA000 { + self.vel_y += 0x33; + } else { + self.vel_y += if self.flags.any_flag() { 0x10 } else { 0x33 }; + } + } + + self.vel_x = self.vel_x.clamp(-0x300, 0x300); + + if self.vel_y > 0x5FF { + self.vel_y = 0x5FF; + } + + self.x += self.vel_x; + self.y += self.vel_y; + + if self.action_num >= 100 && !self.flags.hit_bottom_wall() && self.anim_num != 1000 { + if delx + 0x400 >= dely { + self.anim_num = 1; + } else { + self.anim_num = 6; + } + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 11 }; + + self.anim_rect = state.constants.npc.n180_curly_ai[self.anim_num as usize + dir_offset]; + Ok(()) + } + + pub(crate) fn tick_n181_curly_ai_machine_gun( + &mut self, + state: &mut SharedGameState, + npc_list: &NPCList, + bullet_manager: &mut BulletManager, + ) -> GameResult { + if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if parent.anim_num > 4 { + self.direction = parent.direction; + self.x = parent.x; + self.y = parent.y - 0x1400; + self.anim_num = 1; + } else { + self.x = parent.x + + if parent.direction == Direction::Left { + self.direction = Direction::Left; + -0x1000 + } else { + self.direction = Direction::Right; + 0x1000 + }; + self.y = parent.y; + self.anim_num = 0; + } + + if parent.anim_num == 1 || parent.anim_num == 3 || parent.anim_num == 6 || parent.anim_num == 8 { + self.y -= 0x200; + } + + if self.action_num == 0 { + if parent.action_counter2 == 10 { + parent.action_counter2 = 0; + self.action_num = 10; + self.action_counter = 0; + } + } else if self.action_num == 10 { + self.action_counter += 1; + if self.action_counter % 6 == 1 { + if self.anim_num != 0 { + if self.direction != Direction::Left { + bullet_manager.create_bullet( + self.x + 0x400, + self.y - 0x800, + 12, + TargetPlayer::Player1, + Direction::Up, + &state.constants, + ); + state.create_caret(self.x + 0x400, self.y - 0x800, CaretType::Shoot, Direction::Left); + } else { + bullet_manager.create_bullet( + self.x - 0x400, + self.y - 0x800, + 12, + TargetPlayer::Player1, + Direction::Up, + &state.constants, + ); + state.create_caret(self.x - 0x400, self.y - 0x800, CaretType::Shoot, Direction::Left); + } + } else if self.direction != Direction::Left { + bullet_manager.create_bullet( + self.x + 0x800, + self.y + 0x600, + 12, + TargetPlayer::Player1, + Direction::Right, + &state.constants, + ); + state.create_caret(self.x + 0x800, self.y + 0x600, CaretType::Shoot, Direction::Left); + } else { + bullet_manager.create_bullet( + self.x - 0x800, + self.y + 0x600, + 12, + TargetPlayer::Player1, + Direction::Left, + &state.constants, + ); + state.create_caret(self.x - 0x800, self.y + 0x600, CaretType::Shoot, Direction::Left); + } + } + if self.action_counter == 60 { + self.action_num = 0; + } + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; + + self.anim_rect = state.constants.npc.n181_curly_ai_machine_gun[self.anim_num as usize + dir_offset]; + } + Ok(()) + } + + pub(crate) fn tick_n182_curly_ai_polar_star( + &mut self, + state: &mut SharedGameState, + npc_list: &NPCList, + bullet_manager: &mut BulletManager, + ) -> GameResult { + if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if parent.anim_num > 4 { + self.direction = parent.direction; + self.x = parent.x; + self.y = parent.y - 0x1400; + self.anim_num = 1; + } else { + self.x = parent.x + + if parent.direction == Direction::Left { + self.direction = Direction::Left; + -0x1000 + } else { + self.direction = Direction::Right; + 0x1000 + }; + self.y = parent.y; + self.anim_num = 0; + } + if parent.anim_num == 1 || parent.anim_num == 3 || parent.anim_num == 6 || parent.anim_num == 8 { + self.y -= 0x200; + } + + if self.action_num == 0 { + if parent.action_counter2 == 10 { + parent.action_counter2 = 0; + self.action_num = 10; + self.action_counter = 0; + } + } else if self.action_num == 10 { + self.action_counter += 1; + if self.action_counter % 6 == 1 { + if self.anim_num != 0 { + if self.direction != Direction::Left { + bullet_manager.create_bullet( + self.x + 0x400, + self.y - 0x800, + 12, + TargetPlayer::Player1, + Direction::Up, + &state.constants, + ); + state.create_caret(self.x + 0x400, self.y - 0x800, CaretType::Shoot, Direction::Left); + } else { + bullet_manager.create_bullet( + self.x - 0x400, + self.y - 0x800, + 12, + TargetPlayer::Player1, + Direction::Up, + &state.constants, + ); + state.create_caret(self.x - 0x400, self.y - 0x800, CaretType::Shoot, Direction::Left); + } + } else if self.direction != Direction::Left { + bullet_manager.create_bullet( + self.x + 0x800, + self.y + 0x600, + 12, + TargetPlayer::Player1, + Direction::Right, + &state.constants, + ); + state.create_caret(self.x + 0x800, self.y + 0x600, CaretType::Shoot, Direction::Left); + } else { + bullet_manager.create_bullet( + self.x - 0x800, + self.y + 0x600, + 12, + TargetPlayer::Player1, + Direction::Left, + &state.constants, + ); + state.create_caret(self.x - 0x800, self.y + 0x600, CaretType::Shoot, Direction::Left); + } + } + if self.action_counter == 60 { + self.action_num = 0; + } + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; + + self.anim_rect = state.constants.npc.n182_curly_ai_polar_star[self.anim_num as usize + dir_offset]; + } + Ok(()) + } + + pub(crate) fn tick_n183_curly_air_tank_bubble( + &mut self, + state: &mut SharedGameState, + npc_list: &NPCList, + ) -> GameResult { + if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if self.action_num == 0 { + self.x = parent.x; + self.y = parent.y; + self.action_num = 1; + } + + self.x += (parent.x - self.x) / 2; + self.y += (parent.y - self.y) / 2; + + self.animate(1, 0, 1); + + self.anim_rect = if parent.flags.in_water() { + state.constants.npc.n183_curly_air_tank_bubble[self.anim_num as usize] + } else { + Rect::new(0, 0, 0, 0) + } + } + + Ok(()) + } } diff --git a/src/npc/ai/doctor.rs b/src/npc/ai/doctor.rs index aa60106..a955b30 100644 --- a/src/npc/ai/doctor.rs +++ b/src/npc/ai/doctor.rs @@ -92,7 +92,7 @@ impl NPC { self.action_num = 0x14; } } - _ => {} + _ => (), } self.x += self.vel_x; diff --git a/src/npc/ai/egg_corridor.rs b/src/npc/ai/egg_corridor.rs index dbde043..5f08235 100644 --- a/src/npc/ai/egg_corridor.rs +++ b/src/npc/ai/egg_corridor.rs @@ -1,13 +1,14 @@ -use crate::framework::error::GameResult; use num_traits::{abs, clamp}; use crate::caret::CaretType; -use crate::common::{CDEG_RAD, Direction, Rect}; +use crate::common::{Direction, Rect, CDEG_RAD}; +use crate::framework::error::GameResult; use crate::npc::list::NPCList; use crate::npc::NPC; -use crate::player::Player; +use crate::player::{Player, TargetPlayer}; use crate::rng::RNG; use crate::shared_game_state::SharedGameState; +use crate::weapon::bullet::BulletManager; impl NPC { pub(crate) fn tick_n002_behemoth(&mut self, state: &mut SharedGameState) -> GameResult { @@ -20,23 +21,25 @@ impl NPC { match self.action_num { 0 => { self.vel_x = match self.direction { - Direction::Left => { -0x100 } - Direction::Right => { 0x100 } - _ => { 0 } + Direction::Left => -0x100, + Direction::Right => 0x100, + _ => 0, }; self.anim_counter += 1; if self.anim_counter > 8 { self.anim_counter = 0; self.anim_num = (self.anim_num + 1) % 3; - self.anim_rect = state.constants.npc.n002_behemoth[self.anim_num as usize + if self.direction == Direction::Right { 7 } else { 0 }]; + self.anim_rect = state.constants.npc.n002_behemoth + [self.anim_num as usize + if self.direction == Direction::Right { 7 } else { 0 }]; } if self.shock > 0 { self.action_counter = 0; self.action_num = 1; self.anim_num = 4; - self.anim_rect = state.constants.npc.n002_behemoth[self.anim_num as usize + if self.direction == Direction::Right { 7 } else { 0 }]; + self.anim_rect = state.constants.npc.n002_behemoth + [self.anim_num as usize + if self.direction == Direction::Right { 7 } else { 0 }]; } } 1 => { @@ -50,7 +53,8 @@ impl NPC { self.anim_num = 6; self.anim_counter = 0; self.damage = 5; - self.anim_rect = state.constants.npc.n002_behemoth[self.anim_num as usize + if self.direction == Direction::Right { 7 } else { 0 }]; + self.anim_rect = state.constants.npc.n002_behemoth + [self.anim_num as usize + if self.direction == Direction::Right { 7 } else { 0 }]; } else { self.action_num = 0; self.anim_counter = 0; @@ -59,9 +63,9 @@ impl NPC { } 2 => { self.vel_x = match self.direction { - Direction::Left => { -0x400 } - Direction::Right => { 0x400 } - _ => { 0 } + Direction::Left => -0x400, + Direction::Right => 0x400, + _ => 0, }; self.action_counter += 1; @@ -80,10 +84,11 @@ impl NPC { state.quake_counter = 8; } - self.anim_rect = state.constants.npc.n002_behemoth[self.anim_num as usize + if self.direction == Direction::Right { 7 } else { 0 }]; + self.anim_rect = state.constants.npc.n002_behemoth + [self.anim_num as usize + if self.direction == Direction::Right { 7 } else { 0 }]; } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -97,14 +102,19 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n005_green_critter(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult { + pub(crate) fn tick_n005_green_critter( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + ) -> GameResult { match self.action_num { 0 | 1 => { if self.action_num == 0 { self.y += 0x600; self.action_num = 1; self.anim_num = 0; - self.anim_rect = state.constants.npc.n005_green_critter[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; + self.anim_rect = state.constants.npc.n005_green_critter + [self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; } let player = self.get_closest_player_mut(players); @@ -120,13 +130,15 @@ impl NPC { } if self.action_counter >= 8 - && self.x - (112 * 0x200) < player.x - && self.x + (112 * 0x200) > player.x - && self.y - (80 * 0x200) < player.y - && self.y + (80 * 0x200) > player.y { + && self.x - 0xe000 < player.x + && self.x + 0xe000 > player.x + && self.y - 0xa000 < player.y + && self.y + 0xa000 > player.y + { if self.anim_num != 1 { self.anim_num = 1; - self.anim_rect = state.constants.npc.n005_green_critter[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; + self.anim_rect = state.constants.npc.n005_green_critter + [self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; } } else { if self.action_counter < 8 { @@ -135,7 +147,8 @@ impl NPC { if self.anim_num != 0 { self.anim_num = 0; - self.anim_rect = state.constants.npc.n005_green_critter[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; + self.anim_rect = state.constants.npc.n005_green_critter + [self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; } } @@ -145,22 +158,25 @@ impl NPC { if self.anim_num != 0 { self.anim_num = 0; - self.anim_rect = state.constants.npc.n005_green_critter[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; + self.anim_rect = state.constants.npc.n005_green_critter + [self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; } } if self.action_counter >= 8 && self.target_x >= 100 - && self.x - (64 * 0x200) < player.x - && self.x + (64 * 0x200) > player.x - && self.y - (80 * 0x200) < player.y - && self.y + (80 * 0x200) > player.y { + && self.x - 0x8000 < player.x + && self.x + 0x8000 > player.x + && self.y - 0xa000 < player.y + && self.y + 0xa000 > player.y + { self.action_num = 2; self.action_counter = 0; if self.anim_num != 0 { self.anim_num = 0; - self.anim_rect = state.constants.npc.n005_green_critter[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; + self.anim_rect = state.constants.npc.n005_green_critter + [self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; } } } @@ -171,7 +187,8 @@ impl NPC { if self.anim_num != 2 { self.anim_num = 2; - self.anim_rect = state.constants.npc.n005_green_critter[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; + self.anim_rect = state.constants.npc.n005_green_critter + [self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; } self.vel_y = -0x5ff; @@ -194,11 +211,12 @@ impl NPC { if self.anim_num != 0 { self.anim_num = 0; - self.anim_rect = state.constants.npc.n005_green_critter[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; + self.anim_rect = state.constants.npc.n005_green_critter + [self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; } } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -218,9 +236,13 @@ impl NPC { self.action_num = 1; match self.direction { - Direction::Left => { self.action_num = 1; } - Direction::Right => { self.action_num = 3; } - _ => {} + Direction::Left => { + self.action_num = 1; + } + Direction::Right => { + self.action_num = 3; + } + _ => (), } } 1 => { @@ -301,10 +323,11 @@ impl NPC { self.anim_num = 1; } } - _ => {} + _ => (), } - self.anim_rect = state.constants.npc.n006_green_beetle[self.anim_num as usize + if self.direction == Direction::Right { 5 } else { 0 }]; + self.anim_rect = state.constants.npc.n006_green_beetle + [self.anim_num as usize + if self.direction == Direction::Right { 5 } else { 0 }]; Ok(()) } @@ -347,7 +370,7 @@ impl NPC { self.action_num = 1; } } - _ => {} + _ => (), } if self.vel_x < 0 { @@ -367,13 +390,18 @@ impl NPC { } if self.anim_counter == 1 { - self.anim_rect = state.constants.npc.n007_basil[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; + self.anim_rect = state.constants.npc.n007_basil + [self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }]; } Ok(()) } - pub(crate) fn tick_n008_blue_beetle(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult { + pub(crate) fn tick_n008_blue_beetle( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + ) -> GameResult { match self.action_num { 0 => { let player = self.get_closest_player_mut(players); @@ -394,7 +422,7 @@ impl NPC { self.x = player.x - 256 * 0x200; self.vel_x = 0x2ff; } - _ => {} + _ => (), } } else { self.npc_flags.set_shootable(false); @@ -431,7 +459,7 @@ impl NPC { self.y += self.vel_y; } } - _ => {} + _ => (), } self.anim_counter += 1; @@ -445,7 +473,8 @@ impl NPC { } if self.anim_counter == 1 { - self.anim_rect = state.constants.npc.n008_blue_beetle[self.anim_num as usize + if self.direction == Direction::Right { 2 } else { 0 }]; + self.anim_rect = state.constants.npc.n008_blue_beetle + [self.anim_num as usize + if self.direction == Direction::Right { 2 } else { 0 }]; } Ok(()) @@ -528,7 +557,7 @@ impl NPC { } self.animate(1, 0, 1); } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n025_lift[self.anim_num as usize]; @@ -536,7 +565,12 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n058_basu(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult { + pub(crate) fn tick_n058_basu( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { let player = self.get_closest_player_mut(players); match self.action_num { @@ -599,13 +633,12 @@ impl NPC { self.vel_x = 0; self.x = self.target_x; self.damage = 0; - self.direction = Direction::from_int_facing(self.tsc_direction as usize) - .unwrap_or(Direction::Left); + self.direction = Direction::from_int_facing(self.tsc_direction as usize).unwrap_or(Direction::Left); self.anim_rect = Rect::new(0, 0, 0, 0); return Ok(()); } } - _ => {} + _ => (), } if self.action_counter < 150 { @@ -677,6 +710,490 @@ impl NPC { Ok(()) } + pub(crate) fn tick_n200_zombie_dragon( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { + if self.action_num < 100 && self.life < 950 { + self.action_num = 100; + self.npc_flags.set_shootable(false); + self.damage = 0; + + state.sound_manager.play_sfx(72); + npc_list.create_death_smoke(self.x, self.y, self.display_bounds.right as usize, 8, state, &self.rng); + self.create_xp_drop(state, npc_list); + } + + match self.action_num { + 0 | 10 => { + if self.action_num == 0 { + self.action_num = 10; + self.action_counter3 = 0; + } + + let player = self.get_closest_player_mut(players); + + self.animate(30, 0, 1); + + if self.action_counter3 != 0 { + self.action_counter3 -= 1; + } + + if self.action_counter3 == 0 && player.x > self.x - 0xE000 && player.x < self.x + 0xE000 { + self.action_num = 20; + } + } + 20 | 21 => { + if self.action_num == 20 { + self.action_num = 21; + self.action_counter = 0; + } + + self.action_counter += 1; + if (self.action_counter & 1) != 0 { + self.anim_num = 2; + } else { + self.anim_num = 3; + } + if self.action_counter > 30 { + self.action_num = 30; + } + + let player = self.get_closest_player_mut(players); + self.direction = if player.x >= self.x { Direction::Right } else { Direction::Left }; + } + 30 | 31 => { + let player = self.get_closest_player_mut(players); + if self.action_num == 30 { + self.action_num = 31; + self.action_counter = 0; + self.anim_num = 4; + self.target_x = player.x; + self.target_y = player.y; + } + + self.action_counter += 1; + if self.action_counter <= 39 && self.action_counter % 8 == 1 { + let px = self.x + (self.direction.vector_x() * 0x1c00) - self.target_x; + let py = self.y - self.target_y; + + let deg = f64::atan2(py as f64, px as f64) + self.rng.range(-6..6) as f64 * CDEG_RAD; + + let mut npc = NPC::create(202, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x + self.direction.vector_x() * 0x1c00; + npc.y = self.y; + npc.vel_x = (deg.cos() * -1536.0) as i32; + npc.vel_y = (deg.sin() * -1536.0) as i32; + + let _ = npc_list.spawn(0x100, npc); + + if !player.cond.hidden() { + state.sound_manager.play_sfx(33); + } + } + if self.action_counter > 60 { + self.action_num = 10; + self.action_counter3 = self.rng.range(100..200) as u16; + self.anim_counter = 0; + } + } + 100 => { + self.anim_num = 5; + } + _ => (), + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 6 }; + + self.anim_rect = state.constants.npc.n200_zombie_dragon[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n201_zombie_dragon_dead(&mut self, state: &mut SharedGameState) -> GameResult { + let dir_offset = if self.direction == Direction::Left { 0 } else { 1 }; + + self.anim_rect = state.constants.npc.n201_zombie_dragon_dead[dir_offset]; + Ok(()) + } + + pub(crate) fn tick_n202_zombie_dragon_projectile(&mut self, state: &mut SharedGameState) -> GameResult { + self.y += self.vel_y; + self.x += self.vel_x; + + self.animate(1, 0, 2); + + self.anim_rect = state.constants.npc.n202_zombie_dragon_projectile[self.anim_num as usize]; + self.action_counter3 += 1; + if self.flags.hit_anything() || 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_n203_critter_destroyed_egg_corridor( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + ) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.y += 0x600; + self.action_num = 1; + self.anim_num = 0; + } + + let player = self.get_closest_player_mut(players); + + if self.x > player.x { + self.direction = Direction::Left; + } else { + self.direction = Direction::Right; + } + + if self.target_x < 100 { + self.target_x += 1; + } + + if self.action_counter >= 8 + && self.x - 0xe000 < player.x + && self.x + 0xe000 > player.x + && self.y - 0xa000 < player.y + && self.y + 0xa000 > player.y + { + self.anim_num = 1; + } else { + if self.action_counter < 8 { + self.action_counter += 1; + } + } + + if self.shock > 0 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 0; + } + + if self.action_counter >= 8 + && self.target_x >= 100 + && self.x - 0x6000 < player.x + && self.x + 0x6000 > player.x + && self.y - 0xa000 < player.y + && self.y + 0xa000 > player.y + { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 0; + } + } + 2 => { + self.action_counter += 1; + if self.action_counter > 8 { + self.action_num = 3; + self.anim_num = 2; + + self.vel_y = -0x5ff; + state.sound_manager.play_sfx(30); + + if self.direction == Direction::Left { + self.vel_x = -0x100; + } else { + self.vel_x = 0x100; + } + } + } + 3 => { + if self.flags.hit_bottom_wall() { + self.vel_x = 0; + self.action_counter = 0; + self.action_num = 1; + self.anim_num = 0; + + state.sound_manager.play_sfx(23); + } + } + _ => (), + } + + let dir_offset = if self.direction == Direction::Right { 3 } else { 0 }; + self.anim_rect = state.constants.npc.n203_critter_destroyed_egg_corridor[self.anim_num as usize + dir_offset]; + + self.vel_y += 0x40; + if self.vel_y > 0x5ff { + self.vel_y = 0x5ff; + } + + self.x += self.vel_x; + self.y += self.vel_y; + + Ok(()) + } + + pub(crate) fn tick_n204_small_falling_spike( + &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.target_x = self.x; + } + + let player = self.get_closest_player_ref(&players); + + if player.x > self.x - 0x1800 && player.x < self.x + 0x1800 && player.y > self.y { + self.action_num = 2; + } + } + 2 => { + self.action_counter += 1; + self.x = if ((self.action_counter / 6) & 1) != 0 { self.target_x - 0x200 } else { self.target_x }; + + if self.action_counter > 30 { + self.action_num = 3; + self.anim_num = 1; + } + } + 3 => { + self.vel_y += 0x20; + + if self.flags.hit_anything() { + let player = self.get_closest_player_ref(&players); + if !player.cond.hidden() { + state.sound_manager.play_sfx(12); + } + + npc_list.create_death_smoke( + self.x, + self.y, + self.display_bounds.right as usize, + 4, + state, + &self.rng, + ); + self.cond.set_alive(false); + } + } + _ => (), + } + + if self.vel_y > 3072 { + self.vel_y = 3072; + } + + self.y += self.vel_y; + self.anim_rect = state.constants.npc.n204_small_falling_spike[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n205_large_falling_spike( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + bullet_manager: &mut BulletManager, + ) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.target_x = self.x; + self.y += 0x800; + } + + let player = self.get_closest_player_ref(&players); + if player.x > self.x - 0x1800 && player.x < self.x + 0x1800 && player.y > self.y { + self.action_num = 2; + } + } + 2 => { + self.action_counter += 1; + + if ((self.action_counter / 6) & 1) != 0 { + self.x = self.target_x - 0x200; + } else { + self.x = self.target_x; + } + + if self.action_counter > 30 { + self.action_num = 3; + self.anim_num = 1; + self.action_counter = 0; + } + } + 3 => { + let player = self.get_closest_player_ref(&players); + + if player.y <= self.y { + self.npc_flags.set_solid_hard(true); + self.damage = 0; + } else { + self.npc_flags.set_solid_hard(false); + self.damage = 127; + } + + self.vel_y += 0x20; + self.action_counter += 1; + + if self.action_counter > 8 && self.flags.any_flag() { + self.action_num = 4; + self.action_counter = 0; + self.damage = 0; + self.vel_y = 0; + self.npc_flags.set_solid_hard(true); + + state.sound_manager.play_sfx(12); + npc_list.create_death_smoke( + self.x, + self.y, + self.display_bounds.right as usize, + 4, + state, + &self.rng, + ); + bullet_manager.create_bullet( + self.x, + self.y, + 24, + TargetPlayer::Player1, + Direction::Left, + &state.constants, + ); + } + } + 4 => { + self.action_counter += 1; + if self.action_counter > 4 { + self.action_num = 5; + self.npc_flags.set_shootable(true); + } + } + _ => (), + } + + if self.vel_y > 0xC00 { + self.vel_y = 0xC00; + } + + self.y += self.vel_y; + + self.anim_rect = state.constants.npc.n205_large_falling_spike[self.anim_num as usize]; + Ok(()) + } + + pub(crate) fn tick_n206_counter_bomb( + &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.target_x = self.x; + self.target_y = self.y; + self.action_counter3 = 120; + self.action_counter = self.rng.range(0..50) as u16; + } + self.action_counter += 1; + if self.action_counter >= 50 { + self.action_counter = 0; + self.action_num = 2; + self.vel_y = 0x300; + } + } + 2 => { + let player = self.get_closest_player_ref(&players); + if player.x > self.x - 0xA000 && player.x < self.x + 0xA000 { + self.action_counter = 0; + self.action_num = 3; + } + + if self.shock > 0 { + self.action_counter = 0; + self.action_num = 3; + } + } + 3 => { + let mut npc = NPC::create(207, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x + 0x2000; + npc.y = self.y + 0x800; + + match self.action_counter { + 0 => { + npc.tsc_direction = 0; + let _ = npc_list.spawn(0x100, npc); + } + 60 => { + npc.tsc_direction = 1; + let _ = npc_list.spawn(0x100, npc); + } + 120 => { + npc.tsc_direction = 2; + let _ = npc_list.spawn(0x100, npc); + } + 180 => { + npc.tsc_direction = 3; + let _ = npc_list.spawn(0x100, npc); + } + 240 => { + npc.tsc_direction = 4; + let _ = npc_list.spawn(0x100, npc); + } + 300 => { + self.hit_bounds.right = 0x10000; + self.hit_bounds.left = 0x10000; + self.hit_bounds.top = 0xC800; + self.hit_bounds.bottom = 0xC800; + self.damage = 30; + self.cond.set_explode_die(true); + + state.quake_counter = 20; + state.sound_manager.play_sfx(35); + npc_list.create_death_smoke(self.x, self.y, 0x10000, 100 as usize, state, &self.rng); + } + _ => (), + } + + self.action_counter += 1; + } + _ => (), + } + + if self.action_num > 1 { + if self.target_y < self.y { + self.vel_y -= 0x10; + } + + if self.target_y > self.y { + self.vel_y += 0x10; + } + + self.vel_y = self.vel_y.clamp(-0x100, 0x100); + } + + self.x += self.vel_x; + self.y += self.vel_y; + + self.animate(4, 0, 2); + + self.anim_rect = state.constants.npc.n206_counter_bomb[self.anim_num as usize]; + + Ok(()) + } + pub(crate) fn tick_n207_counter_bomb_countdown(&mut self, state: &mut SharedGameState) -> GameResult { match self.action_num { 0 | 1 => { @@ -700,7 +1217,7 @@ impl NPC { self.cond.set_alive(false); } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n207_counter_bomb_countdown[self.anim_num as usize % 5]; @@ -708,7 +1225,12 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n208_basu_destroyed_egg_corridor(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult { + pub(crate) fn tick_n208_basu_destroyed_egg_corridor( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + npc_list: &NPCList, + ) -> GameResult { let player = self.get_closest_player_mut(players); match self.action_num { @@ -771,13 +1293,12 @@ impl NPC { self.vel_x = 0; self.x = self.target_x; self.damage = 0; - self.direction = Direction::from_int_facing(self.tsc_direction as usize) - .unwrap_or(Direction::Left); + self.direction = Direction::from_int_facing(self.tsc_direction as usize).unwrap_or(Direction::Left); self.anim_rect = Rect::new(0, 0, 0, 0); return Ok(()); } } - _ => {} + _ => (), } if self.action_counter < 150 { @@ -824,7 +1345,10 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n209_basu_projectile_destroyed_egg_corridor(&mut self, state: &mut SharedGameState) -> GameResult { + pub(crate) fn tick_n209_basu_projectile_destroyed_egg_corridor( + &mut self, + state: &mut SharedGameState, + ) -> GameResult { self.x += self.vel_x; self.y += self.vel_y; @@ -848,4 +1372,87 @@ impl NPC { Ok(()) } + + pub(crate) fn tick_n210_beetle_destroyed_egg_corridor( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + ) -> GameResult { + match self.action_num { + 0 => { + let player = self.get_closest_player_mut(players); + + if player.x < self.x + 0x2000 && player.x > self.x - 0x2000 { + self.npc_flags.set_shootable(true); + self.vel_y = -0x200; + self.target_y = self.y; + self.action_num = 1; + self.damage = 2; + + match self.direction { + Direction::Left => { + self.x = player.x + 0x20000; + self.vel_x = -0x2ff; + } + Direction::Right => { + self.x = player.x - 0x20000; + self.vel_x = 0x2ff; + } + _ => (), + } + } else { + self.npc_flags.set_shootable(false); + self.anim_rect.left = 0; + self.anim_rect.right = 0; + self.damage = 0; + self.vel_x = 0; + self.vel_y = 0; + + return Ok(()); + } + } + 1 => { + let player = self.get_closest_player_mut(players); + + if self.x > player.x { + self.direction = Direction::Left; + self.vel_x -= 0x10; + } else { + self.direction = Direction::Right; + self.vel_x += 0x10; + } + + self.vel_y += if self.y < self.target_y { 8 } else { -8 }; + + self.vel_x = clamp(self.vel_x, -0x2ff, 0x2ff); + self.vel_y = clamp(self.vel_y, -0x200, 0x200); + + if self.shock > 0 { + self.x += self.vel_x / 2; + self.y += self.vel_y / 2; + } else { + self.x += self.vel_x; + self.y += self.vel_y; + } + } + _ => (), + } + + self.anim_counter += 1; + if self.anim_counter > 1 { + self.anim_counter = 0; + self.anim_num += 1; + + if self.anim_num > 1 { + self.anim_num = 0; + } + } + + if self.anim_counter == 1 { + self.anim_rect = state.constants.npc.n210_beetle_destroyed_egg_corridor + [self.anim_num as usize + if self.direction == Direction::Right { 2 } else { 0 }]; + } + + Ok(()) + } } diff --git a/src/npc/ai/first_cave.rs b/src/npc/ai/first_cave.rs index 9b2cdd6..a1e29d2 100644 --- a/src/npc/ai/first_cave.rs +++ b/src/npc/ai/first_cave.rs @@ -56,7 +56,7 @@ impl NPC { } } } - _ => {} + _ => (), } if self.shock > 0 { @@ -167,7 +167,7 @@ impl NPC { } } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -215,7 +215,7 @@ impl NPC { self.vel_y = clamp(self.vel_y, -0x300, 0x300); } - _ => {} + _ => (), } self.x += self.vel_x; diff --git a/src/npc/ai/grasstown.rs b/src/npc/ai/grasstown.rs index 73e3630..e0d2a26 100644 --- a/src/npc/ai/grasstown.rs +++ b/src/npc/ai/grasstown.rs @@ -120,7 +120,7 @@ impl NPC { state.quake_counter = 30; } } - _ => {} + _ => (), } if self.action_num != 4 { @@ -198,7 +198,7 @@ impl NPC { self.npc_flags.set_ignore_solidity(true); } } - _ => {} + _ => (), } self.x += self.vel_x; @@ -329,7 +329,7 @@ impl NPC { state.sound_manager.play_sfx(23); } } - _ => {} + _ => (), } if self.action_num != 4 { @@ -429,7 +429,7 @@ impl NPC { self.vel_y = 0x200; } } - _ => {} + _ => (), } self.x += self.vel_x; @@ -549,7 +549,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } if self.shock > 0 { @@ -656,7 +656,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } self.direction = if self.x <= self.target_x { Direction::Right } else { Direction::Left }; @@ -835,7 +835,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } if self.action_num <= 9 @@ -1021,7 +1021,7 @@ impl NPC { npc_list.create_death_smoke(self.x, self.y, 0x2000, 16, state, &self.rng); self.cond.set_alive(false); } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n107_malco_broken[self.anim_num as usize]; @@ -1085,7 +1085,7 @@ impl NPC { let _ = npc_list.spawn(0x100, npc.clone()); } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -1183,7 +1183,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } if self.action_num <= 9 @@ -1333,7 +1333,7 @@ impl NPC { state.sound_manager.play_sfx(23); } } - _ => {} + _ => (), } self.vel_y += if self.action_num <= 50 { 0x40 } else { 0x20 }; @@ -1423,7 +1423,7 @@ impl NPC { self.cond.set_alive(false); } } - _ => {} + _ => (), } if self.action_counter % 4 == 0 && self.action_num >= 20 { diff --git a/src/npc/ai/igor.rs b/src/npc/ai/igor.rs index 1f47df8..01007d1 100644 --- a/src/npc/ai/igor.rs +++ b/src/npc/ai/igor.rs @@ -71,7 +71,7 @@ impl NPC { 7 => { self.action_num = 1; } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -258,7 +258,7 @@ impl NPC { self.vel_x2 = 0; } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -373,7 +373,7 @@ impl NPC { self.anim_rect = state.constants.npc.n089_igor_dead[self.anim_num as usize + dir_offset]; } } - _ => {} + _ => (), } Ok(()) diff --git a/src/npc/ai/intro.rs b/src/npc/ai/intro.rs index 490e911..8adf697 100644 --- a/src/npc/ai/intro.rs +++ b/src/npc/ai/intro.rs @@ -75,7 +75,7 @@ impl NPC { } } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n298_intro_doctor[self.anim_num as usize]; @@ -97,7 +97,7 @@ impl NPC { self.anim_num = 0; self.action_counter = 0; } - _ => {} + _ => (), } } diff --git a/src/npc/ai/last_cave.rs b/src/npc/ai/last_cave.rs index fe411a3..875c2ea 100644 --- a/src/npc/ai/last_cave.rs +++ b/src/npc/ai/last_cave.rs @@ -89,7 +89,7 @@ impl NPC { state.sound_manager.play_sfx(23); } } - _ => {} + _ => (), } self.vel_y += 0x55; diff --git a/src/npc/ai/maze.rs b/src/npc/ai/maze.rs index 55d28ee..f1ea636 100644 --- a/src/npc/ai/maze.rs +++ b/src/npc/ai/maze.rs @@ -136,7 +136,7 @@ impl NPC { state.sound_manager.play_sfx(23); } } - _ => {} + _ => (), } if self.action_num == 4 { @@ -314,7 +314,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -349,7 +349,7 @@ impl NPC { match self.direction { Direction::Left => self.vel_x = 0x100, Direction::Right => self.vel_x = -0x100, - _ => {} + _ => (), }; state.sound_manager.play_sfx(53); } @@ -368,7 +368,7 @@ impl NPC { self.cond.set_explode_die(true); } } - _ => {} + _ => (), } self.vel_y += 0x20; @@ -458,7 +458,7 @@ impl NPC { self.anim_counter = 0; } } - _ => {} + _ => (), } if player.x >= self.x { self.direction = Direction::Right; @@ -626,7 +626,7 @@ impl NPC { self.action_num = 1; } } - _ => {} + _ => (), } self.y += self.vel_y; @@ -692,7 +692,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n166_chaba[self.anim_num as usize]; @@ -786,7 +786,7 @@ impl NPC { self.cond.set_alive(false); } } - _ => {} + _ => (), } state.npc_super_pos = (self.x, -512000); @@ -818,7 +818,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; @@ -850,7 +850,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; @@ -901,7 +901,7 @@ impl NPC { self.action_counter += 1; } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n168_boulder; @@ -1118,7 +1118,7 @@ impl NPC { self.action_counter = 0; } } - _ => {} + _ => (), } self.vel_y += 0x33; @@ -1358,7 +1358,7 @@ impl NPC { self.target_x += if self.direction != Direction::Left { 0x200 } else { -0x200 }; } - _ => {} + _ => (), } self.vel_x = self.vel_x.clamp(-0x400, 0x400); @@ -1419,7 +1419,7 @@ impl NPC { self.action_num = 1; } - _ => {} + _ => (), } self.animate(10, 0, 3); @@ -1454,7 +1454,7 @@ impl NPC { self.y -= 0x3000; self.action_num = 1; } - _ => {} + _ => (), } Ok(()) @@ -1480,7 +1480,7 @@ impl NPC { _ => (), }; } - _ => {} + _ => (), } self.animate(10, 0, 3); diff --git a/src/npc/ai/mimiga_village.rs b/src/npc/ai/mimiga_village.rs index 6bf19cf..a1e5b47 100644 --- a/src/npc/ai/mimiga_village.rs +++ b/src/npc/ai/mimiga_village.rs @@ -91,7 +91,7 @@ impl NPC { self.action_num = 0; } } - _ => {} + _ => (), } if self.shock > 0 && [1, 2, 4].contains(&self.action_num) { @@ -199,7 +199,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } if self.direction == Direction::Left { @@ -243,7 +243,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } @@ -341,7 +341,7 @@ impl NPC { self.anim_num = 6; } - _ => {} + _ => (), } if (self.vel_x < 0 && self.flags.hit_left_wall()) @@ -424,7 +424,7 @@ impl NPC { self.action_num = 0; } } - _ => {} + _ => (), } if self.shock > 0 && [1, 2, 4].contains(&self.action_num) { diff --git a/src/npc/ai/misc.rs b/src/npc/ai/misc.rs index 20817a4..5838b7b 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; @@ -84,7 +84,7 @@ impl NPC { Direction::Up => { self.anim_rect = state.constants.npc.n004_smoke[self.anim_num as usize + 8]; } - _ => {} + _ => (), } Ok(()) @@ -171,7 +171,7 @@ impl NPC { self.action_num = 1; } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -271,7 +271,7 @@ impl NPC { self.action_num = 1; } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -289,7 +289,7 @@ impl NPC { 0 => match self.direction { Direction::Left => self.anim_rect = state.constants.npc.n018_door[0], Direction::Right => self.anim_rect = state.constants.npc.n018_door[1], - _ => {} + _ => (), }, 1 => { let mut npc = NPC::create(4, &state.npc_table); @@ -307,7 +307,7 @@ impl NPC { self.action_num = 0; self.anim_rect = state.constants.npc.n018_door[0] } - _ => {} + _ => (), } Ok(()) @@ -324,7 +324,7 @@ impl NPC { self.anim_num = self.anim_counter / 4; self.anim_rect = state.constants.npc.n020_computer[1 + self.anim_num as usize]; } - _ => {} + _ => (), } Ok(()) @@ -352,7 +352,7 @@ impl NPC { 1 => { self.anim_num = (self.anim_num + 1) & 1; } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n022_teleporter[self.anim_num as usize]; @@ -408,7 +408,7 @@ impl NPC { self.anim_rect = state.constants.npc.n030_hermit_gunsmith[0]; } } - _ => {} + _ => (), } } else { if self.action_num == 0 { @@ -442,7 +442,7 @@ impl NPC { match self.direction { Direction::Left => self.anim_rect = state.constants.npc.n034_bed[0], Direction::Right => self.anim_rect = state.constants.npc.n034_bed[1], - _ => {} + _ => (), } } @@ -472,7 +472,7 @@ impl NPC { self.anim_rect.left = 0; self.anim_rect.right = 0; } - _ => {} + _ => (), } Ok(()) @@ -485,7 +485,7 @@ impl NPC { match self.direction { Direction::Left => self.anim_rect = state.constants.npc.n039_save_sign[0], Direction::Right => self.anim_rect = state.constants.npc.n039_save_sign[1], - _ => {} + _ => (), } } @@ -511,7 +511,7 @@ impl NPC { match self.direction { Direction::Left => self.anim_rect = state.constants.npc.n043_chalkboard[0], Direction::Right => self.anim_rect = state.constants.npc.n043_chalkboard[1], - _ => {} + _ => (), } } @@ -555,7 +555,6 @@ impl NPC { if self.direction == Direction::Left { self.anim_counter = (self.anim_counter + 1) % 4; self.anim_num = self.anim_counter / 2; - self.anim_rect = state.constants.npc.n072_sprinkler[self.anim_num as usize]; let player = self.get_closest_player_mut(players); if self.anim_num % 2 == 0 && (player.x - self.x).abs() < 480 * 0x200 { @@ -578,6 +577,8 @@ impl NPC { } } + self.anim_rect = state.constants.npc.n072_sprinkler[self.anim_num as usize]; + Ok(()) } @@ -637,7 +638,7 @@ impl NPC { match self.direction { Direction::Left => self.anim_rect = state.constants.npc.n078_pot[0], Direction::Right => self.anim_rect = state.constants.npc.n078_pot[1], - _ => {} + _ => (), } } @@ -650,8 +651,7 @@ impl NPC { self.anim_num = 0; let player = self.get_closest_player_mut(players); - if abs(player.x - self.x) < 0x1000 && player.y < self.y + 0x1000 && player.y > self.y - 0x2000 - { + if abs(player.x - self.x) < 0x1000 && player.y < self.y + 0x1000 && player.y > self.y - 0x2000 { state.sound_manager.play_sfx(43); self.action_num = 1; } @@ -662,7 +662,7 @@ impl NPC { self.anim_num = 1; } } - _ => {} + _ => (), } let dir_offset = if self.direction == Direction::Left { 0 } else { 3 }; @@ -730,7 +730,7 @@ impl NPC { } } } - _ => {} + _ => (), } if self.anim_counter == 0 { @@ -789,7 +789,7 @@ impl NPC { } } } - _ => {} + _ => (), } if self.anim_counter == 0 { @@ -845,7 +845,7 @@ impl NPC { } } } - _ => {} + _ => (), } if self.anim_counter == 0 { @@ -900,7 +900,7 @@ impl NPC { } } } - _ => {} + _ => (), } if self.anim_counter == 0 { @@ -1002,7 +1002,7 @@ impl NPC { self.npc_flags.set_solid_hard(true); } } - _ => {} + _ => (), } self.vel_y += 0x20; @@ -1061,14 +1061,14 @@ impl NPC { npc.direction = Direction::Right; let _ = npc_list.spawn(0, npc); } - _ => {} + _ => (), } } match self.direction { Direction::Left => self.anim_rect = state.constants.npc.n125_hidden_item[0], Direction::Right => self.anim_rect = state.constants.npc.n125_hidden_item[1], - _ => {} + _ => (), } Ok(()) @@ -1119,7 +1119,7 @@ impl NPC { return Ok(()); } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n146_lighting[self.anim_num as usize]; @@ -1243,7 +1243,7 @@ impl NPC { self.vel_x += 0x20; } } - _ => {} + _ => (), } self.vel_x = clamp(self.vel_x, -0x200, 0x200); @@ -1387,7 +1387,7 @@ impl NPC { self.vel_y += 0x20; } } - _ => {} + _ => (), } self.vel_y = clamp(self.vel_y, -0x200, 0x200); @@ -1401,6 +1401,134 @@ impl NPC { Ok(()) } + pub(crate) fn tick_n189_homing_flame( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + ) -> 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.vel_x = -0x40; + } + + self.y += self.vel_y; + self.action_counter += 1; + if self.action_counter > 256 { + self.action_num = 10; + } + } + 10 => { + self.vel_x += if player.x >= self.x { 8 } else { -8 }; + self.vel_y += if player.y >= self.y { 8 } else { -8 }; + + 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; + } + _ => (), + } + + if player.x >= self.x { + self.direction = Direction::Right; + } else { + self.direction = Direction::Left; + } + + self.animate(2, 0, 2); + + self.anim_rect = state.constants.npc.n189_homing_flame[self.anim_num as usize]; + Ok(()) + } + + pub(crate) fn tick_n190_broken_robot(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult { + match self.action_num { + 0 => self.anim_num = 0, + 10 => { + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x; + + for _ in 0..8 { + npc.y = self.y + self.rng.range(-8..8) * 0x200; + npc.vel_x = self.rng.range(-8..-2) * 0x200; + npc.vel_y = self.rng.range(-3..3) * 0x200; + + npc_list.spawn(0x100, npc.clone()); + } + + state.sound_manager.play_sfx(72); + self.cond.set_alive(false); + } + 20 => self.animate(10, 0, 1), + _ => (), + } + + self.anim_rect = state.constants.npc.n190_broken_robot[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n191_water_level(&mut self, state: &mut SharedGameState) -> GameResult { + match self.action_num { + 0 | 10 => { + if self.action_num == 0 { + self.action_num = 10; + self.target_y = self.y; + self.vel_y = 0x200; + } + + self.vel_y += if self.y >= self.target_y { -4 } else { 4 }; + self.vel_y = self.vel_y.clamp(-0x100, 0x100); + + self.y += self.vel_y; + } + 20 | 21 => { + if self.action_num == 20 { + self.action_num = 21; + self.action_counter = 0; + } + + self.vel_y += if self.y >= self.target_y { -4 } else { 4 }; + self.vel_y = self.vel_y.clamp(-0x200, 0x200); + + self.y += self.vel_y; + + self.action_counter += 1; + if self.action_counter > 1000 { + self.action_num = 22; + } + } + 22 => { + self.vel_y += if self.y >= 0 { -4 } else { 4 }; + self.vel_y = self.vel_y.clamp(-0x200, 0x200); + + self.y += self.vel_y; + + if self.y <= 0x7FFF || state.npc_super_pos.1 > 0 { + self.action_num = 21; + self.action_counter = 0; + } + } + 30 => { + self.vel_y += if self.y >= 0 { -4 } else { 4 }; + self.vel_y = self.vel_y.clamp(-0x200, 0x100); + + self.y += self.vel_y; + } + _ => (), + } + + state.water_level = self.y; + self.anim_rect = Rect::new(0, 0, 0, 0); + + Ok(()) + } + pub(crate) fn tick_n194_broken_blue_robot(&mut self, state: &mut SharedGameState) -> GameResult { if self.action_num == 0 { self.action_num = 1; @@ -1468,6 +1596,27 @@ impl NPC { Ok(()) } + pub(crate) fn tick_n219_smoke_generator(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult { + if self.direction != Direction::Left { + let mut npc = NPC::create(199, &state.npc_table); + npc.x = self.x + self.rng.range(-160..160) * 0x200; + npc.y = self.y + self.rng.range(-128..128) * 0x200; + npc.direction = Direction::Right; + + let _ = npc_list.spawn(0x100, npc); + } else if self.rng.range(0..40) == 1 { + let mut npc = NPC::create(4, &state.npc_table); + npc.x = self.x + self.rng.range(-20..20) * 0x200; + npc.y = self.y; + + let _ = npc_list.spawn(0x100, npc); + } + + self.anim_rect = Rect::new(0, 0, 0, 0); + + Ok(()) + } + pub(crate) fn tick_n222_prison_bars(&mut self, state: &mut SharedGameState) -> GameResult { if self.action_num == 0 { self.y -= 0x1000; @@ -1594,7 +1743,7 @@ impl NPC { Direction::Up => self.y -= 0x400, Direction::Right => self.x += 0x400, Direction::Bottom => self.y += 0x400, - _ => {} + _ => (), }, 30 => { self.x = player.x; @@ -1625,7 +1774,7 @@ impl NPC { self.y = (player.y + npc.y) / 2; } } - _ => {} + _ => (), } Ok(()) @@ -1696,7 +1845,7 @@ impl NPC { self.anim_num += 4; self.action_num = 1; } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n351_statue_shootable[self.anim_num as usize % 9]; @@ -1801,7 +1950,7 @@ impl NPC { self.y = npc.y - 0x2600; } } - _ => {} + _ => (), } } diff --git a/src/npc/ai/misery.rs b/src/npc/ai/misery.rs index 720e74d..5906791 100644 --- a/src/npc/ai/misery.rs +++ b/src/npc/ai/misery.rs @@ -55,7 +55,7 @@ impl NPC { self.animate(3, 2, 3); } - _ => {} + _ => (), } self.x += self.vel_x; @@ -183,7 +183,7 @@ impl NPC { self.action_num = 14; } } - _ => {} + _ => (), } self.x += self.vel_x; @@ -350,7 +350,7 @@ impl NPC { 50 => { self.anim_num = 8; } - _ => {} + _ => (), } self.x += self.vel_x; diff --git a/src/npc/ai/mod.rs b/src/npc/ai/mod.rs index 47819b8..1ec1799 100644 --- a/src/npc/ai/mod.rs +++ b/src/npc/ai/mod.rs @@ -1,24 +1,25 @@ -pub mod balrog; -pub mod booster; -pub mod chaco; -pub mod characters; -pub mod curly; -pub mod doctor; -pub mod egg_corridor; -pub mod first_cave; -pub mod grasstown; -pub mod igor; -pub mod intro; -pub mod last_cave; -pub mod maze; -pub mod mimiga_village; -pub mod misc; -pub mod misery; -pub mod outer_wall; -pub mod pickups; -pub mod quote; -pub mod sand_zone; -pub mod santa; -pub mod sue; -pub mod toroko; -pub mod weapon_trail; +pub(in super) mod balrog; +pub(in super) mod booster; +pub(in super) mod chaco; +pub(in super) mod characters; +pub(in super) mod curly; +pub(in super) mod doctor; +pub(in super) mod egg_corridor; +pub(in super) mod first_cave; +pub(in super) mod grasstown; +pub(in super) mod igor; +pub(in super) mod intro; +pub(in super) mod last_cave; +pub(in super) mod maze; +pub(in super) mod mimiga_village; +pub(in super) mod misc; +pub(in super) mod misery; +pub(in super) mod outer_wall; +pub(in super) mod pickups; +pub(in super) mod plantation; +pub(in super) mod quote; +pub(in super) mod sand_zone; +pub(in super) mod santa; +pub(in super) mod sue; +pub(in super) mod toroko; +pub(in super) mod weapon_trail; diff --git a/src/npc/ai/outer_wall.rs b/src/npc/ai/outer_wall.rs index 1cfd9af..85282c6 100644 --- a/src/npc/ai/outer_wall.rs +++ b/src/npc/ai/outer_wall.rs @@ -1,12 +1,226 @@ use num_traits::abs; +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; use crate::shared_game_state::SharedGameState; impl NPC { - pub(crate) fn tick_n215_sandcroc_outer_wall(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult { + pub(crate) fn tick_n212_sky_dragon( + &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 -= 0x800; + } + self.animate(30, 0, 1); + } + 10 | 11 => { + if self.action_num == 10 { + self.action_num = 11; + self.anim_num = 2; + self.anim_counter = 0; + self.target_y = self.y - 0x2000; + self.target_x = self.x - 0xc00; + self.vel_y = 0; + self.npc_flags.set_ignore_solidity(true); + } + + self.vel_x += if self.x >= self.target_x { -8 } else { 8 }; + self.vel_y += if self.y >= self.target_y { -8 } else { 8 }; + + self.x += self.vel_x; + self.y += self.vel_y; + + self.animate(5, 2, 3); + } + 20 | 21 => { + if self.action_num == 20 { + self.action_num = 21; + self.npc_flags.set_ignore_solidity(true); + } + self.vel_y += if self.y >= self.target_y { -0x10 } else { 0x10 }; + + self.vel_x += 0x20; + self.vel_x = self.vel_x.clamp(-0x600, 0x600); + + self.x += self.vel_x; + self.y += self.vel_y; + + self.animate(2, 2, 3); + } + 30 => { + self.action_num = 31; + + let mut npc = NPC::create(297, &state.npc_table); + npc.cond.set_alive(true); + let _ = npc_list.spawn(0x100, npc); + } + _ => (), + } + + self.anim_rect = state.constants.npc.n212_sky_dragon[self.anim_num as usize]; + + if players[0].equip.has_mimiga_mask() && self.anim_num > 1 { + self.anim_rect.top += 40; + self.anim_rect.bottom += 40; + } + + Ok(()) + } + + pub(crate) fn tick_n213_night_spirit( + &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.anim_num = 0; + self.target_x = self.x; + self.target_y = self.y; + } + if player.y > self.y - 0x1000 && player.y < self.y + 0x1000 { + self.y += if self.direction != Direction::Left { 0x1E000 } else { -0x1E000 }; + self.action_num = 10; + self.action_counter = 0; + self.anim_num = 1; + self.vel_y = 0; + self.npc_flags.set_shootable(true); + } + } + 10 => { + self.animate(2, 1, 3); + + self.action_counter += 1; + if self.action_counter > 200 { + self.action_num = 20; + self.action_counter = 0; + self.anim_num = 4; + } + } + 20 => { + self.animate(2, 4, 6); + + self.action_counter += 1; + if self.action_counter > 50 { + self.action_num = 30; + self.action_counter = 0; + self.anim_num = 7; + } + } + 30 => { + self.animate(2, 7, 9); + + self.action_counter += 1; + if self.action_counter % 5 == 1 { + let mut npc = NPC::create(214, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.x; + npc.y = self.y; + + npc.vel_y = self.rng.range(-0x200..0x200); + npc.vel_x = self.rng.range(2..12) * 0x80; + let _ = npc_list.spawn(0x100, npc); + + state.sound_manager.play_sfx(21); + } + + if self.action_counter > 50 { + self.action_num = 10; + self.action_counter = 0; + self.anim_num = 1; + } + } + 40 => { + self.vel_y += if self.y >= self.target_y { -0x40 } else { 0x40 }; + self.vel_y = self.vel_y.clamp(-0x400, 0x400); + self.y += if self.shock > 0 { self.vel_y / 2 } else { self.vel_y }; + + self.animate(2, 4, 6); + + if player.y < self.target_y + 0x1E000 && player.y > self.target_y - 0x1E000 { + self.action_num = 20; + self.action_counter = 0; + self.anim_num = 4; + } + } + _ => (), + } + + if self.action_num > 9 && self.action_num <= 30 { + self.vel_y += if self.y >= player.y { -0x19 } else { 0x19 }; + + self.vel_y = self.vel_y.clamp(-0x400, 0x400); + + if self.flags.hit_top_wall() { + self.vel_y = 0x200; + } + if self.flags.hit_bottom_wall() { + self.vel_y = -0x200; + } + self.y += if self.shock > 0 { self.vel_y / 2 } else { self.vel_y }; + + if player.y > self.target_y + 0x1E000 || player.y < self.target_y - 0x1E000 { + self.action_num = 40; + } + } + + self.anim_rect = state.constants.npc.n213_night_spirit[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n214_night_spirit_projectile( + &mut self, + state: &mut SharedGameState, + npc_list: &NPCList, + ) -> GameResult { + if self.action_num == 0 { + self.action_num = 1; + self.npc_flags.set_ignore_solidity(true); + } + + if self.action_num == 1 { + self.animate(2, 0, 2); + + self.vel_x -= 0x19; + self.x += self.vel_x; + self.y += self.vel_y; + + if self.vel_x < 0 { + self.npc_flags.set_ignore_solidity(false); + } + + if self.flags.hit_anything() { + npc_list.create_death_smoke(self.x, self.y, self.display_bounds.right as usize, 4, state, &self.rng); + state.sound_manager.play_sfx(28); + self.cond.set_alive(false); + } + } + + self.anim_rect = state.constants.npc.n214_night_spirit_projectile[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n215_sandcroc_outer_wall( + &mut self, + state: &mut SharedGameState, + players: [&mut Player; 2], + ) -> GameResult { match self.action_num { 0 | 1 => { if self.action_num == 0 { @@ -47,7 +261,7 @@ impl NPC { self.action_counter = 0; self.npc_flags.set_shootable(true); } - _ => {} + _ => (), } } 30 => { @@ -80,7 +294,7 @@ impl NPC { self.action_counter += 1; } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n215_sandcroc_outer_wall[self.anim_num as usize]; diff --git a/src/npc/ai/pickups.rs b/src/npc/ai/pickups.rs index 5f0c97f..5362e9e 100644 --- a/src/npc/ai/pickups.rs +++ b/src/npc/ai/pickups.rs @@ -187,7 +187,7 @@ impl NPC { 3 => { self.anim_rect = state.constants.npc.n086_missile_pickup[2 + self.anim_num as usize]; } - _ => {} + _ => (), } if self.action_counter2 > 550 { @@ -255,7 +255,7 @@ impl NPC { 6 => { self.anim_rect = state.constants.npc.n087_heart_pickup[2 + self.anim_num as usize]; } - _ => {} + _ => (), } if self.action_counter2 > 550 { diff --git a/src/npc/ai/plantation.rs b/src/npc/ai/plantation.rs new file mode 100644 index 0000000..b179bc7 --- /dev/null +++ b/src/npc/ai/plantation.rs @@ -0,0 +1,289 @@ +use crate::common::Direction; +use crate::framework::error::GameResult; +use crate::npc::NPC; +use crate::player::Player; +use crate::rng::RNG; +use crate::shared_game_state::SharedGameState; + +impl NPC { + pub(crate) fn tick_n220_shovel_brigade(&mut self, state: &mut SharedGameState) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.anim_num = 0; + self.anim_counter = 0; + } + + 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; + } + } + + _ => (), + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; + + self.anim_rect = state.constants.npc.n220_shovel_brigade[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n221_shovel_brigade_walking(&mut self, state: &mut SharedGameState) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.anim_num = 0; + self.anim_counter = 0; + self.vel_x = 0; + } + + if self.rng.range(0..60) == 1 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 1; + } + if self.rng.range(0..60) == 1 { + self.action_num = 10; + 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; + } + } + 10 | 11 => { + if self.action_num == 10 { + self.action_num = 11; + self.action_counter = self.rng.range(0..16) as u16; + self.anim_num = 2; + self.anim_counter = 0; + + if (self.rng.range(0..9) & 1) != 0 { + self.direction = Direction::Left; + } else { + self.direction = Direction::Right; + } + } + + if self.direction != Direction::Left || !self.flags.hit_left_wall() { + if self.direction == Direction::Right && self.flags.hit_right_wall() { + self.direction = Direction::Left; + } + } else { + self.direction = Direction::Right; + } + + if self.direction != Direction::Left { + self.vel_x = 0x200; + } else { + self.vel_x = -0x200; + } + + self.animate(4, 2, 5); + + self.action_counter += 1; + if self.action_counter > 32 { + self.action_num = 0; + } + } + _ => (), + } + self.vel_y += 0x20; + 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 { 6 }; + + self.anim_rect = state.constants.npc.n221_shovel_brigade_walking[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n223_momorin(&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.anim_num = 0; + self.anim_counter = 0; + } + + if self.rng.range(0..160) == 1 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 1; + } + } + 2 => { + self.action_counter += 1; + if self.action_counter > 12 { + self.action_num = 1; + self.anim_num = 0; + } + } + 3 => { + self.anim_num = 2; + } + _ => (), + } + + let player = self.get_closest_player_ref(&players); + + if self.action_num <= 1 && player.y < self.y + 0x2000 && player.y > self.y - 0x2000 { + if player.x >= self.x { + self.direction = Direction::Right; + } else { + self.direction = Direction::Left; + } + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 3 }; + + self.anim_rect = state.constants.npc.n223_momorin[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n224_chie(&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.anim_num = 0; + self.anim_counter = 0; + } + + if self.rng.range(0..160) == 1 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 1; + } + } + 2 => { + self.action_counter += 1; + if self.action_counter > 12 { + self.action_num = 1; + self.anim_num = 0; + } + } + _ => (), + } + let player = self.get_closest_player_ref(&players); + + if self.action_num <= 1 && player.y < self.y + 0x2000 && player.y > self.y - 0x2000 { + if player.x >= self.x { + self.direction = Direction::Right; + } else { + self.direction = Direction::Left; + } + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; + + self.anim_rect = state.constants.npc.n224_chie[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n225_megane(&mut self, state: &mut SharedGameState) -> GameResult { + if self.action_num == 0 { + self.action_num = 1; + self.anim_num = 0; + self.anim_counter = 0; + } + + if self.action_num == 1 { + if self.rng.range(0..160) == 1 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 1; + } + } else if self.action_num == 2 { + self.action_counter += 1; + + if self.action_counter > 12 { + self.action_num = 1; + self.anim_num = 0; + } + } + + let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; + + self.anim_rect = state.constants.npc.n225_megane[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n226_kanpachi_plantation(&mut self, state: &mut SharedGameState) -> GameResult { + match self.action_num { + 0 | 1 => { + if self.action_num == 0 { + self.action_num = 1; + self.anim_num = 0; + self.anim_counter = 0; + self.vel_x = 0; + } + if self.rng.range(0..60) == 1 { + 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; + } + } + 10 | 11 => { + if self.action_num == 10 { + self.action_num = 11; + self.anim_num = 2; + self.anim_counter = 0; + } + + self.vel_x = 0x200; + + self.animate(4, 2, 5); + self.action_counter += 1; + } + 20 => { + self.vel_x = 0; + self.anim_num = 6; + } + _ => (), + } + + self.vel_y += 0x20; + + if self.vel_y > 0x5FF { + self.vel_y = 0x5FF; + } + + self.x += self.vel_x; + self.y += self.vel_y; + + self.anim_rect = state.constants.npc.n226_kanpachi_plantation[self.anim_num as usize]; + Ok(()) + } +} diff --git a/src/npc/ai/quote.rs b/src/npc/ai/quote.rs index 62f89d2..7bdd871 100644 --- a/src/npc/ai/quote.rs +++ b/src/npc/ai/quote.rs @@ -52,7 +52,7 @@ impl NPC { self.cond.set_alive(false); } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -109,7 +109,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -270,7 +270,7 @@ impl NPC { } } } - _ => {} + _ => (), } let dir_offset = if self.direction == Direction::Left { 0 } else { 9 }; diff --git a/src/npc/ai/sand_zone.rs b/src/npc/ai/sand_zone.rs index 908ec9b..428ee28 100644 --- a/src/npc/ai/sand_zone.rs +++ b/src/npc/ai/sand_zone.rs @@ -109,7 +109,7 @@ impl NPC { self.action_num = 6; } } - _ => {} + _ => (), } if self.life <= 100 { @@ -175,7 +175,7 @@ impl NPC { self.anim_num = 1; } } - _ => {} + _ => (), } if self.vel_x2 < 0 && self.flags.hit_left_wall() { @@ -247,7 +247,7 @@ impl NPC { self.action_counter = 0; self.npc_flags.set_shootable(true); } - _ => {} + _ => (), } } 3 => { @@ -280,7 +280,7 @@ impl NPC { self.action_counter += 1; } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n047_sandcroc[self.anim_num as usize]; @@ -382,7 +382,7 @@ impl NPC { self.anim_num = 1; } } - _ => {} + _ => (), } if self.action_num > 9 { @@ -458,7 +458,7 @@ impl NPC { } } } - _ => {} + _ => (), } self.vel_y = clamp(self.vel_y, -0x5ff, 0x5ff); @@ -527,7 +527,7 @@ impl NPC { self.vel_y += 0x20; } } - _ => {} + _ => (), } if self.vel_x < 0 && self.flags.hit_left_wall() { @@ -678,7 +678,7 @@ impl NPC { ); } } - _ => {} + _ => (), } self.vel_y += 0x80; @@ -756,7 +756,7 @@ impl NPC { self.anim_counter = 0; } } - _ => {} + _ => (), } let dir_offset = if self.direction == Direction::Left { 0 } else { 3 }; @@ -836,7 +836,7 @@ impl NPC { self.vel_x = clamp(self.vel_x, -0x5ff, 0x5ff); self.vel_y = clamp(self.vel_y, -0x5ff, 0x5ff); } - _ => {} + _ => (), } self.x += self.vel_x; @@ -897,7 +897,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n121_colon_b[self.anim_num as usize]; @@ -1020,7 +1020,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } if self.action_num > 10 && self.action_num < 20 && self.life != 1000 { self.action_num = 20; @@ -1079,7 +1079,7 @@ impl NPC { state.sound_manager.play_sfx(26); } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n124_sunstone[self.anim_num as usize]; @@ -1159,7 +1159,7 @@ impl NPC { self.vel_x = -0x400; } } - _ => {} + _ => (), } // why @@ -1234,7 +1234,7 @@ impl NPC { } } } - _ => {} + _ => (), } self.action_counter += 1; @@ -1350,7 +1350,7 @@ impl NPC { state.sound_manager.play_sfx(105); } } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -1390,7 +1390,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } let dir_offset = if self.direction == Direction::Left { 0 } else { 2 }; @@ -1428,7 +1428,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } // todo dog stacking? @@ -1511,7 +1511,7 @@ impl NPC { self.npc_flags.set_invulnerable(false); } } - _ => {} + _ => (), } self.vel_y += 0x40; if self.vel_y > 0x5FF { @@ -1604,7 +1604,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } if self.action_num > 9 { diff --git a/src/npc/ai/santa.rs b/src/npc/ai/santa.rs index f595713..ed42ac3 100644 --- a/src/npc/ai/santa.rs +++ b/src/npc/ai/santa.rs @@ -62,7 +62,7 @@ impl NPC { 5 => { self.anim_num = 6; } - _ => {} + _ => (), } let dir_offset = if self.direction == Direction::Left { 0 } else { 7 }; diff --git a/src/npc/ai/sue.rs b/src/npc/ai/sue.rs index f2b97fa..cd64023 100644 --- a/src/npc/ai/sue.rs +++ b/src/npc/ai/sue.rs @@ -205,7 +205,7 @@ impl NPC { self.anim_num = 9; self.vel_y = -0x400; } - _ => {} + _ => (), } if self.action_num != 14 { @@ -275,7 +275,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } self.anim_rect = state.constants.npc.n092_sue_at_pc[self.anim_num as usize]; diff --git a/src/npc/ai/toroko.rs b/src/npc/ai/toroko.rs index 479bd8f..466c84e 100644 --- a/src/npc/ai/toroko.rs +++ b/src/npc/ai/toroko.rs @@ -148,7 +148,7 @@ impl NPC { 12 => { self.vel_x = 0; } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -252,7 +252,7 @@ impl NPC { self.vel_x = 0; self.anim_num = 5; } - _ => {} + _ => (), } self.vel_y += 0x40; @@ -560,7 +560,7 @@ impl NPC { self.cond.set_alive(false); } } - _ => {} + _ => (), } if self.action_num > 100 && self.action_num <= 104 && (self.action_counter % 9) == 0 { @@ -744,7 +744,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } self.vel_y += 64; self.vel_y = self.vel_y.clamp(-0x5ff, 0x5ff); @@ -807,7 +807,7 @@ impl NPC { self.anim_num = 0; } } - _ => {} + _ => (), } if self.action_num > 1 { diff --git a/src/npc/ai/weapon_trail.rs b/src/npc/ai/weapon_trail.rs index 5d1d8a2..46e2aa3 100644 --- a/src/npc/ai/weapon_trail.rs +++ b/src/npc/ai/weapon_trail.rs @@ -24,7 +24,7 @@ impl NPC { Direction::Up | Direction::Bottom => { self.anim_rect = state.constants.npc.n127_machine_gun_trail_l2[self.anim_num as usize + 3]; } - _ => {} + _ => (), } Ok(()) @@ -54,7 +54,7 @@ impl NPC { self.display_bounds.left = 0x1000; self.display_bounds.top = 0x800; } - _ => {} + _ => (), } } @@ -71,7 +71,7 @@ impl NPC { Direction::Bottom => { self.anim_rect = state.constants.npc.n128_machine_gun_trail_l3[self.anim_num as usize + 15]; } - _ => {} + _ => (), } Ok(()) diff --git a/src/npc/boss/core.rs b/src/npc/boss/core.rs index ccdd0c3..5515807 100644 --- a/src/npc/boss/core.rs +++ b/src/npc/boss/core.rs @@ -1,12 +1,96 @@ +use crate::caret::CaretType; +use crate::common::{Direction, CDEG_RAD}; +use crate::framework::error::GameResult; use crate::npc::boss::BossNPC; use crate::npc::list::NPCList; use crate::npc::NPC; use crate::player::Player; use crate::rng::RNG; use crate::shared_game_state::SharedGameState; +use crate::stage::Stage; + +impl NPC { + pub(crate) fn tick_n178_core_blade_projectile(&mut self, state: &mut SharedGameState) -> GameResult { + if self.flags.hit_anything() { + state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left); + self.cond.set_alive(false); + } + + if self.flags.in_water() { + self.x += self.vel_x / 2; + self.y += self.vel_y / 2; + } else { + self.x += self.vel_x; + self.y += self.vel_y; + } + + self.animate(1, 0, 2); + + self.action_counter3 += 1; + if self.action_counter3 > 150 { + self.vanish(state); + state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left); + } + + self.anim_rect = state.constants.npc.n178_core_blade_projectile[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n179_core_wisp_projectile(&mut self, state: &mut SharedGameState) -> GameResult { + if self.flags.hit_anything() { + state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left); + self.cond.set_alive(false); + } + + self.vel_x -= 0x20; + self.vel_y = 0; + + if self.vel_x < -0x400 { + self.vel_x = -0x400; + } + + self.x += self.vel_x; + self.y += self.vel_y; + + self.animate(1, 0, 2); + + self.action_counter3 += 1; + if self.action_counter3 > 300 { + self.vanish(state); + state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left); + } + + self.anim_rect = state.constants.npc.n179_core_wisp_projectile[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n218_core_giant_ball(&mut self, state: &mut SharedGameState) -> GameResult + { + self.x += self.vel_x; + self.y += self.vel_y; + + self.action_counter += 1; + if self.action_counter > 200 { + self.cond.set_alive(false); + } + + self.animate(2, 0, 1); + self.anim_rect = state.constants.npc.n218_core_giant_ball[self.anim_num as usize]; + + Ok(()) + } +} impl BossNPC { - pub(crate) fn tick_b04_core(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) { + pub(crate) fn tick_b04_core( + &mut self, + state: &mut SharedGameState, + mut players: [&mut Player; 2], + npc_list: &NPCList, + stage: &mut Stage, + ) { let mut flag = false; // i will refactor that one day #[allow(mutable_transmutes)] @@ -83,37 +167,44 @@ impl BossNPC { self.parts[2] = self.parts[1].clone(); self.parts[2].x = self.parts[0].x + 0x2000; - self.parts[2].x = self.parts[0].y; + self.parts[2].y = self.parts[0].y; self.parts[3] = self.parts[1].clone(); self.parts[3].x = self.parts[0].x - 0x1000; - self.parts[3].x = self.parts[0].y + 0x8000; + self.parts[3].y = self.parts[0].y + 0x8000; self.parts[6] = self.parts[1].clone(); self.parts[6].x = self.parts[0].x - 0x6000; - self.parts[6].x = self.parts[0].y - 0x4000; + self.parts[6].y = self.parts[0].y - 0x4000; self.parts[7] = self.parts[1].clone(); self.parts[7].x = self.parts[0].x - 0x6000; - self.parts[7].x = self.parts[0].y + 0x4000; - } - 200 => { - self.parts[0].action_num = 201; - self.parts[0].action_counter = 0; - self.parts[11].npc_flags.set_shootable(false); - state.npc_super_pos.1 = 0; + self.parts[7].y = self.parts[0].y + 0x4000; - state.sound_manager.stop_sfx(40); - state.sound_manager.stop_sfx(41); - state.sound_manager.stop_sfx(58); + for part in self.parts.iter_mut() { + part.prev_x = part.x; + part.prev_y = part.y; + } } - 201 => { - self.parts[0].target_x = self.parts[0].x; - self.parts[0].target_y = self.parts[0].y; + 200 | 201 => { + if self.parts[0].action_num == 200 { + self.parts[0].action_num = 201; + self.parts[0].action_counter = 0; + self.parts[11].npc_flags.set_shootable(false); + state.npc_super_pos.1 = 0; + + state.sound_manager.stop_sfx(40); + state.sound_manager.stop_sfx(41); + state.sound_manager.stop_sfx(58); + } + + let idx = self.parts[0].get_closest_player_idx_mut(&players); + self.parts[0].target_x = players[idx].x; + self.parts[0].target_y = players[idx].y; self.parts[0].action_counter += 1; if self.parts[0].action_counter > 400 { - self.parts[0].action_counter2 += 2; + self.parts[0].action_counter2 += 1; state.sound_manager.play_sfx(115); @@ -123,24 +214,24 @@ impl BossNPC { self.parts[0].action_counter2 = 0; self.parts[0].action_num = 220; } + + self.parts[4].anim_num = 0; + self.parts[5].anim_num = 0; + + flag = true; } - - self.parts[4].anim_num = 0; - self.parts[5].anim_num = 0; - - flag = true; } 210 | 211 => { if self.parts[0].action_num == 210 { self.parts[0].action_num = 211; self.parts[0].action_counter = 0; - self.parts[0].action_counter2 = self.parts[0].life; + self.parts[0].action_counter3 = self.parts[0].life; self.parts[11].npc_flags.set_shootable(true); } - let player = self.parts[0].get_closest_player_mut(players); - self.parts[0].target_x = player.x; - self.parts[0].target_y = player.y; + let idx = self.parts[0].get_closest_player_idx_mut(&players); + self.parts[0].target_x = players[idx].x; + self.parts[0].target_y = players[idx].y; if self.parts[0].shock > 0 { *flash_counter += 1; @@ -171,7 +262,7 @@ impl BossNPC { } if self.parts[0].action_counter > 400 - || (self.parts[0].life as i32) < self.parts[0].action_counter2 as i32 - 200 + || (self.parts[0].life as i32) < self.parts[0].action_counter3 as i32 - 200 { self.parts[0].action_num = 200; self.parts[4].anim_num = 2; @@ -200,7 +291,7 @@ impl BossNPC { npc.y = players[idx].y + self.parts[0].rng.range(-160..160) * 0x200; let _ = npc_list.spawn(0x100, npc); - for player in players { + for player in players.iter_mut() { player.vel_x -= 0x20; player.cond.set_increase_acceleration(true); } @@ -238,6 +329,53 @@ impl BossNPC { flag = true; } } + 500 | 501 => { + if self.parts[0].action_num == 500 { + self.parts[0].action_num = 501; + self.parts[0].action_counter = 0; + self.parts[0].vel_x = 0; + self.parts[0].vel_y = 0; + self.parts[4].anim_num = 2; + self.parts[5].anim_num = 0; + self.parts[1].action_num = 200; + self.parts[2].action_num = 200; + self.parts[3].action_num = 200; + self.parts[6].action_num = 200; + self.parts[7].action_num = 200; + + state.quake_counter = 20; + + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + for _ in 0..32 { + npc.x = self.parts[0].x + self.parts[0].rng.range(-0x80..0x80) * 0x200; + npc.y = self.parts[0].y + self.parts[0].rng.range(-0x40..0x40) * 0x200; + + npc.vel_x = self.parts[0].rng.range(-0x80..0x80) * 0x200; + npc.vel_y = self.parts[0].rng.range(-0x80..0x80) * 0x200; + + let _ = npc_list.spawn(0x100, npc.clone()); + } + + for i in 0..12 { + self.parts[i].npc_flags.set_invulnerable(false); + self.parts[i].npc_flags.set_shootable(false); + } + } + + self.parts[0].action_counter += 1; + if (self.parts[0].action_counter & 0x0f) != 0 { + let mut npc = NPC::create(4, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.parts[0].x + self.parts[0].rng.range(-0x40..0x40) * 0x200; + npc.y = self.parts[0].y + self.parts[0].rng.range(-0x20..0x20) * 0x200; + + npc.vel_x = self.parts[0].rng.range(-0x80..0x80) * 0x200; + npc.vel_y = self.parts[0].rng.range(-0x80..0x80) * 0x200; + + let _ = npc_list.spawn(0x100, npc); + } + } _ => {} } @@ -258,21 +396,21 @@ impl BossNPC { npc.x = self.parts[4].x + self.parts[0].rng.range(-32..16) * 0x200; npc.vel_x = self.parts[0].rng.range(-0x200..0x200); npc.vel_y = self.parts[0].rng.range(-0x100..0x100); - npc_list.spawn(0x100, npc.clone()); + let _ = npc_list.spawn(0x100, npc.clone()); } } if self.parts[0].action_num >= 200 && self.parts[0].action_num < 300 { - if self.parts[0].action_counter == 140 { + if self.parts[0].action_counter == 80 { + self.parts[1].action_num = 120; + } else if self.parts[0].action_counter == 110 { + self.parts[2].action_num = 120; + } else if self.parts[0].action_counter == 140 { self.parts[3].action_num = 120; } else if self.parts[0].action_counter == 170 { self.parts[6].action_num = 120; } else if self.parts[0].action_counter == 200 { self.parts[7].action_num = 120; - } else if self.parts[0].action_counter == 80 { - self.parts[1].action_num = 120; - } else if self.parts[0].action_counter == 110 { - self.parts[2].action_num = 120; } if self.parts[0].x < self.parts[0].target_x + 0x14000 { @@ -292,9 +430,251 @@ impl BossNPC { } } - self.parts[0].vel_x = self.parts[0].vel_x.clamp(-0x100, 0x100); - self.parts[0].vel_y = self.parts[0].vel_y.clamp(-0x100, 0x100); + self.parts[0].vel_x = self.parts[0].vel_x.clamp(-0x80, 0x80); + self.parts[0].vel_y = self.parts[0].vel_y.clamp(-0x80, 0x80); self.parts[0].x += self.parts[0].vel_x; self.parts[0].y += self.parts[0].vel_y; + + self.tick_b04_core_face(4, state); + self.tick_b04_core_tail(5, state); + self.tick_b04_core_small_head(1, state, &players, npc_list, stage); + self.tick_b04_core_small_head(2, state, &players, npc_list, stage); + self.tick_b04_core_small_head(3, state, &players, npc_list, stage); + self.tick_b04_core_small_head(6, state, &players, npc_list, stage); + self.tick_b04_core_small_head(7, state, &players, npc_list, stage); + self.tick_b04_core_hitbox(8, state); + self.tick_b04_core_hitbox(9, state); + self.tick_b04_core_hitbox(10, state); + self.tick_b04_core_hitbox(11, state); + } + + fn tick_b04_core_face(&mut self, i: usize, state: &mut SharedGameState) { + let (head, tail) = self.parts.split_at_mut(i); + let base = &mut head[0]; + let part = &mut tail[0]; + + match part.action_num { + 10 | 11 => { + if part.action_num == 10 { + part.action_num = 11; + part.anim_num = 2; + part.npc_flags.set_ignore_solidity(true); + part.display_bounds.left = 0x4800; + part.display_bounds.top = 0x7000; + } + part.x = base.x - 0x4800; + part.y = base.y; + } + 50 | 51 => { + if part.action_num == 50 { + part.action_num = 51; + part.action_counter = 112; + } + part.action_counter -= 1; + + if part.action_counter == 0 { + part.action_num = 100; + part.anim_num = 3; + } + part.x = base.x - 0x4800; + part.y = base.y; + } + 100 => { + part.anim_num = 3; + } + _ => {} + } + + if part.action_num == 51 { + part.anim_rect.bottom = part.action_counter + part.anim_rect.top; + } + + part.anim_rect = state.constants.npc.b04_core[part.anim_num as usize]; + } + + fn tick_b04_core_tail(&mut self, i: usize, state: &mut SharedGameState) { + let (head, tail) = self.parts.split_at_mut(i); + let base = &mut head[0]; + let part = &mut tail[0]; + + match part.action_num { + 10 | 11 => { + if part.action_num == 10 { + part.action_num = 11; + part.anim_num = 0; + part.npc_flags.set_ignore_solidity(true); + part.display_bounds.left = 0x5800; + part.display_bounds.top = 0x7000; + } + part.x = base.x + 0x5800; + part.y = base.y; + } + 50 | 51 => { + if part.action_num == 50 { + part.action_num = 51; + part.action_counter = 112; + } + part.action_counter -= 1; + + if part.action_counter == 0 { + part.action_num = 100; + part.anim_num = 2; + } + part.x = base.x + 0x5800; + part.y = base.y; + } + 100 => { + part.anim_num = 2; + } + _ => {} + } + + if part.action_num == 51 { + part.anim_rect.bottom = part.action_counter + part.anim_rect.top; + } + + part.anim_rect = state.constants.npc.b04_core[4 + part.anim_num as usize]; + } + + fn tick_b04_core_small_head( + &mut self, + i: usize, + state: &mut SharedGameState, + players: &[&mut Player; 2], + npc_list: &NPCList, + stage: &Stage, + ) { + let (head, tail) = self.parts.split_at_mut(i); + let base = &mut head[0]; + let part = &mut tail[0]; + + part.life = 1000; + match part.action_num { + 10 => { + part.anim_num = 2; + part.npc_flags.set_shootable(false); + } + 100 | 101 => { + if part.action_num == 100 { + part.action_num = 101; + part.anim_num = 2; + part.action_counter = 0; + part.target_x = base.x + (base.rng.range(-128..32) * 0x200); + part.target_y = base.y + (base.rng.range(-64..64) * 0x200); + part.npc_flags.set_shootable(true); + } + part.x += (part.target_x - part.x) / 16; + part.y += (part.target_y - part.y) / 16; + + part.action_counter += 1; + if part.action_counter > 50 { + part.anim_num = 0; + } + } + 120 | 121 => { + if part.action_num == 120 { + part.action_num = 121; + part.action_counter = 0; + } + + part.action_counter += 1; + if ((part.action_counter / 2) & 1) != 0 { + part.anim_num = 0; + } else { + part.anim_num = 1; + } + + if part.action_counter > 20 { + part.action_num = 130; + } + } + 130 | 131 => { + if part.action_num == 130 { + part.action_num = 131; + part.anim_num = 2; + part.action_counter = 0; + part.target_x = part.x + (part.rng.range(24..48) * 0x200); + part.target_y = part.y + (part.rng.range(-4..4) * 0x200); + } + + part.x += (part.target_x - part.x) / 16; + part.y += (part.target_y - part.y) / 16; + + part.action_counter += 1; + if part.action_counter > 50 { + part.action_num = 140; + part.anim_num = 0; + } + + if part.action_counter == 1 || part.action_counter == 3 { + let player_idx = part.get_closest_player_idx_mut(players); + let px = part.x - players[player_idx].x; + let py = part.y - players[player_idx].y; + + let deg = f64::atan2(py as f64, px as f64) + part.rng.range(-2..2) as f64 * CDEG_RAD; + + let mut npc = NPC::create(178, &state.npc_table); + npc.cond.set_alive(true); + npc.x = part.x; + npc.y = part.y; + npc.vel_x = (deg.cos() * -1024.0) as i32; + npc.vel_y = (deg.sin() * -1024.0) as i32; + + let _ = npc_list.spawn(0x100, npc); + + state.sound_manager.play_sfx(39); + } + } + 140 => { + part.x += (part.target_x - part.x) / 16; + part.y += (part.target_y - part.y) / 16; + } + 200 | 201 => { + if part.action_num == 200 { + part.action_num = 201; + part.anim_num = 2; + part.vel_x = 0; + part.vel_y = 0; + } + part.vel_x += 32; + part.x += part.vel_x; + + if part.x > (stage.map.width as i32 * 0x2000) + 0x4000 { + part.cond.set_alive(false); + } + } + _ => (), + } + if part.shock > 0 { + part.target_x += 1024; + } + + part.anim_rect = state.constants.npc.b04_core[7 + part.anim_num as usize]; + } + + fn tick_b04_core_hitbox(&mut self, i: usize, state: &mut SharedGameState) { + let (head, tail) = self.parts.split_at_mut(i); + let base = &mut head[0]; + let part = &mut tail[0]; + + match part.action_counter2 { + 0 => { + part.x = base.x; + part.y = base.y - 0x4000; + } + 1 => { + part.x = base.x + 0x3800; + part.y = base.y; + } + 2 => { + part.x = base.x + 0x800; + part.y = base.y + 0x4000; + } + 3 => { + part.x = base.x - 0x3800; + part.y = base.y + 0x800; + } + _ => (), + } } } diff --git a/src/npc/boss/ironhead.rs b/src/npc/boss/ironhead.rs index e739c7e..d69dcbf 100644 --- a/src/npc/boss/ironhead.rs +++ b/src/npc/boss/ironhead.rs @@ -1,7 +1,100 @@ +use crate::common::Direction; +use crate::framework::error::GameResult; use crate::npc::boss::BossNPC; +use crate::npc::NPC; +use crate::shared_game_state::SharedGameState; +use crate::rng::RNG; -impl BossNPC { - pub(crate) fn tick_b05_ironhead(&mut self) { +impl NPC { + pub(crate) fn tick_n196_ironhead_wall(&mut self, state: &mut SharedGameState) -> GameResult { + self.x -= 0xC00; + if self.x <= 0x26000 { + self.x += 0x2C000; + } + let dir_offset = if self.direction == Direction::Left { 0 } else { 1 }; + + self.anim_rect = state.constants.npc.n196_ironhead_wall[dir_offset]; + + Ok(()) + } + + pub(crate) fn tick_n197_porcupine_fish(&mut self, state: &mut SharedGameState) -> GameResult { + match self.action_num { + 0 | 10 => { + if self.action_num == 0 { + self.action_num = 10; + self.anim_counter = 0; + self.vel_y = self.rng.range(-0x200..0x200); + self.vel_x = 0x800; + } + + self.animate(2, 0, 1); + + if self.vel_x < 0 { + self.damage = 3; + self.action_num = 20; + } + } + 20 => { + self.damage = 3; + self.animate(0, 2, 3); + + if self.x <= 0x5FFF { + // npc->destroy_voice = 0; // todo + self.cond.set_explode_die(true); + } + } + _ => (), + } + + if self.flags.hit_top_wall() { + self.vel_y = 0x200; + } + if self.flags.hit_bottom_wall() { + self.vel_y = -0x200; + } + + self.vel_x -= 0xC; + self.x += self.vel_x; + self.y += self.vel_y; + self.anim_rect = state.constants.npc.n197_porcupine_fish[self.anim_num as usize]; + + Ok(()) + } + + pub(crate) fn tick_n198_ironhead_projectile(&mut self, state: &mut SharedGameState) -> GameResult { + if self.action_num == 0 { + self.action_counter += 1; + if self.action_counter > 20 { + self.action_num = 1; + self.vel_x = 0; + self.vel_y = 0; + self.action_counter3 = 0; + } + } else if self.action_num == 1 { + self.vel_x += 0x20; + } + + self.animate(0, 0, 2); + self.x += self.vel_x; + self.y += self.vel_y; + + self.anim_rect = state.constants.npc.n198_ironhead_projectile[self.anim_num as usize]; + + self.action_counter3 += 1; + if self.action_counter3 > 100 { + self.cond.set_alive(false); + } + + if self.action_counter3 % 4 == 1 { + state.sound_manager.play_sfx(46); + } + + Ok(()) } } + +impl BossNPC { + pub(crate) fn tick_b05_ironhead(&mut self) {} +} diff --git a/src/npc/boss/mod.rs b/src/npc/boss/mod.rs index 0cd7922..3d629f6 100644 --- a/src/npc/boss/mod.rs +++ b/src/npc/boss/mod.rs @@ -65,7 +65,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl fn tick( &mut self, state: &mut SharedGameState, - (players, npc_list, _stage, bullet_manager, flash): ( + (players, npc_list, stage, bullet_manager, flash): ( [&mut Player; 2], &NPCList, &mut Stage, @@ -81,7 +81,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl 1 => self.tick_b01_omega(state, players, npc_list, bullet_manager, flash), 2 => self.tick_b02_balfrog(state, players, npc_list), 3 => self.tick_b03_monster_x(state, players, npc_list, flash), - 4 => self.tick_b04_core(state, players, npc_list), + 4 => self.tick_b04_core(state, players, npc_list, stage), 5 => self.tick_b05_ironhead(), 6 => self.tick_b06_twins(), 7 => self.tick_b07_undead_core(), diff --git a/src/npc/mod.rs b/src/npc/mod.rs index 2d9f0a8..089b3c4 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -1,13 +1,13 @@ use std::io; use std::io::Cursor; -use byteorder::{LE, ReadBytesExt}; +use byteorder::{ReadBytesExt, LE}; use num_traits::abs; use crate::bitfield; -use crate::common::{Condition, interpolate_fix9_scale, Rect}; use crate::common::Direction; use crate::common::Flag; +use crate::common::{interpolate_fix9_scale, Condition, Rect}; use crate::components::flash::Flash; use crate::components::number_popup::NumberPopup; use crate::entity::GameEntity; @@ -185,7 +185,7 @@ impl NPC { } } -impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Flash)> for NPC { +impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mut Flash)> for NPC { fn tick( &mut self, state: &mut SharedGameState, @@ -193,7 +193,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl [&mut Player; 2], &NPCList, &mut Stage, - &BulletManager, + &mut BulletManager, &mut Flash, ), ) -> GameResult { @@ -383,23 +383,55 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl 175 => self.tick_n175_gaudi_egg(state), 176 => self.tick_n176_buyo_buyo_base(state, players, npc_list), 177 => self.tick_n177_buyo_buyo(state, players), + 178 => self.tick_n178_core_blade_projectile(state), + 179 => self.tick_n179_core_wisp_projectile(state), + 180 => self.tick_n180_curly_ai(state, players, npc_list), + 181 => self.tick_n181_curly_ai_machine_gun(state, npc_list, bullet_manager), + 182 => self.tick_n182_curly_ai_polar_star(state, npc_list, bullet_manager), + 183 => self.tick_n183_curly_air_tank_bubble(state, npc_list), 184 => self.tick_n184_shutter(state, npc_list), 185 => self.tick_n185_small_shutter(state), 186 => self.tick_n186_lift_block(state), 187 => self.tick_n187_fuzz_core(state, players, npc_list), 188 => self.tick_n188_fuzz(state, players, npc_list), + 189 => self.tick_n189_homing_flame(state, players), + 190 => self.tick_n190_broken_robot(state, npc_list), + 191 => self.tick_n191_water_level(state), 192 => self.tick_n192_scooter(state), 193 => self.tick_n193_broken_scooter(state), 194 => self.tick_n194_broken_blue_robot(state), 195 => self.tick_n195_background_grate(state), + 196 => self.tick_n196_ironhead_wall(state), + 197 => self.tick_n197_porcupine_fish(state), + 198 => self.tick_n198_ironhead_projectile(state), 199 => self.tick_n199_wind_particles(state), + 200 => self.tick_n200_zombie_dragon(state, players, npc_list), + 201 => self.tick_n201_zombie_dragon_dead(state), + 202 => self.tick_n202_zombie_dragon_projectile(state), + 203 => self.tick_n203_critter_destroyed_egg_corridor(state, players), + 204 => self.tick_n204_small_falling_spike(state, players, npc_list), + 205 => self.tick_n205_large_falling_spike(state, players, npc_list, bullet_manager), + 206 => self.tick_n206_counter_bomb(state, players, npc_list), 207 => self.tick_n207_counter_bomb_countdown(state), 208 => self.tick_n208_basu_destroyed_egg_corridor(state, players, npc_list), 209 => self.tick_n209_basu_projectile_destroyed_egg_corridor(state), + 210 => self.tick_n210_beetle_destroyed_egg_corridor(state, players), 211 => self.tick_n211_small_spikes(state), + 212 => self.tick_n212_sky_dragon(state, players, npc_list), + 213 => self.tick_n213_night_spirit(state, players, npc_list), + 214 => self.tick_n214_night_spirit_projectile(state, npc_list), 215 => self.tick_n215_sandcroc_outer_wall(state, players), 216 => self.tick_n216_debug_cat(state), + 217 => self.tick_n217_itoh(state), + 218 => self.tick_n218_core_giant_ball(state), + 219 => self.tick_n219_smoke_generator(state, npc_list), + 220 => self.tick_n220_shovel_brigade(state), + 221 => self.tick_n221_shovel_brigade_walking(state), 222 => self.tick_n222_prison_bars(state), + 223 => self.tick_n223_momorin(state, players), + 224 => self.tick_n224_chie(state, players), + 225 => self.tick_n225_megane(state), + 226 => self.tick_n226_kanpachi_plantation(state), 227 => self.tick_n227_bucket(state), 229 => self.tick_n229_red_flowers_sprouts(state), 230 => self.tick_n230_red_flowers_blooming(state), diff --git a/src/physics.rs b/src/physics.rs index 703413e..9552e99 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -125,104 +125,102 @@ pub trait PhysicalEntity { let bounds_bottom = if self.is_player() { 0x800 } else { 0x600 }; let half_tile_size = state.tile_size.as_int() * 0x100; - // left wall if (self.y() - self.hit_bounds().top as i32) < ((y * 2 + 1) * half_tile_size - bounds_top) - && (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size + bounds_bottom) - && (self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size - && (self.x() - self.hit_bounds().right as i32) > (x * 2) * half_tile_size - { - self.set_x(((x * 2 + 1) * half_tile_size) + self.hit_bounds().right as i32); + && (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size + bounds_bottom) { + // left wall + if (self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size + && (self.x() - self.hit_bounds().right as i32) > (x * 2) * half_tile_size + { + self.set_x(((x * 2 + 1) * half_tile_size) + self.hit_bounds().right as i32); - if self.is_player() { - if self.vel_x() < -0x180 { - self.set_vel_x(-0x180); + if self.is_player() { + if self.vel_x() < -0x180 { + self.set_vel_x(-0x180); + } + + if !self.player_left_pressed() && self.vel_x() < 0 { + self.set_vel_x(0); + } } - if !self.player_left_pressed() && self.vel_x() < 0 { - self.set_vel_x(0); - } + self.flags().set_hit_left_wall(true); } - self.flags().set_hit_left_wall(true); - } + // right wall + if (self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size + && (self.x() + self.hit_bounds().right as i32) < (x * 2) * half_tile_size + { + self.set_x(((x * 2 - 1) * half_tile_size) - self.hit_bounds().right as i32); - // right wall - if (self.y() - self.hit_bounds().top as i32) < ((y * 2 + 1) * half_tile_size - bounds_top) - && (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size + bounds_bottom) - && (self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size - && (self.x() + self.hit_bounds().right as i32) < (x * 2) * half_tile_size - { - self.set_x(((x * 2 - 1) * half_tile_size) - self.hit_bounds().right as i32); + if self.is_player() { + if self.vel_x() > 0x180 { + self.set_vel_x(0x180); + } - if self.is_player() { - if self.vel_x() > 0x180 { - self.set_vel_x(0x180); + if !self.player_right_pressed() && self.vel_x() > 0 { + self.set_vel_x(0); + } } - if !self.player_right_pressed() && self.vel_x() > 0 { - self.set_vel_x(0); - } + self.flags().set_hit_right_wall(true); } - - self.flags().set_hit_right_wall(true); } - // ceiling if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size - bounds_x) - && ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size + bounds_x) - && (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size - && (self.y() - self.hit_bounds().top as i32) > (y * 2) * half_tile_size - { - self.set_y(((y * 2 + 1) * half_tile_size) + self.hit_bounds().top as i32); + && ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size + bounds_x) { + // ceiling + if (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size + && (self.y() - self.hit_bounds().top as i32) > (y * 2) * half_tile_size + { + self.set_y(((y * 2 + 1) * half_tile_size) + self.hit_bounds().top as i32); - if self.is_player() { - if !self.cond().hidden() && self.vel_y() < -0x200 { - state.sound_manager.play_sfx(3); - state.create_caret( - self.x(), - self.y() - self.hit_bounds().top as i32, - CaretType::LittleParticles, - Direction::Left, - ); - state.create_caret( - self.x(), - self.y() - self.hit_bounds().top as i32, - CaretType::LittleParticles, - Direction::Left, - ); - } + if self.is_player() { + if !self.cond().hidden() && self.vel_y() < -0x200 { + state.sound_manager.play_sfx(3); + state.create_caret( + self.x(), + self.y() - self.hit_bounds().top as i32, + CaretType::LittleParticles, + Direction::Left, + ); + state.create_caret( + self.x(), + self.y() - self.hit_bounds().top as i32, + CaretType::LittleParticles, + Direction::Left, + ); + } - if self.vel_y() < 0 { + if self.vel_y() < 0 { + self.set_vel_y(0); + } + } else { self.set_vel_y(0); } - } else { - self.set_vel_y(0); + + self.flags().set_hit_top_wall(true); } - self.flags().set_hit_top_wall(true); - } + // floor + if ((self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size)) + && ((self.y() + self.hit_bounds().bottom as i32) < (y * 2) * half_tile_size) + { + self.set_y(((y * 2 - 1) * half_tile_size) - self.hit_bounds().bottom as i32); - // floor - if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size - bounds_x) - && ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size + bounds_x) - && ((self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size)) - && ((self.y() + self.hit_bounds().bottom as i32) < (y * 2) * half_tile_size) - { - self.set_y(((y * 2 - 1) * half_tile_size) - self.hit_bounds().bottom as i32); + if self.is_player() { + if self.vel_y() > 0x400 { + state.sound_manager.play_sfx(23); + } - if self.is_player() { - if self.vel_y() > 0x400 { - state.sound_manager.play_sfx(23); - } - - if self.vel_y() > 0 { + if self.vel_y() > 0 { + self.set_vel_y(0); + } + } else { self.set_vel_y(0); } - } else { - self.set_vel_y(0); - } - self.flags().set_hit_bottom_wall(true); + self.flags().set_hit_bottom_wall(true); + } } } @@ -897,5 +895,9 @@ pub trait PhysicalEntity { _ => {} } } + + if self.is_player() && (self.y() - 0x800) > state.water_level { + self.flags().set_in_water(true); + } } } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 8d3c3e7..ce6031b 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -1289,6 +1289,45 @@ impl GameScene { batch.draw(ctx)?; + if layer == TileLayer::Foreground && self.stage.data.background_type == BackgroundType::Water { + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &self.tex_background_name)?; + let rect_top = Rect { + left: 0, + top: 0, + right: 32, + bottom: 16 + }; + let rect_middle = Rect { + left: 0, + top: 16, + right: 32, + bottom: 48 + }; + + let tile_start_x = frame_x as i32 / 32; + let tile_end_x = (frame_x + 16.0 + state.canvas_size.0) as i32 / 32 + 1; + let water_y = state.water_level as f32 / 512.0; + let tile_count_y = (frame_y + 16.0 + state.canvas_size.1 - water_y) as i32 / 32 + 1; + + for x in tile_start_x..tile_end_x { + batch.add_rect( + (x as f32 * 32.0) - frame_x, + water_y - frame_y, + &rect_top, + ); + + for y in 0..tile_count_y { + batch.add_rect( + (x as f32 * 32.0) - frame_x, + (y as f32 * 32.0) + water_y - frame_y, + &rect_middle, + ); + } + } + + batch.draw(ctx)?; + } + Ok(()) } @@ -1518,7 +1557,7 @@ impl GameScene { [&mut self.player1, &mut self.player2], &self.npc_list, &mut self.stage, - &self.bullet_manager, + &mut self.bullet_manager, &mut self.flash, ), )?; @@ -1698,12 +1737,12 @@ impl GameScene { self.draw_debug_object(npc, state, ctx)?; let text = format!("{}:{}:{}", npc.id, npc.npc_type, npc.action_num); - state.font.draw_colored_text_scaled( + state.font.draw_colored_text_with_shadow_scaled( text.chars(), ((npc.x - self.frame.x) / 0x200) as f32, ((npc.y - self.frame.y) / 0x200) as f32, 0.5, - ((npc.id & 0xf0) as u8, (npc.cond.0 >> 8) as u8, (npc.id & 0x0f << 4) as u8, 255), + (255, 255, 0, 255), &state.constants, &mut state.texture_set, ctx, @@ -1789,12 +1828,16 @@ impl Scene for GameScene { self.frame.target_y = self.player1.y; self.frame.immediate_update(state, &self.stage); + // I'd personally set it to something higher but left it as is for accuracy. + state.water_level = 0x1e0000; + self.lighting_mode = match () { _ if self.intro_mode => LightingMode::None, _ if !state.constants.is_switch && (self.stage.data.background_type == BackgroundType::Black || self.stage.data.background.name() == "bkBlack") => LightingMode::Ambient, _ if state.constants.is_switch && (self.stage.data.background_type == BackgroundType::Black || self.stage.data.background.name() == "bkBlack") => LightingMode::None, + _ if self.stage.data.background.name() == "bkFall" => LightingMode::None, _ if self.stage.data.background_type != BackgroundType::Black && self.stage.data.background_type != BackgroundType::Outside && self.stage.data.background_type != BackgroundType::OutsideWind @@ -1857,6 +1900,8 @@ impl Scene for GameScene { } for _ in 0..ticks { + TextScriptVM::run(state, self, ctx)?; + match state.textscript_vm.mode { ScriptMode::Map if state.control_flags.tick_world() => self.tick_world(state)?, ScriptMode::StageSelect => self.stage_select.tick(state, (ctx, &self.player1, &self.player2))?, @@ -1883,12 +1928,11 @@ impl Scene for GameScene { } self.flash.tick(state, ())?; - TextScriptVM::run(state, self, ctx)?; #[cfg(feature = "scripting")] state.lua.scene_tick(); - if state.control_flags.control_enabled() { + if state.control_flags.tick_world() { self.tick = self.tick.wrapping_add(1); } } diff --git a/src/scene/loading_scene.rs b/src/scene/loading_scene.rs index 03df7cd..fa95491 100644 --- a/src/scene/loading_scene.rs +++ b/src/scene/loading_scene.rs @@ -45,7 +45,6 @@ impl LoadingScene { impl Scene for LoadingScene { fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - println!("**TICK**"); // deferred to let the loading image draw if self.tick == 1 { if let Err(err) = self.load_stuff(state, ctx) { diff --git a/src/scripting/boot.lua b/src/scripting/boot.lua index 3c14922..671ab88 100644 --- a/src/scripting/boot.lua +++ b/src/scripting/boot.lua @@ -362,6 +362,19 @@ setmetatable(doukutsu.rs, { end, }) +setmetatable(doukutsu, { + __index = function(self, property) + if property == "currentStage" then + local v = __doukutsu_rs:stageCommand(0x03) + if v == nil then + v = -1 + end + + return v + end + end, +}) + doukutsu.player = __doukutsu_rs_runtime_dont_touch._playerRef0 function doukutsu.playSfx(id) diff --git a/src/scripting/doukutsu.d.ts b/src/scripting/doukutsu.d.ts index 3b05c91..3cc5c68 100644 --- a/src/scripting/doukutsu.d.ts +++ b/src/scripting/doukutsu.d.ts @@ -207,6 +207,11 @@ declare namespace doukutsu { * Helper property for doukutsu-rs specific APIs. */ const rs: DoukutsuRSApi; + + /** + * The number of current stage, read-only. Set to -1 if in menu. + */ + const currentStage: number; /** * Plays a sound effect with specified ID. diff --git a/src/scripting/doukutsu.rs b/src/scripting/doukutsu.rs index 6418988..3be3538 100644 --- a/src/scripting/doukutsu.rs +++ b/src/scripting/doukutsu.rs @@ -440,8 +440,9 @@ impl Doukutsu { LightingMode::Ambient => "ambient", }), 0x02 => state.push(game_state.settings.shader_effects), + 0x03 => state.push(game_scene.stage_id as u32), 0x101 => { - if let Some(v) = state.to_str(4) { + if let Some(v) = state.to_str(3) { game_scene.lighting_mode = match v { "none" => LightingMode::None, "backgroundOnly" => LightingMode::BackgroundOnly, diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 166d517..2b1cfad 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -129,6 +129,7 @@ pub struct SharedGameState { pub npc_super_pos: (i32, i32), pub npc_curly_target: (i32, i32), pub npc_curly_counter: u16, + pub water_level: i32, pub stages: Vec, pub frame_time: f64, pub debugger: bool, @@ -207,8 +208,6 @@ impl SharedGameState { } } - println!("lookup path: {:#?}", texture_set.paths); - #[cfg(feature = "hooks")] init_hooks(); @@ -231,6 +230,7 @@ impl SharedGameState { npc_super_pos: (0, 0), npc_curly_target: (0, 0), npc_curly_counter: 0, + water_level: 0, stages: Vec::with_capacity(96), frame_time: 0.0, debugger: false, diff --git a/src/sound/org_playback.rs b/src/sound/org_playback.rs index 9d42706..5fec6e3 100644 --- a/src/sound/org_playback.rs +++ b/src/sound/org_playback.rs @@ -165,7 +165,6 @@ impl OrgPlaybackEngine { for track in 0..8 { if let Some(note) = self.song.tracks[track].notes.iter().find(|x| x.pos == self.play_pos) { // New note - //eprintln!("{:?}", &self.keys); if note.key != 255 { if self.keys[track] == 255 { // New diff --git a/src/text_script.rs b/src/text_script.rs index e3544ad..564ff3e 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -1259,6 +1259,7 @@ impl TextScriptVM { let skip = state.textscript_vm.flags.cutscene_skip(); state.control_flags.set_tick_world(true); + state.control_flags.set_interactions_disabled(true); state.textscript_vm.flags.0 = 0; state.textscript_vm.flags.set_cutscene_skip(skip); state.textscript_vm.face = 0; @@ -1542,7 +1543,7 @@ impl TextScriptVM { [&mut game_scene.player1, &mut game_scene.player2], &game_scene.npc_list, &mut game_scene.stage, - &game_scene.bullet_manager, + &mut game_scene.bullet_manager, &mut game_scene.flash, ), )?; diff --git a/src/texture_set.rs b/src/texture_set.rs index 7364d02..0561470 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -257,7 +257,6 @@ impl TextureSet { .iter() .find_map(|s| { FILE_TYPES.iter().map(|ext| [s, name, ext].join("")).find(|path| { - println!("{}", path); filesystem::exists(ctx, path) }) }) @@ -268,7 +267,6 @@ impl TextureSet { .iter() .find_map(|s| { FILE_TYPES.iter().map(|ext| [s, name, ".glow", ext].join("")).find(|path| { - println!("{}", path); filesystem::exists(ctx, path) }) }).is_some(); diff --git a/src/weapon/bullet.rs b/src/weapon/bullet.rs index d05bb7a..0530b8d 100644 --- a/src/weapon/bullet.rs +++ b/src/weapon/bullet.rs @@ -1010,6 +1010,15 @@ impl Bullet { self.anim_rect = state.constants.weapon.bullet_rects.b023_blade_slash[self.anim_num as usize + dir_offset]; } + fn tick_spike(&mut self) { + self.action_counter += 1; + if self.action_counter > 2 { + self.cond.set_alive(false); + } + + self.anim_rect = Rect::new(0, 0, 0, 0); + } + fn tick_blade_1(&mut self, state: &mut SharedGameState) { self.action_counter += 1; if self.action_counter > self.lifetime { @@ -1597,6 +1606,7 @@ impl Bullet { 21 => self.tick_bubble_3(state, players, new_bullets), 22 => self.tick_bubble_spines(state), 23 => self.tick_blade_slash(state), + 24 => self.tick_spike(), 25 => self.tick_blade_1(state), 26 => self.tick_blade_2(state), 27 => self.tick_blade_3(state, new_bullets),