From e018b53b042d041f4b2911deed06a6b13071a72a Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Fri, 8 Oct 2021 04:41:31 +0200 Subject: [PATCH] commit stuff from last 2 months idfk what I've exactly changed --- Cargo.toml | 2 +- src/bmfont_renderer.rs | 15 + src/builtin/lightmap/direct.png | Bin 8690 -> 0 bytes src/builtin/lightmap/spot.png | Bin 11309 -> 3041 bytes src/builtin/touch.png | Bin 16896 -> 789 bytes src/builtin_fs.rs | 1 - src/common.rs | 8 +- src/engine_constants/npcs.rs | 32 +- src/framework/backend_glutin.rs | 2 - src/lib.rs | 5 +- src/npc/ai/balrog.rs | 15 +- src/npc/ai/booster.rs | 2 +- src/npc/ai/chaco.rs | 2 +- src/npc/ai/characters.rs | 104 ++++- src/npc/ai/curly.rs | 439 +++++++++++++++++++- src/npc/ai/doctor.rs | 2 +- src/npc/ai/egg_corridor.rs | 713 +++++++++++++++++++++++++++++--- src/npc/ai/first_cave.rs | 6 +- src/npc/ai/grasstown.rs | 24 +- src/npc/ai/igor.rs | 6 +- src/npc/ai/intro.rs | 4 +- src/npc/ai/last_cave.rs | 2 +- src/npc/ai/maze.rs | 32 +- src/npc/ai/mimiga_village.rs | 10 +- src/npc/ai/misc.rs | 213 ++++++++-- src/npc/ai/misery.rs | 6 +- src/npc/ai/mod.rs | 49 +-- src/npc/ai/outer_wall.rs | 220 +++++++++- src/npc/ai/pickups.rs | 4 +- src/npc/ai/plantation.rs | 289 +++++++++++++ src/npc/ai/quote.rs | 6 +- src/npc/ai/sand_zone.rs | 40 +- src/npc/ai/santa.rs | 2 +- src/npc/ai/sue.rs | 4 +- src/npc/ai/toroko.rs | 10 +- src/npc/ai/weapon_trail.rs | 6 +- src/npc/boss/core.rs | 454 ++++++++++++++++++-- src/npc/boss/ironhead.rs | 97 ++++- src/npc/boss/mod.rs | 4 +- src/npc/mod.rs | 40 +- src/physics.rs | 148 +++---- src/scene/game_scene.rs | 54 ++- src/scene/loading_scene.rs | 1 - src/scripting/boot.lua | 13 + src/scripting/doukutsu.d.ts | 5 + src/scripting/doukutsu.rs | 3 +- src/shared_game_state.rs | 4 +- src/sound/org_playback.rs | 1 - src/text_script.rs | 3 +- src/texture_set.rs | 2 - src/weapon/bullet.rs | 10 + 51 files changed, 2752 insertions(+), 362 deletions(-) delete mode 100644 src/builtin/lightmap/direct.png create mode 100644 src/npc/ai/plantation.rs 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 af79222e1f478d114e9a45011dfe16f57b1bb475..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8690 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 literal 11309 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 JM6LruV1mzuQuF`- literal 16896 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 teZmVZdx{8~m4@a&u>sV@lAhdcdo 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),