From c4a87f5a64b110672b28d479db25a5723876f577 Mon Sep 17 00:00:00 2001 From: Alula Date: Wed, 4 Nov 2020 10:06:02 +0100 Subject: [PATCH] Add complete implementation of Balfrog fight --- src/engine_constants.rs | 24 +++ src/npc/boss/balfrog.rs | 313 ++++++++++++++++++++++++++++++++++++-- src/npc/boss/mod.rs | 20 ++- src/npc/grasstown.rs | 240 +++++++++++++++++++++++++++++ src/npc/mimiga_village.rs | 7 +- src/npc/mod.rs | 21 ++- src/player_hit.rs | 13 +- src/scene/game_scene.rs | 142 ++++++++--------- src/text_script.rs | 2 +- 9 files changed, 676 insertions(+), 106 deletions(-) diff --git a/src/engine_constants.rs b/src/engine_constants.rs index cba6764..a065f1e 100644 --- a/src/engine_constants.rs +++ b/src/engine_constants.rs @@ -230,6 +230,9 @@ pub struct NPCConsts { pub n097_fan_up: [Rect; 3], pub n098_fan_right: [Rect; 3], pub n099_fan_down: [Rect; 3], + pub n104_frog: [Rect; 6], + pub n108_balfrog_projectile: [Rect; 3], + pub n110_puchi: [Rect; 6], pub n111_quote_teleport_out: [Rect; 4], pub n112_quote_teleport_in: [Rect; 4], pub n129_fireball_snake_trail: [Rect; 18], @@ -1299,6 +1302,27 @@ impl EngineConstants { Rect { left: 288, top: 168, right: 304, bottom: 184 }, Rect { left: 304, top: 168, right: 320, bottom: 184 }, ], + n104_frog: [ + Rect { left: 0, top: 112, right: 32, bottom: 144 }, // left + Rect { left: 32, top: 112, right: 64, bottom: 144 }, + Rect { left: 64, top: 112, right: 96, bottom: 144 }, + Rect { left: 0, top: 144, right: 32, bottom: 176 }, // right + Rect { left: 32, top: 144, right: 64, bottom: 176 }, + Rect { left: 64, top: 144, right: 96, bottom: 176 }, + ], + n108_balfrog_projectile: [ + Rect { left: 96, top: 48, right: 112, bottom: 64 }, + Rect { left: 112, top: 48, right: 128, bottom: 64 }, + Rect { left: 128, top: 48, right: 144, bottom: 64 }, + ], + n110_puchi: [ + Rect { left: 96, top: 128, right: 112, bottom: 144 }, // left + Rect { left: 112, top: 128, right: 128, bottom: 144 }, + Rect { left: 128, top: 128, right: 144, bottom: 144 }, + Rect { left: 96, top: 144, right: 112, bottom: 160 }, // right + Rect { left: 112, top: 144, right: 128, bottom: 160 }, + Rect { left: 128, top: 144, right: 144, bottom: 160 }, + ], n111_quote_teleport_out: [ Rect { left: 0, top: 0, right: 16, bottom: 16 }, // left Rect { left: 16, top: 0, right: 32, bottom: 16 }, diff --git a/src/npc/boss/balfrog.rs b/src/npc/boss/balfrog.rs index 83516b5..153d522 100644 --- a/src/npc/boss/balfrog.rs +++ b/src/npc/boss/balfrog.rs @@ -1,9 +1,37 @@ -use crate::common::{Direction, Rect}; +use crate::caret::CaretType; +use crate::common::{Direction, Rect, CDEG_RAD}; +use crate::ggez::GameResult; +use crate::npc::{NPC, NPCMap}; use crate::npc::boss::BossNPC; -use crate::npc::NPCMap; use crate::player::Player; use crate::shared_game_state::SharedGameState; +impl NPC { + pub(crate) fn tick_n108_balfrog_projectile(&mut self, state: &mut SharedGameState) -> GameResult { + if self.action_counter > 300 || (self.flags.0 & 0xff) != 0 { + self.cond.set_alive(false); + state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left); + } + + self.x += self.vel_x; + self.y += self.vel_y; + + self.action_counter += 1; + self.anim_counter += 1; + if self.anim_counter > 1 { + self.anim_counter = 0; + self.anim_num += 1; + if self.anim_num > 2 { + self.anim_num = 0; + } + } + + self.anim_rect = state.constants.npc.n108_balfrog_projectile[self.anim_num as usize]; + + Ok(()) + } +} + impl BossNPC { pub(crate) fn tick_b02_balfrog(&mut self, state: &mut SharedGameState, player: &Player) { match self.parts[0].action_num { @@ -59,12 +87,12 @@ impl BossNPC { } 20 | 21 => { if self.parts[0].action_num == 20 { - self.parts[0].action_num = 0; + self.parts[0].action_num = 21; self.parts[0].action_counter = 0 } self.parts[0].action_counter += 1; - if self.parts[0].action_counter / 2 % 2 != 0 { + if (self.parts[0].action_counter / 2 % 2) != 0 { self.parts[0].anim_num = 3; } else { self.parts[0].anim_num = 0; @@ -138,7 +166,7 @@ impl BossNPC { npc.cond.set_alive(true); npc.x = state.game_rng.range(4..16) as isize * 16 * 0x200; npc.y = state.game_rng.range(0..4) as isize * 16 * 0x200; - npc.direction = if npc.x < player.x { Direction::Left } else { Direction::Right }; + npc.direction = Direction::FacingPlayer; state.new_npcs.push(npc); @@ -182,7 +210,7 @@ impl BossNPC { if self.parts[0].anim_counter > 4 { self.parts[0].action_num = 113; self.parts[0].action_counter = 0; - self.parts[0].action_counter2 = 16; + self.parts[0].vel_x2 = 16; self.parts[0].anim_num = 3; self.parts[0].target_x = self.parts[0].life as isize; self.parts[1].npc_flags.set_shootable(true); @@ -205,24 +233,27 @@ impl BossNPC { self.parts[0].action_counter += 1; if self.parts[0].action_counter > 16 { self.parts[0].action_counter = 0; - self.parts[0].action_counter2 = self.parts[0].action_counter2.saturating_sub(1); + self.parts[0].vel_x2 = self.parts[0].vel_x2.saturating_sub(1); - let px = self.parts[0].x + self.parts[0].direction.vector_x() * 2 * 16 * 0x200; + let px = self.parts[0].x + self.parts[0].direction.vector_x() * 2 * 16 * 0x200 - player.x; let py = self.parts[0].y - 8 * 0x200 - player.y; - let deg = f64::atan2(py as f64, px as f64); + let deg = f64::atan2(py as f64, px as f64) + + state.game_rng.range(-16..16) as f64 * CDEG_RAD; // todo rand let mut npc = NPCMap::create_npc(108, &state.npc_table); npc.cond.set_alive(true); - npc.x = px; - npc.y = py; - npc.vel_x = (deg.cos() * 512.0) as isize; - npc.vel_y = (deg.sin() * 512.0) as isize; + npc.x = self.parts[0].x + self.parts[0].direction.vector_x() * 2 * 16 * 0x200; + npc.y = self.parts[0].y - 8 * 0x200; + npc.vel_x = (deg.cos() * -512.0) as isize; + npc.vel_y = (deg.sin() * -512.0) as isize; + + state.new_npcs.push(npc); state.sound_manager.play_sfx(39); - if self.parts[0].action_counter2 == 0 || (self.parts[0].life as isize) < self.parts[0].target_x - 90 { + if self.parts[0].vel_x2 == 0 || (self.parts[0].life as isize) < self.parts[0].target_x - 90 { self.parts[0].action_num = 114; self.parts[0].action_counter = 0; self.parts[0].anim_num = 2; @@ -246,6 +277,206 @@ impl BossNPC { } } } + 120 | 121 => { + if self.parts[0].action_num == 120 { + self.parts[0].action_num = 121; + self.parts[0].action_counter = 0; + self.parts[0].anim_num = 1; + self.parts[0].vel_x = 0; + } + + self.parts[0].action_counter += 1; + if self.parts[0].action_counter > 50 { + self.parts[0].action_num = 122; + self.parts[0].anim_num = 2; + self.parts[0].anim_counter = 0; + } + } + 122 => { + self.parts[0].anim_counter += 1; + if self.parts[0].anim_counter > 20 { + self.parts[0].action_num = 123; + self.parts[0].anim_num = 1; + self.parts[0].anim_counter = 0; + } + } + 123 => { + self.parts[0].anim_counter += 1; + if self.parts[0].anim_counter > 4 { + self.parts[0].action_num = 124; + self.parts[0].anim_num = 5; + self.parts[0].vel_x = -5 * 0x200; + self.parts[0].display_bounds.top = 64 * 0x200; + self.parts[0].display_bounds.bottom = 24 * 0x200; + + state.sound_manager.play_sfx(25); + } + } + 124 => { + if self.parts[0].flags.hit_bottom_wall() { + self.parts[0].action_num = 100; + self.parts[0].anim_num = 1; + self.parts[0].display_bounds.top = 48 * 0x200; + self.parts[0].display_bounds.bottom = 16 * 0x200; + + let mut npc = NPCMap::create_npc(104, &state.npc_table); + for _ in 0..2 { + npc.cond.set_alive(true); + npc.x = state.game_rng.range(4..16) as isize * 16 * 0x200; + npc.y = state.game_rng.range(0..4) as isize * 16 * 0x200; + npc.direction = Direction::FacingPlayer; + + state.new_npcs.push(npc); + } + + let mut npc = NPCMap::create_npc(110, &state.npc_table); + for _ in 0..6 { + npc.cond.set_alive(true); + npc.x = state.game_rng.range(4..16) as isize * 16 * 0x200; + npc.y = state.game_rng.range(0..4) as isize * 16 * 0x200; + npc.direction = Direction::FacingPlayer; + + state.new_npcs.push(npc); + } + + let mut npc = NPCMap::create_npc(4, &state.npc_table); + for _ in 0..8 { + npc.cond.set_alive(true); + npc.x = self.parts[0].x + state.game_rng.range(-12..12) as isize * 0x200; + npc.y = self.parts[0].y + self.parts[0].hit_bounds.bottom as isize; + npc.vel_x = state.game_rng.range(-0x155..0x155) as isize; + npc.vel_y = state.game_rng.range(-0x600..0) as isize; + npc.direction = Direction::Left; + + state.new_npcs.push(npc); + } + + if self.parts[0].direction == Direction::Left && self.parts[0].x < player.x { + self.parts[0].action_num = 110; + self.parts[0].direction = Direction::Right; + } + + if self.parts[0].direction == Direction::Right && self.parts[0].x > player.x { + self.parts[0].action_num = 110; + self.parts[0].direction = Direction::Right; + } + + state.sound_manager.play_sfx(26); + state.quake_counter = 60; + } + } + 130 | 131 => { + if self.parts[0].action_num == 130 { + self.parts[0].action_num = 131; + self.parts[0].action_counter = 0; + self.parts[0].anim_num = 3; + self.parts[0].vel_x = 0; + + self.parts[1].cond.set_alive(false); + self.parts[2].cond.set_alive(false); + + state.sound_manager.play_sfx(72); + + let mut npc = NPCMap::create_npc(4, &state.npc_table); + for _ in 0..8 { + npc.cond.set_alive(true); + npc.x = self.parts[0].x + state.game_rng.range(-12..12) as isize * 0x200; + npc.y = self.parts[0].y + state.game_rng.range(-12..12) as isize * 0x200; + npc.vel_x = state.game_rng.range(-0x155..0x155) as isize; + npc.vel_y = state.game_rng.range(-0x600..0) as isize; + npc.direction = Direction::Left; + state.new_npcs.push(npc); + } + } + + self.parts[0].action_counter += 1; + if (self.parts[0].action_counter % 5) == 0 { + let mut npc = NPCMap::create_npc(4, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.parts[0].x + state.game_rng.range(-12..12) as isize * 0x200; + npc.y = self.parts[0].y + state.game_rng.range(-12..12) as isize * 0x200; + npc.vel_x = state.game_rng.range(-0x155..0x155) as isize; + npc.vel_y = state.game_rng.range(-0x600..0) as isize; + npc.direction = Direction::Left; + state.new_npcs.push(npc); + } + + self.parts[0].x += if (self.parts[0].action_counter / 2 % 2) != 0 { + -0x200 + } else { + 0x200 + }; + + if self.parts[0].action_counter > 100 { + self.parts[0].action_num = 132; + self.parts[0].action_counter = 0; + } + } + 132 => { + self.parts[0].action_counter += 1; + if (self.parts[0].action_counter / 2 % 2) != 0 { + self.parts[0].anim_num = 6; + self.parts[0].display_bounds = Rect { + left: 20 * 0x200, + top: 12 * 0x200, + right: 20 * 0x200, + bottom: 12 * 0x200, + }; + } else { + self.parts[0].anim_num = 3; + self.parts[0].display_bounds = Rect { + left: 48 * 0x200, + top: 48 * 0x200, + right: 32 * 0x200, + bottom: 16 * 0x200, + }; + } + + if (self.parts[0].action_counter % 9) == 0 { + let mut npc = NPCMap::create_npc(4, &state.npc_table); + npc.cond.set_alive(true); + npc.x = self.parts[0].x + state.game_rng.range(-12..12) as isize * 0x200; + npc.y = self.parts[0].y + state.game_rng.range(-12..12) as isize * 0x200; + npc.vel_x = state.game_rng.range(-0x155..0x155) as isize; + npc.vel_y = state.game_rng.range(-0x600..0) as isize; + npc.direction = Direction::Left; + state.new_npcs.push(npc); + } + + if self.parts[0].action_counter > 150 { + self.parts[0].action_num = 140; + self.parts[0].hit_bounds.bottom = 12 * 0x200; + } + } + 140 | 141 => { + if self.parts[0].action_num == 140 { + self.parts[0].action_num = 141; + } + + if self.parts[0].flags.hit_bottom_wall() { + self.parts[0].action_num = 142; + self.parts[0].action_counter = 0; + self.parts[0].anim_num = 7; + } + } + 142 => { + self.parts[0].action_counter += 1; + if self.parts[0].action_counter > 30 { + self.parts[0].anim_num = 8; + self.parts[0].vel_y = -5 * 0x200; + self.parts[0].npc_flags.set_ignore_solidity(true); + self.parts[0].action_num = 143; + } + } + 143 => { + self.parts[0].vel_y = -5 * 0x200; + if self.parts[0].y < 0 { + self.parts[0].cond.set_alive(false); + + state.sound_manager.play_sfx(26); + state.quake_counter = 30; + } + } _ => {} } @@ -258,7 +489,59 @@ impl BossNPC { self.parts[0].y += self.parts[0].vel_y; let dir_offset = if self.parts[0].direction == Direction::Left { 0 } else { 9 }; - self.parts[0].anim_rect = state.constants.npc.b02_balfrog[self.parts[0].anim_num as usize + dir_offset]; + + match self.parts[0].anim_num { + 0 => { + self.hurt_sound[1] = 52; + self.parts[1].size = 3; + self.parts[1].npc_flags.set_invulnerable(true); + self.parts[1].hit_bounds = Rect { + left: 16 * 0x200, + top: 16 * 0x200, + right: 16 * 0x200, + bottom: 16 * 0x200, + }; + + self.hurt_sound[2] = 52; + self.parts[2].size = 3; + self.parts[2].npc_flags.set_invulnerable(true); + self.parts[2].hit_bounds = Rect { + left: 24 * 0x200, + top: 16 * 0x200, + right: 24 * 0x200, + bottom: 16 * 0x200, + }; + } + 1 => { + self.parts[1].x = self.parts[0].x + self.parts[0].direction.vector_x() * 24 * 0x200; + self.parts[1].y = self.parts[0].y - 24 * 0x200; + + self.parts[2].x = self.parts[0].x; + self.parts[2].y = self.parts[0].y; + } + 2 => { + self.parts[1].x = self.parts[0].x + self.parts[0].direction.vector_x() * 24 * 0x200; + self.parts[1].y = self.parts[0].y - 20 * 0x200; + + self.parts[2].x = self.parts[0].x; + self.parts[2].y = self.parts[0].y; + } + 3 | 4 => { + self.parts[1].x = self.parts[0].x + self.parts[0].direction.vector_x() * 24 * 0x200; + self.parts[1].y = self.parts[0].y - 16 * 0x200; + + self.parts[2].x = self.parts[0].x; + self.parts[2].y = self.parts[0].y; + } + 5 => { + self.parts[1].x = self.parts[0].x + self.parts[0].direction.vector_x() * 24 * 0x200; + self.parts[1].y = self.parts[0].y - 43 * 0x200; + + self.parts[2].x = self.parts[0].x; + self.parts[2].y = self.parts[0].y; + } + _ => { } + } } } diff --git a/src/npc/boss/mod.rs b/src/npc/boss/mod.rs index c3c28ce..7b4cb06 100644 --- a/src/npc/boss/mod.rs +++ b/src/npc/boss/mod.rs @@ -29,12 +29,16 @@ pub struct BossNPC { impl BossNPC { pub fn new() -> BossNPC { - let mut part = NPC::empty(); - part.cond.set_drs_boss(true); + let mut parts = [{ + let mut part = NPC::empty(); + part.cond.set_drs_boss(true); + part + }; 16]; + parts[0].cond.set_alive(true); BossNPC { boss_type: 0, - parts: [part; 16], + parts, hurt_sound: [0; 16], death_sound: [0; 16], } @@ -43,6 +47,10 @@ impl BossNPC { impl GameEntity<(&mut Player, &HashMap>, &mut Stage)> for BossNPC { fn tick(&mut self, state: &mut SharedGameState, (player, map, stage): (&mut Player, &HashMap>, &mut Stage)) -> GameResult { + if !self.parts[0].cond.alive() { + return Ok(()); + } + match self.boss_type { 1 => self.tick_b01_omega(), 2 => self.tick_b02_balfrog(state, player), @@ -54,6 +62,12 @@ impl GameEntity<(&mut Player, &HashMap>, &mut Stage)> for Boss 8 => self.tick_b09_ballos(), _ => {} } + + for part in self.parts.iter_mut() { + if part.shock > 0 { + part.shock -= 1; + } + } Ok(()) } diff --git a/src/npc/grasstown.rs b/src/npc/grasstown.rs index aaae5ee..7624f96 100644 --- a/src/npc/grasstown.rs +++ b/src/npc/grasstown.rs @@ -346,4 +346,244 @@ impl NPC { Ok(()) } + + pub fn tick_n104_frog(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult { + match self.action_num { + 0 => { + self.action_num = 1; + self.action_counter = 0; + self.vel_x = 0; + self.vel_y = 0; + + if self.tsc_direction == 4 { + self.direction = if (state.game_rng.next() & 1) == 0 { Direction::Left } else { Direction::Right }; + self.tsc_direction = self.direction as u16; + self.action_num = 3; + self.anim_num = 2; + self.npc_flags.set_ignore_solidity(true); + } else { + self.npc_flags.set_ignore_solidity(false); + } + + self.action_counter += 1; + + if state.game_rng.range(0..50) == 1 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 0; + self.anim_counter = 0; + } + } + 1 => { + self.action_counter += 1; + + if state.game_rng.range(0..50) == 1 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 0; + self.anim_counter = 0; + } + } + 2 => { + self.action_counter += 1; + self.anim_counter += 1; + + if self.anim_counter > 2 { + self.anim_counter = 0; + self.anim_num += 1; + if self.anim_num > 1 { + self.anim_num = 0; + } + } + + if self.action_counter > 18 { + self.action_num = 1; + } + } + 3 => { + self.action_counter += 1; + if self.action_counter > 40 { + self.npc_flags.set_ignore_solidity(false); + } + + if self.flags.hit_bottom_wall() { + self.anim_num = 0; + self.action_num = 0; + self.action_counter = 0; + } + } + 10 | 11 => { + if self.action_num == 10 { + self.action_num = 11; + } + + if self.flags.hit_left_wall() && self.vel_x < 0 { + self.vel_x = -self.vel_x; + self.direction = Direction::Right; + } + + if self.flags.hit_right_wall() && self.vel_x > 0 { + self.vel_x = -self.vel_x; + self.direction = Direction::Left; + } + + if self.flags.hit_bottom_wall() { + self.action_num = 0; + self.action_counter = 0; + self.anim_num = 0; + } + } + _ => {} + } + + if self.action_num <= 9 && self.action_num != 3 && self.action_counter > 10 + && ((self.shock > 0) + || (abs(self.x - player.x) < 160 * 0x200 + && abs(self.y - player.y) < 64 * 0x200) + && state.game_rng.range(0..50) == 2) { + self.direction = if self.x >= player.x { + Direction::Left + } else { + Direction::Right + }; + self.action_num = 10; + self.anim_num = 2; + self.vel_x = self.direction.vector_x() * 0x200; + self.vel_y = -0x5ff; + + if !player.cond.hidden() { + state.sound_manager.play_sfx(30); + } + } + + self.vel_y = (self.vel_y + 0x80).min(0x5ff); + + self.x += self.vel_x; + self.y += self.vel_y; + + let dir_offset = if self.direction == Direction::Left { 0 } else { 3 }; + self.anim_rect = state.constants.npc.n104_frog[self.anim_num as usize + dir_offset]; + + Ok(()) + } + + pub fn tick_n110_puchi(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult { + match self.action_num { + 0 => { + self.action_num = 1; + self.action_counter = 0; + self.vel_x = 0; + self.vel_y = 0; + + if self.tsc_direction == 4 { + self.direction = if (state.game_rng.next() & 1) == 0 { Direction::Left } else { Direction::Right }; + self.tsc_direction = self.direction as u16; + self.action_num = 3; + self.anim_num = 2; + self.npc_flags.set_ignore_solidity(true); + } else { + self.npc_flags.set_ignore_solidity(false); + } + + self.action_counter += 1; + + if state.game_rng.range(0..50) == 1 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 0; + self.anim_counter = 0; + } + } + 1 => { + self.action_counter += 1; + + if state.game_rng.range(0..50) == 1 { + self.action_num = 2; + self.action_counter = 0; + self.anim_num = 0; + self.anim_counter = 0; + } + } + 2 => { + self.action_counter += 1; + self.anim_counter += 1; + + if self.anim_counter > 2 { + self.anim_counter = 0; + self.anim_num += 1; + if self.anim_num > 1 { + self.anim_num = 0; + } + } + + if self.action_counter > 18 { + self.action_num = 1; + } + } + 3 => { + self.action_counter += 1; + if self.action_counter > 40 { + self.npc_flags.set_ignore_solidity(false); + } + + if self.flags.hit_bottom_wall() { + self.anim_num = 0; + self.action_num = 0; + self.action_counter = 0; + } + } + 10 | 11 => { + if self.action_num == 10 { + self.action_num = 11; + } + + if self.flags.hit_left_wall() && self.vel_x < 0 { + self.vel_x = -self.vel_x; + self.direction = Direction::Right; + } + + if self.flags.hit_right_wall() && self.vel_x > 0 { + self.vel_x = -self.vel_x; + self.direction = Direction::Left; + } + + if self.flags.hit_bottom_wall() { + self.action_num = 0; + self.action_counter = 0; + self.anim_num = 0; + } + } + _ => {} + } + + if self.action_num <= 9 && self.action_num != 3 && self.action_counter > 10 + && ((self.shock > 0) + || (abs(self.x - player.x) < 160 * 0x200 + && abs(self.y - player.y) < 64 * 0x200) + && state.game_rng.range(0..50) == 2) { + self.direction = if self.x >= player.x { + Direction::Left + } else { + Direction::Right + }; + self.action_num = 10; + self.anim_num = 2; + self.vel_x = self.direction.vector_x() * 0x100; + self.vel_y = -0x2ff; + + if !player.cond.hidden() { + state.sound_manager.play_sfx(30); + } + } + + self.vel_y = (self.vel_y + 0x80).min(0x5ff); + + self.x += self.vel_x; + self.y += self.vel_y; + + let dir_offset = if self.direction == Direction::Left { 0 } else { 3 }; + self.anim_rect = state.constants.npc.n110_puchi[self.anim_num as usize + dir_offset]; + + Ok(()) + } } diff --git a/src/npc/mimiga_village.rs b/src/npc/mimiga_village.rs index ea85ddc..76333af 100644 --- a/src/npc/mimiga_village.rs +++ b/src/npc/mimiga_village.rs @@ -308,6 +308,7 @@ impl NPC { self.vel_x = self.direction.vector_x() * 0x100; } 3 => { + self.anim_num = 4; self.vel_x = 0; self.action_counter += 1; @@ -316,18 +317,16 @@ impl NPC { self.action_num = 4; state.sound_manager.play_sfx(106); } - - self.anim_num = 4; } 4 => { + self.anim_num = 5; self.damage = 10; + self.action_counter += 1; if self.action_counter > 2 { self.action_counter = 0; self.action_num = 5; } - - self.anim_num = 5; } 5 => { self.action_counter += 1; diff --git a/src/npc/mod.rs b/src/npc/mod.rs index a4e8880..fa78d8c 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -242,6 +242,9 @@ impl GameEntity<(&mut Player, &HashMap>, &mut Stage)> for NPC 97 => self.tick_n097_fan_up(state, player), 98 => self.tick_n098_fan_right(state, player), 99 => self.tick_n099_fan_down(state, player), + 104 => self.tick_n104_frog(state, player), + 108 => self.tick_n108_balfrog_projectile(state), + 110 => self.tick_n110_puchi(state, player), 111 => self.tick_n111_quote_teleport_out(state, player), 112 => self.tick_n112_quote_teleport_in(state, player), 129 => self.tick_n129_fireball_snake_trail(state), @@ -574,7 +577,7 @@ impl NPCMap { state.create_caret(x, y, CaretType::Explosion, Direction::Left); } - pub fn process_dead_npcs(&mut self, list: &[u16], has_missile: bool, state: &mut SharedGameState) { + pub fn process_dead_npcs(&mut self, list: &[u16], has_missile: bool, player: &Player, state: &mut SharedGameState) { for id in list { if let Some(npc_cell) = self.npcs.get(id) { let mut npc = npc_cell.borrow_mut(); @@ -649,10 +652,10 @@ impl NPCMap { } } - self.process_npc_changes(state); + self.process_npc_changes(player, state); } - pub fn process_npc_changes(&mut self, state: &mut SharedGameState) { + pub fn process_npc_changes(&mut self, player: &Player, state: &mut SharedGameState) { if !state.new_npcs.is_empty() { for mut npc in state.new_npcs.iter_mut() { let id = if npc.id == 0 { @@ -662,6 +665,18 @@ impl NPCMap { }; npc.id = id; + if npc.tsc_direction == 0 { + npc.tsc_direction = npc.direction as u16; + } + + if npc.direction == Direction::FacingPlayer { + npc.direction = if npc.x < player.x { + Direction::Right + } else { + Direction::Left + }; + } + self.npc_ids.insert(id); self.npcs.insert(id, RefCell::new(*npc)); } diff --git a/src/player_hit.rs b/src/player_hit.rs index 249cee3..f59a800 100644 --- a/src/player_hit.rs +++ b/src/player_hit.rs @@ -7,7 +7,7 @@ use crate::common::{Condition, Direction, Flag, Rect}; use crate::inventory::{AddExperienceResult, Inventory}; use crate::npc::{NPC, NPCMap}; use crate::physics::PhysicalEntity; -use crate::player::{Player, ControlMode}; +use crate::player::{ControlMode, Player}; use crate::shared_game_state::SharedGameState; impl PhysicalEntity for Player { @@ -241,7 +241,7 @@ impl Player { flags = self.judge_hit_npc_non_solid(npc.borrow()); } - if flags.0 != 0 { + if !npc.cond.drs_boss() && flags.0 != 0 { match npc.npc_type { // experience pickup 1 => { @@ -294,7 +294,14 @@ impl Player { } if state.control_flags.control_enabled() && !npc.npc_flags.interactable() { - if flags.0 != 0 && npc.damage != 0 && !state.control_flags.interactions_disabled() { + if npc.npc_flags.rear_and_top_not_hurt() { + if flags.hit_left_wall() && npc.vel_x > 0 + || flags.hit_right_wall() && npc.vel_x < 0 + || flags.hit_top_wall() && npc.vel_y > 0 + || flags.hit_bottom_wall() && npc.vel_y < 0 { + self.damage(npc.damage as isize, state); + } + } else if flags.0 != 0 && npc.damage != 0 && !state.control_flags.interactions_disabled() { self.damage(npc.damage as isize, state); } } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 587870d..5c62199 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use itertools::Itertools; use log::info; @@ -11,7 +13,7 @@ use crate::ggez::{Context, GameResult, graphics, timer}; use crate::ggez::graphics::{BlendMode, Color, Drawable, DrawParam, FilterMode, Vector2}; use crate::ggez::nalgebra::clamp; use crate::inventory::{Inventory, TakeExperienceResult}; -use crate::npc::NPCMap; +use crate::npc::{NPC, NPCMap}; use crate::physics::PhysicalEntity; use crate::player::{Player, PlayerAppearance}; use crate::rng::RNG; @@ -607,10 +609,10 @@ impl GameScene { for npc_cell in self.npc_map.npcs.values() { let npc = npc_cell.borrow(); - if npc.x < (self.frame.x - 128 - npc.display_bounds.width() as isize * 0x200) - || npc.x > (self.frame.x + (state.canvas_size.0 as isize + npc.display_bounds.width() as isize) * 0x200) - && npc.y < (self.frame.y - 128 - npc.display_bounds.height() as isize * 0x200) - || npc.y > (self.frame.y + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200) { + if npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as isize * 0x200) + || npc.x > (self.frame.x + 128 * 0x200 + (state.canvas_size.0 as isize + npc.display_bounds.width() as isize) * 0x200) + && npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as isize * 0x200) + || npc.y > (self.frame.y + 128 * 0x200 + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200) { continue; } @@ -971,7 +973,7 @@ impl GameScene { if !dead_npcs.is_empty() { let missile = self.inventory.has_weapon(WeaponType::MissileLauncher) | self.inventory.has_weapon(WeaponType::SuperMissileLauncher); - self.npc_map.process_dead_npcs(&dead_npcs, missile, state); + self.npc_map.process_dead_npcs(&dead_npcs, missile, &self.player, state); self.npc_map.garbage_collect(); } } @@ -1011,7 +1013,7 @@ impl GameScene { } } self.npc_map.boss_map.tick(state, (&mut self.player, &self.npc_map.npcs, &mut self.stage))?; - self.npc_map.process_npc_changes(state); + self.npc_map.process_npc_changes(&self.player, state); self.npc_map.garbage_collect(); self.player.tick_map_collisions(state, &mut self.stage); @@ -1027,13 +1029,15 @@ impl GameScene { } } for npc in self.npc_map.boss_map.parts.iter_mut() { - npc.tick_map_collisions(state, &mut self.stage); + if npc.cond.alive() && !npc.npc_flags.ignore_solidity() { + npc.tick_map_collisions(state, &mut self.stage); + } } - self.npc_map.process_npc_changes(state); + self.npc_map.process_npc_changes(&self.player, state); self.npc_map.garbage_collect(); self.tick_npc_bullet_collissions(state); - self.npc_map.process_npc_changes(state); + self.npc_map.process_npc_changes(&self.player, state); self.bullet_manager.tick_bullets(state, &self.player, &mut self.stage); state.tick_carets(); @@ -1183,77 +1187,61 @@ impl GameScene { Ok(()) } - fn draw_debug_outlines(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - for npc in self.npc_map.npcs.values() { - let npc = npc.borrow(); + fn draw_debug_npc(&self, npc: &NPC, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + if npc.x < (self.frame.x - 128 - npc.display_bounds.width() as isize * 0x200) + || npc.x > (self.frame.x + 128 + (state.canvas_size.0 as isize + npc.display_bounds.width() as isize) * 0x200) + && npc.y < (self.frame.y - 128 - npc.display_bounds.height() as isize * 0x200) + || npc.y > (self.frame.y + 128 + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200) { + return Ok(()); + } - if npc.x < (self.frame.x - 128 - npc.display_bounds.width() as isize * 0x200) - || npc.x > (self.frame.x + 128 + (state.canvas_size.0 as isize + npc.display_bounds.width() as isize) * 0x200) - && npc.y < (self.frame.y - 128 - npc.display_bounds.height() as isize * 0x200) - || npc.y > (self.frame.y + 128 + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200) { - continue; - } + { + let hit_rect_size = clamp(npc.hit_rect_size(), 1, 4); + let hit_rect_size = hit_rect_size * hit_rect_size; - // todo faster way to draw dynamic rectangles - // // top - // state.texture_set.draw_rect(Rect::new_size((npc.x - npc.hit_bounds.right as isize - self.frame.x) / 0x200, - // (npc.y - npc.hit_bounds.top as isize - self.frame.y) / 0x200, - // (npc.hit_bounds.right + npc.hit_bounds.right) as isize / 0x200, - // 1), - // [0.0, if npc.flags.hit_top_wall() { 1.0 } else { 0.0 }, 1.0, 1.0], ctx)?; - // // bottom - // state.texture_set.draw_rect(Rect::new_size((npc.x - npc.hit_bounds.right as isize - self.frame.x) / 0x200, - // (npc.y + npc.hit_bounds.bottom as isize - self.frame.y) / 0x200 - 1, - // (npc.hit_bounds.right + npc.hit_bounds.right) as isize / 0x200, - // 1), - // [0.0, if npc.flags.hit_bottom_wall() { 1.0 } else { 0.0 }, 1.0, 1.0], ctx)?; - // // left - // state.texture_set.draw_rect(Rect::new_size((npc.x - npc.hit_bounds.right as isize - self.frame.x) / 0x200, - // (npc.y - npc.hit_bounds.top as isize - self.frame.y) / 0x200, - // 1, - // (npc.hit_bounds.top + npc.hit_bounds.bottom) as isize / 0x200), - // [0.0, if npc.flags.hit_left_wall() { 1.0 } else { 0.0 }, 1.0, 1.0], ctx)?; - // // right - // state.texture_set.draw_rect(Rect::new_size((npc.x + npc.hit_bounds.right as isize - self.frame.x) / 0x200 - 1, - // (npc.y - npc.hit_bounds.top as isize - self.frame.y) / 0x200, - // 1, - // (npc.hit_bounds.top + npc.hit_bounds.bottom) as isize / 0x200), - // [0.0, if npc.flags.hit_right_wall() { 1.0 } else { 0.0 }, 1.0, 1.0], ctx)?; + let x = (npc.x + npc.offset_x()) / (16 * 0x200); + let y = (npc.y + npc.offset_y()) / (16 * 0x200); + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?; - { - let hit_rect_size = clamp(npc.hit_rect_size(), 1, 4); - let hit_rect_size = hit_rect_size * hit_rect_size; + let caret_rect = Rect::new_size(2, 74, 4, 4); + let caret2_rect = Rect::new_size(65, 9, 6, 6); - let x = (npc.x + npc.offset_x()) / (16 * 0x200); - let y = (npc.y + npc.offset_y()) / (16 * 0x200); - let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?; - - let caret_rect = Rect::new_size(2, 74, 4, 4); - let caret2_rect = Rect::new_size(65, 9, 6, 6); - - for (idx, (&ox, &oy)) in crate::physics::OFF_X.iter() - .zip(crate::physics::OFF_Y.iter()).enumerate() { - if idx == hit_rect_size { - break; - } - - batch.add_rect(((x + ox) * 16 - self.frame.x / 0x200) as f32 - 2.0, - ((y + oy) * 16 - self.frame.y / 0x200) as f32 - 2.0, - &caret_rect); + for (idx, (&ox, &oy)) in crate::physics::OFF_X.iter() + .zip(crate::physics::OFF_Y.iter()).enumerate() { + if idx == hit_rect_size { + break; } - - batch.add_rect(((npc.x - self.frame.x) / 0x200) as f32 - 3.0, - ((npc.y - self.frame.y) / 0x200) as f32 - 3.0, - &caret2_rect); - - batch.draw(ctx)?; + batch.add_rect(((x + ox) * 16 - self.frame.x / 0x200) as f32 - 2.0, + ((y + oy) * 16 - self.frame.y / 0x200) as f32 - 2.0, + &caret_rect); } - let text = format!("{}:{}:{}", npc.id, npc.npc_type, npc.size); - state.font.draw_colored_text(text.chars(), ((npc.x - self.frame.x) / 0x200) as f32, ((npc.y - self.frame.y) / 0x200) as f32, - (0, 255, (npc.id & 0xff) as u8), &state.constants, &mut state.texture_set, ctx)?; + + batch.add_rect(((npc.x - self.frame.x) / 0x200) as f32 - 3.0, + ((npc.y - self.frame.y) / 0x200) as f32 - 3.0, + &caret2_rect); + + batch.draw(ctx)?; } + + let text = format!("{}:{}:{}", npc.id, npc.npc_type, npc.action_num); + state.font.draw_colored_text(text.chars(), ((npc.x - self.frame.x) / 0x200) as f32, ((npc.y - self.frame.y) / 0x200) as f32, + ((npc.id & 0xf0) as u8, (npc.cond.0 >> 8) as u8, (npc.id & 0x0f << 4) as u8), + &state.constants, &mut state.texture_set, ctx)?; + + Ok(()) + } + + fn draw_debug_outlines(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + for npc in self.npc_map.npcs.values().map(|n| n.borrow()) { + self.draw_debug_npc(npc.deref(), state, ctx)?; + } + + for boss in self.npc_map.boss_map.parts.iter() { + self.draw_debug_npc(boss, state, ctx)?; + } + Ok(()) } } @@ -1386,21 +1374,21 @@ impl Scene for GameScene { self.draw_light_map(state, ctx)?; } + self.npc_map.boss_map.draw(state, ctx, &self.frame)?; for npc_id in self.npc_map.npc_ids.iter() { if let Some(npc_cell) = self.npc_map.npcs.get(npc_id) { let npc = npc_cell.borrow(); - if npc.x < (self.frame.x - 128 - npc.display_bounds.width() as isize * 0x200) - || npc.x > (self.frame.x + 128 + (state.canvas_size.0 as isize + npc.display_bounds.width() as isize) * 0x200) - && npc.y < (self.frame.y - 128 - npc.display_bounds.height() as isize * 0x200) - || npc.y > (self.frame.y + 128 + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200) { + if npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as isize * 0x200) + || npc.x > (self.frame.x + 128 * 0x200 + (state.canvas_size.0 as isize + npc.display_bounds.width() as isize) * 0x200) + && npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as isize * 0x200) + || npc.y > (self.frame.y + 128 * 0x200 + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200) { continue; } npc.draw(state, ctx, &self.frame)?; } } - self.npc_map.boss_map.draw(state, ctx, &self.frame)?; self.draw_bullets(state, ctx)?; self.player.draw(state, ctx, &self.frame)?; self.draw_tiles(state, ctx, TileLayer::Foreground)?; diff --git a/src/text_script.rs b/src/text_script.rs index 0a7cc6b..0002ef0 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -1150,7 +1150,7 @@ impl TextScriptVM { if let Some(npc_cell) = game_scene.npc_map.npcs.get(npc_id) { let npc = npc_cell.borrow(); - if event_num == npc.event_num { + if npc.cond.alive() && event_num == npc.event_num { game_scene.boss_life_bar.set_npc_target(npc.id, &game_scene.npc_map); break; }