From 30f47b17eb56c04c724eba43d66388d7f166ddbd Mon Sep 17 00:00:00 2001 From: Alula Date: Mon, 2 Nov 2020 15:01:30 +0100 Subject: [PATCH] boss life bar, textscript opcodes and etc. --- src/common.rs | 1 + src/components/boss_life_bar.rs | 122 +++++++++++++++++++++ src/components/mod.rs | 1 + src/ggez/graphics/spritebatch.rs | 4 + src/lib.rs | 3 +- src/npc/boss/balfrog.rs | 180 ++++++++++++++++++++++++++++++- src/npc/boss/mod.rs | 19 ++-- src/npc/mod.rs | 14 ++- src/player.rs | 2 +- src/scene/game_scene.rs | 107 +++++++++++++++++- src/text_script.rs | 23 +++- 11 files changed, 457 insertions(+), 19 deletions(-) create mode 100644 src/components/boss_life_bar.rs create mode 100644 src/components/mod.rs diff --git a/src/common.rs b/src/common.rs index f93fd72..1b73352 100644 --- a/src/common.rs +++ b/src/common.rs @@ -76,6 +76,7 @@ bitfield! { pub alive, set_alive: 7; // 0x80 // engine specific flags + pub drs_boss, set_drs_boss: 14; pub drs_destroyed, set_drs_destroyed: 15; } diff --git a/src/components/boss_life_bar.rs b/src/components/boss_life_bar.rs new file mode 100644 index 0000000..9cf38cb --- /dev/null +++ b/src/components/boss_life_bar.rs @@ -0,0 +1,122 @@ +use crate::entity::GameEntity; +use crate::frame::Frame; +use crate::ggez::{Context, GameResult}; +use crate::npc::NPCMap; +use crate::shared_game_state::SharedGameState; +use crate::common::Rect; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +enum BossLifeTarget { + None, + NPC(u16), + Boss, +} + +pub struct BossLifeBar { + target: BossLifeTarget, + life: u16, + max_life: u16, + prev_life: u16, + counter: u16, +} + +impl BossLifeBar { + pub fn new() -> BossLifeBar { + BossLifeBar { + target: BossLifeTarget::None, + life: 0, + max_life: 0, + prev_life: 0, + counter: 0, + } + } + + pub fn set_npc_target(&mut self, npc_id: u16, npc_map: &NPCMap) { + if let Some(npc_cell) = npc_map.npcs.get(&npc_id) { + let npc = npc_cell.borrow(); + + self.target = BossLifeTarget::NPC(npc.id); + self.life = npc.life; + self.max_life = self.life; + self.prev_life = self.life; + } + } + + pub fn set_boss_target(&mut self, npc_map: &NPCMap) { + self.target = BossLifeTarget::Boss; + self.life = npc_map.boss_map.parts[0].life; + self.max_life = self.life; + self.prev_life = self.life; + } +} + +impl GameEntity<&NPCMap> for BossLifeBar { + fn tick(&mut self, state: &mut SharedGameState, npc_map: &NPCMap) -> GameResult<()> { + match self.target { + BossLifeTarget::NPC(npc_id) => { + if let Some(npc_cell) = npc_map.npcs.get(&npc_id) { + let npc = npc_cell.borrow(); + + self.life = npc.life; + } + } + BossLifeTarget::Boss => { + self.life = npc_map.boss_map.parts[0].life; + } + _ => { + return Ok(()); + } + } + + if self.life == 0 { + self.target = BossLifeTarget::None; + } else if self.prev_life > self.life { + self.counter += 1; + if self.counter > 30 { + self.prev_life = self.prev_life.saturating_sub(1); + } + } else { + self.counter = 0; + } + + Ok(()) + } + + fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult<()> { + if self.max_life == 0 || self.target == BossLifeTarget::None { + return Ok(()); + } + + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?; + + let box_length = 256; + let bar_length = box_length - 58; + + let text_rect = Rect::new_size(0, 48, 32, 8); + let box_rect1 = Rect::new_size(0, 0, 244, 8); + let box_rect2 = Rect::new_size(0, 16, 244, 8); + let mut rect_prev_bar = Rect::new_size(0, 32, 232, 8); + let mut rect_life_bar = Rect::new_size(0, 24, 232, 8); + + rect_prev_bar.right = ((self.prev_life as usize * bar_length) / self.max_life as usize).min(bar_length); + rect_life_bar.right = ((self.life as usize * bar_length) / self.max_life as usize).min(bar_length); + + batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(), + state.canvas_size.1 - 20.0, &box_rect1); + batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(), + state.canvas_size.1 - 12.0, &box_rect2); + batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(), + state.canvas_size.1 - 20.0, &box_rect1); + batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(), + state.canvas_size.1 - 16.0, &rect_prev_bar); + batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0 + 40.0).floor(), + state.canvas_size.1 - 16.0, &rect_life_bar); + batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0 + 8.0).floor(), + state.canvas_size.1 - 16.0, &text_rect); + + batch.draw(ctx)?; + + Ok(()) + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..172bda2 --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1 @@ +pub mod boss_life_bar; diff --git a/src/ggez/graphics/spritebatch.rs b/src/ggez/graphics/spritebatch.rs index 125c80f..a457f5e 100644 --- a/src/ggez/graphics/spritebatch.rs +++ b/src/ggez/graphics/spritebatch.rs @@ -155,6 +155,10 @@ impl SpriteBatch { impl graphics::Drawable for SpriteBatch { fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult { + if self.sprites.is_empty() { + return Ok(()); + } + // Awkwardly we must update values on all sprites and such. // Also awkwardly we have this chain of colors with differing priorities. self.flush(ctx, &self.image)?; diff --git a/src/lib.rs b/src/lib.rs index e1e9478..f8a75dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ use crate::ggez::conf::{Backend, WindowMode, WindowSetup}; use crate::ggez::event::{KeyCode, KeyMods}; use crate::ggez::graphics; use crate::ggez::graphics::{Canvas, DrawParam, window}; +use crate::ggez::graphics::glutin_ext::WindowUpdateExt; use crate::ggez::input::keyboard; use crate::ggez::mint::ColumnMatrix4; use crate::ggez::nalgebra::Vector2; @@ -34,7 +35,6 @@ use crate::scene::loading_scene::LoadingScene; use crate::scene::Scene; use crate::shared_game_state::{SharedGameState, TimingMode}; use crate::ui::UI; -use crate::ggez::graphics::glutin_ext::WindowUpdateExt; mod bmfont; mod bmfont_renderer; @@ -42,6 +42,7 @@ mod builtin_fs; mod bullet; mod caret; mod common; +mod components; mod encoding; mod engine_constants; mod entity; diff --git a/src/npc/boss/balfrog.rs b/src/npc/boss/balfrog.rs index 60cc101..83516b5 100644 --- a/src/npc/boss/balfrog.rs +++ b/src/npc/boss/balfrog.rs @@ -1,12 +1,14 @@ use crate::common::{Direction, Rect}; use crate::npc::boss::BossNPC; use crate::npc::NPCMap; +use crate::player::Player; use crate::shared_game_state::SharedGameState; impl BossNPC { - pub(crate) fn tick_b02_balfrog(&mut self, state: &mut SharedGameState) { + pub(crate) fn tick_b02_balfrog(&mut self, state: &mut SharedGameState, player: &Player) { match self.parts[0].action_num { 0 => { + self.hurt_sound[0] = 52; self.parts[0].x = 6 * 16 * 0x200; self.parts[0].y = 12 * 16 * 0x200; self.parts[0].direction = Direction::Right; @@ -68,6 +70,182 @@ impl BossNPC { self.parts[0].anim_num = 0; } } + 100 | 101 => { + if self.parts[0].action_num == 100 { + self.parts[0].action_num = 101; + 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 = 102; + self.parts[0].anim_counter = 0; + self.parts[0].anim_num = 2; + } + } + 102 => { + self.parts[0].anim_counter += 1; + + if self.parts[0].anim_counter > 10 { + self.parts[0].action_num = 103; + self.parts[0].anim_counter = 0; + self.parts[0].anim_num = 1; + } + } + 103 => { + self.parts[0].anim_counter += 1; + if self.parts[0].anim_counter > 4 { + self.parts[0].action_num = 104; + self.parts[0].anim_num = 5; + self.parts[0].vel_x = self.parts[0].direction.vector_x() * 0x200; + self.parts[0].vel_y = -2 * 0x200; + self.parts[0].display_bounds.top = 64 * 0x200; + self.parts[0].display_bounds.bottom = 24 * 0x200; + + state.sound_manager.play_sfx(25); + } + } + 104 => { + if self.parts[0].direction == Direction::Left && self.parts[0].flags.hit_left_wall() { + self.parts[0].direction = Direction::Right; + self.parts[0].vel_x = 0x200; + } + + if self.parts[0].direction == Direction::Right && self.parts[0].flags.hit_right_wall() { + self.parts[0].direction = Direction::Left; + self.parts[0].vel_x = -0x200; + } + + 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; + + if self.parts[0].direction == Direction::Left && self.parts[0].x < player.x { + self.parts[0].direction = Direction::Right; + self.parts[0].action_num = 110; + } + + if self.parts[0].direction == Direction::Right && self.parts[0].x > player.x { + self.parts[0].direction = Direction::Left; + self.parts[0].action_num = 110; + } + + let mut npc = NPCMap::create_npc(110, &state.npc_table); + 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 }; + + state.new_npcs.push(npc); + + let mut npc = NPCMap::create_npc(4, &state.npc_table); + + for _ in 0..4 { + npc.cond.set_alive(true); + npc.direction = Direction::Left; + 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; + + state.new_npcs.push(npc); + } + + state.quake_counter = 30; + state.sound_manager.play_sfx(26); + } + } + 110 | 111 => { + if self.parts[0].action_num == 110 { + self.parts[0].anim_num = 1; + self.parts[0].action_num = 111; + self.parts[0].action_counter = 0; + } + + self.parts[0].action_counter += 1; + + self.parts[0].vel_x = self.parts[0].vel_x * 8 / 9; + + if self.parts[0].action_counter > 50 { + self.parts[0].anim_num = 2; + self.parts[0].anim_counter = 0; + self.parts[0].action_num = 112; + } + } + 112 => { + self.parts[0].anim_counter += 1; + + 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].anim_num = 3; + self.parts[0].target_x = self.parts[0].life as isize; + self.parts[1].npc_flags.set_shootable(true); + } + } + 113 => { + if self.parts[0].shock != 0 { + if self.parts[0].action_counter2 / 2 % 2 != 0 { + self.parts[0].anim_num = 4; + } else { + self.parts[0].anim_num = 3; + } + } else { + self.parts[0].action_counter2 = 0; + self.parts[0].anim_num = 3; + } + + self.parts[0].vel_x = self.parts[0].vel_x * 10 / 11; + + 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); + + let px = self.parts[0].x + self.parts[0].direction.vector_x() * 2 * 16 * 0x200; + let py = self.parts[0].y - 8 * 0x200 - player.y; + + let deg = f64::atan2(py as f64, px as f64); + // 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; + + 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 { + self.parts[0].action_num = 114; + self.parts[0].action_counter = 0; + self.parts[0].anim_num = 2; + self.parts[0].anim_counter = 0; + self.parts[1].npc_flags.set_shootable(false); + } + } + } + 114 => { + self.parts[0].anim_counter += 1; + if self.parts[0].anim_counter > 10 { + self.parts[0].anim_num = 1; + self.parts[0].anim_counter = 0; + + self.parts[1].action_counter2 += 1; + if self.parts[1].action_counter2 > 2 { + self.parts[1].action_counter2 = 0; + self.parts[0].action_num = 120; + } else { + self.parts[0].action_num = 100; + } + } + } _ => {} } diff --git a/src/npc/boss/mod.rs b/src/npc/boss/mod.rs index 5c47359..c3c28ce 100644 --- a/src/npc/boss/mod.rs +++ b/src/npc/boss/mod.rs @@ -1,14 +1,14 @@ use std::cell::RefCell; use std::collections::HashMap; -use crate::ggez::{GameResult, Context}; +use crate::common::{Direction, interpolate_fix9_scale}; +use crate::entity::GameEntity; +use crate::frame::Frame; +use crate::ggez::{Context, GameResult}; use crate::npc::NPC; use crate::player::Player; use crate::shared_game_state::SharedGameState; use crate::stage::Stage; -use crate::entity::GameEntity; -use crate::frame::Frame; -use crate::common::{Direction, interpolate_fix9_scale}; pub mod balfrog; pub mod ballos; @@ -23,13 +23,20 @@ pub mod undead_core; pub struct BossNPC { pub boss_type: u16, pub parts: [NPC; 16], + pub hurt_sound: [u8; 16], + pub death_sound: [u8; 16], } impl BossNPC { pub fn new() -> BossNPC { + let mut part = NPC::empty(); + part.cond.set_drs_boss(true); + BossNPC { boss_type: 0, - parts: [NPC::empty(); 16], + parts: [part; 16], + hurt_sound: [0; 16], + death_sound: [0; 16], } } } @@ -38,7 +45,7 @@ impl GameEntity<(&mut Player, &HashMap>, &mut Stage)> for Boss fn tick(&mut self, state: &mut SharedGameState, (player, map, stage): (&mut Player, &HashMap>, &mut Stage)) -> GameResult { match self.boss_type { 1 => self.tick_b01_omega(), - 2 => self.tick_b02_balfrog(state), + 2 => self.tick_b02_balfrog(state, player), 3 => self.tick_b03_monster_x(), 4 => self.tick_b04_core(), 5 => self.tick_b05_ironhead(), diff --git a/src/npc/mod.rs b/src/npc/mod.rs index 12d427e..a4e8880 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -112,7 +112,7 @@ pub struct NPC { pub anim_rect: Rect, } -static PARTICLE_NPCS: [u16; 9] = [1, 4, 73, 84, 86, 87, 129, 199, 355]; +static PARTICLE_NPCS: [u16; 10] = [1, 4, 73, 84, 86, 87, 108, 129, 199, 355]; impl NPC { pub fn get_start_index(&self) -> u16 { @@ -314,13 +314,19 @@ impl PhysicalEntity for NPC { fn vel_y(&self) -> isize { self.vel_y } #[inline(always)] - fn hit_rect_size(&self) -> usize { if self.size >= 3 { 3 } else { 2 } } + fn hit_rect_size(&self) -> usize { + if self.size >= 3 { + if self.cond.drs_boss() { 4 } else { 3 } + } else { + 2 + } + } #[inline(always)] - fn offset_x(&self) -> isize { if self.size >= 3 { -0x1000 } else { 0 } } + fn offset_x(&self) -> isize { if self.size >= 3 && !self.cond.drs_boss() { -0x1000 } else { 0 } } #[inline(always)] - fn offset_y(&self) -> isize { if self.size >= 3 { -0x1000 } else { 0 } } + fn offset_y(&self) -> isize { if self.size >= 3 && !self.cond.drs_boss() { -0x1000 } else { 0 } } #[inline(always)] fn hit_bounds(&self) -> &Rect { diff --git a/src/player.rs b/src/player.rs index b69fe8e..0ed9c65 100644 --- a/src/player.rs +++ b/src/player.rs @@ -19,7 +19,7 @@ pub enum ControlMode { IronHead, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] /// Cave Story+ player skins pub enum PlayerAppearance { diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 914dbd6..587870d 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -4,6 +4,7 @@ use log::info; use crate::bullet::BulletManager; use crate::caret::CaretType; use crate::common::{Direction, FadeDirection, FadeState, fix9_scale, Rect}; +use crate::components::boss_life_bar::BossLifeBar; use crate::entity::GameEntity; use crate::frame::{Frame, UpdateTarget}; use crate::ggez::{Context, GameResult, graphics, timer}; @@ -16,7 +17,7 @@ use crate::player::{Player, PlayerAppearance}; use crate::rng::RNG; use crate::scene::Scene; use crate::scene::title_scene::TitleScene; -use crate::shared_game_state::{SharedGameState, Season}; +use crate::shared_game_state::{Season, SharedGameState}; use crate::stage::{BackgroundType, Stage}; use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptVM}; use crate::texture_set::SizedBatch; @@ -26,6 +27,7 @@ use crate::weapon::WeaponType; pub struct GameScene { pub tick: usize, pub stage: Stage, + pub boss_life_bar: BossLifeBar, pub frame: Frame, pub player: Player, pub inventory: Inventory, @@ -74,6 +76,7 @@ impl GameScene { stage, player: Player::new(state), inventory: Inventory::new(), + boss_life_bar: BossLifeBar::new(), frame: Frame { x: 0, y: 0, @@ -800,7 +803,7 @@ impl GameScene { } for bullet in self.bullet_manager.bullets.iter_mut() { - if bullet.damage < 1 { + if !bullet.cond.alive() || bullet.damage < 1 { continue; } @@ -823,7 +826,6 @@ impl GameScene { } if npc.npc_flags.shootable() { - log::info!("damage: {} {}", npc.life, -(bullet.damage.min(npc.life) as isize)); npc.life = npc.life.saturating_sub(bullet.damage); if npc.life == 0 { @@ -843,7 +845,12 @@ impl GameScene { if let Some(table_entry) = state.npc_table.get_entry(npc.npc_type) { state.sound_manager.play_sfx(table_entry.hurt_sound); } + npc.shock = 16; + + for _ in 0..3 { + state.create_caret((bullet.x + npc.x) / 2, (bullet.y + npc.y) / 2, CaretType::HurtParticles, Direction::Left); + } } if npc.npc_flags.show_damage() { @@ -872,6 +879,95 @@ impl GameScene { } } + for i in 0..self.npc_map.boss_map.parts.len() { + let mut idx = i; + let (mut destroy_x, mut destroy_y, mut destroy_radius, mut destroy_count) = (0, 0, 0, 0); + let mut npc = unsafe { self.npc_map.boss_map.parts.get_unchecked_mut(i) }; + if !npc.cond.alive() { + continue; + } + + for bullet in self.bullet_manager.bullets.iter_mut() { + if !bullet.cond.alive() || bullet.damage < 1 { + continue; + } + + let hit = ( + npc.npc_flags.shootable() + && (npc.x - npc.hit_bounds.right as isize) < (bullet.x + bullet.enemy_hit_width as isize) + && (npc.x + npc.hit_bounds.right as isize) > (bullet.x - bullet.enemy_hit_width as isize) + && (npc.y - npc.hit_bounds.top as isize) < (bullet.y + bullet.enemy_hit_height as isize) + && (npc.y + npc.hit_bounds.bottom as isize) > (bullet.y - bullet.enemy_hit_height as isize) + ) || ( + npc.npc_flags.invulnerable() + && (npc.x - npc.hit_bounds.right as isize) < (bullet.x + bullet.hit_bounds.right as isize) + && (npc.x + npc.hit_bounds.right as isize) > (bullet.x - bullet.hit_bounds.left as isize) + && (npc.y - npc.hit_bounds.top as isize) < (bullet.y + bullet.hit_bounds.bottom as isize) + && (npc.y + npc.hit_bounds.bottom as isize) > (bullet.y - bullet.hit_bounds.top as isize) + ); + + if !hit { + continue; + } + + if npc.npc_flags.shootable() { + if npc.cond.damage_boss() { + idx = 0; + npc = unsafe { self.npc_map.boss_map.parts.get_unchecked_mut(0) }; + } + + npc.life = npc.life.saturating_sub(bullet.damage); + + if npc.life == 0 { + npc.life = npc.id; + + if self.player.cond.alive() && npc.npc_flags.event_when_killed() { + state.control_flags.set_tick_world(true); + state.control_flags.set_interactions_disabled(true); + state.textscript_vm.start_script(npc.event_num); + } else { + state.sound_manager.play_sfx(self.npc_map.boss_map.death_sound[idx]); + + destroy_x = npc.x; + destroy_y = npc.y; + destroy_radius = npc.display_bounds.right; + destroy_count = 4usize * (2usize).pow((npc.size as u32).saturating_sub(1)); + + npc.cond.set_alive(false); + } + } else { + if npc.shock < 14 { + for _ in 0..3 { + state.create_caret(bullet.x, bullet.y, CaretType::HurtParticles, Direction::Left); + } + state.sound_manager.play_sfx(self.npc_map.boss_map.hurt_sound[idx]); + } + + npc.shock = 8; + + npc = unsafe { self.npc_map.boss_map.parts.get_unchecked_mut(0) }; + npc.shock = 8; + } + + bullet.life = bullet.life.saturating_sub(1); + if bullet.life < 1 { + bullet.cond.set_alive(false); + } + } else if [13, 14, 15, 28, 29, 30].contains(&bullet.btype) { + bullet.life = bullet.life.saturating_sub(1); + } else if !bullet.weapon_flags.hit_right_slope() { + state.create_caret(bullet.x, bullet.y, CaretType::ProjectileDissipation, Direction::Right); + state.sound_manager.play_sfx(31); + bullet.life = 0; + continue; + } + } + + if destroy_count != 0 { + self.npc_map.create_death_effect(destroy_x, destroy_y, destroy_radius, destroy_count, state); + } + } + if !dead_npcs.is_empty() { let missile = self.inventory.has_weapon(WeaponType::MissileLauncher) | self.inventory.has_weapon(WeaponType::SuperMissileLauncher); @@ -998,6 +1094,8 @@ impl GameScene { } else { self.life_bar_counter = 0; } + + self.boss_life_bar.tick(state, &self.npc_map)?; } Ok(()) @@ -1141,7 +1239,7 @@ impl GameScene { 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); + &caret_rect); } @@ -1317,6 +1415,7 @@ impl Scene for GameScene { if state.control_flags.control_enabled() { self.draw_hud(state, ctx)?; + self.boss_life_bar.draw(state, ctx, &self.frame)?; } if state.textscript_vm.mode == ScriptMode::StageSelect { diff --git a/src/text_script.rs b/src/text_script.rs index b4a577c..0a7cc6b 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -1140,6 +1140,26 @@ impl TextScriptVM { exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } + OpCode::BSL => { + let event_num = read_cur_varint(&mut cursor)? as u16; + + if event_num == 0 { + game_scene.boss_life_bar.set_boss_target(&game_scene.npc_map); + } else { + for npc_id in game_scene.npc_map.npc_ids.iter() { + if let Some(npc_cell) = game_scene.npc_map.npcs.get(npc_id) { + let npc = npc_cell.borrow(); + + if event_num == npc.event_num { + game_scene.boss_life_bar.set_npc_target(npc.id, &game_scene.npc_map); + break; + } + } + } + } + + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } OpCode::BOA => { let action_num = read_cur_varint(&mut cursor)? as u16; @@ -1407,8 +1427,7 @@ impl TextScriptVM { exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } // One operand codes - OpCode::BSL | OpCode::NUM | - OpCode::MPp | OpCode::SKm | OpCode::SKp | + OpCode::NUM | OpCode::MPp | OpCode::SKm | OpCode::SKp | OpCode::UNJ | OpCode::MPJ | OpCode::XX1 | OpCode::SIL | OpCode::SSS | OpCode::ACH | OpCode::S2MV => { let par_a = read_cur_varint(&mut cursor)?;