mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-03-23 18:39:20 +00:00
boss life bar, textscript opcodes and etc.
This commit is contained in:
parent
6964b8d167
commit
30f47b17eb
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
122
src/components/boss_life_bar.rs
Normal file
122
src/components/boss_life_bar.rs
Normal file
|
@ -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(())
|
||||
}
|
||||
}
|
1
src/components/mod.rs
Normal file
1
src/components/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod boss_life_bar;
|
|
@ -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)?;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<u16, RefCell<NPC>>, &mut Stage)> for Boss
|
|||
fn tick(&mut self, state: &mut SharedGameState, (player, map, stage): (&mut Player, &HashMap<u16, RefCell<NPC>>, &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(),
|
||||
|
|
|
@ -112,7 +112,7 @@ pub struct NPC {
|
|||
pub anim_rect: Rect<usize>,
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)?;
|
||||
|
|
Loading…
Reference in a new issue