doukutsu-rs/src/game/npc/boss/core.rs

709 lines
27 KiB
Rust

use crate::common::{CDEG_RAD, Direction};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
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.y - 0x800) > state.water_level {
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,
mut players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
) {
let mut flag = false;
// i will refactor that one day
#[allow(mutable_transmutes)]
let flash_counter: &mut u16 = unsafe { std::mem::transmute(&self.parts[19].action_counter3) };
match self.parts[0].action_num {
0 => {
self.parts[0].action_num = 10;
self.parts[0].exp = 1;
self.parts[0].cond.set_alive(true);
self.parts[0].npc_flags.0 = 0;
self.parts[0].npc_flags.set_show_damage(true);
self.parts[0].npc_flags.set_event_when_killed(true);
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_invulnerable(true);
self.parts[0].life = 650;
self.hurt_sound[0] = 114;
self.parts[0].x = 0x9a000;
self.parts[0].y = 0x1c000;
self.parts[0].vel_x = 0;
self.parts[0].vel_y = 0;
self.parts[0].event_num = 1000;
self.parts[4].cond.set_alive(true);
self.parts[4].action_num = 10;
self.parts[5].cond.set_alive(true);
self.parts[5].action_num = 10;
self.parts[8].cond.set_alive(true);
self.parts[8].npc_flags.0 = 0;
self.parts[8].npc_flags.set_ignore_solidity(true);
self.parts[8].npc_flags.set_invulnerable(true);
self.parts[8].display_bounds.left = 0;
self.parts[8].display_bounds.top = 0;
self.parts[8].hit_bounds.right = 0x5000;
self.parts[8].hit_bounds.top = 0x2000;
self.parts[8].hit_bounds.bottom = 0x2000;
self.parts[8].action_counter2 = 0;
self.parts[9] = self.parts[8].clone();
self.parts[9].hit_bounds.right = 0x4800;
self.parts[9].hit_bounds.top = 0x3000;
self.parts[9].hit_bounds.bottom = 0x3000;
self.parts[9].action_counter2 = 1;
self.parts[10] = self.parts[8].clone();
self.parts[10].hit_bounds.right = 0x5800;
self.parts[10].hit_bounds.top = 0x1000;
self.parts[10].hit_bounds.bottom = 0x1000;
self.parts[10].action_counter2 = 2;
self.parts[11] = self.parts[8].clone();
self.parts[11].cond.set_damage_boss(true);
self.parts[11].hit_bounds.right = 0x2800;
self.parts[11].hit_bounds.top = 0x2800;
self.parts[11].hit_bounds.bottom = 0x2800;
self.parts[11].action_counter2 = 3;
self.parts[1].cond.set_alive(true);
self.parts[1].action_num = 10;
self.parts[1].npc_flags.set_shootable(true);
self.parts[1].npc_flags.set_ignore_solidity(true);
self.parts[1].npc_flags.set_invulnerable(true);
self.parts[1].life = 1000;
self.hurt_sound[1] = 54;
self.parts[1].hit_bounds.right = 0x3000;
self.parts[1].hit_bounds.top = 0x2000;
self.parts[1].hit_bounds.bottom = 0x2000;
self.parts[1].display_bounds.top = 0x2800;
self.parts[1].display_bounds.left = 0x4000;
self.parts[1].x = self.parts[0].x - 0x1000;
self.parts[1].y = self.parts[0].y - 0x8000;
self.parts[2] = self.parts[1].clone();
self.parts[2].x = self.parts[0].x + 0x2000;
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].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].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].y = self.parts[0].y + 0x4000;
for part in &mut self.parts {
part.prev_x = part.x;
part.prev_y = part.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 += 1;
state.sound_manager.play_sfx(115);
if self.parts[0].action_counter2 < 4 {
self.parts[0].action_num = 210;
} else {
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;
}
}
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_counter3 = self.parts[0].life;
self.parts[11].npc_flags.set_shootable(true);
}
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;
if (*flash_counter & 2) != 0 {
self.parts[4].anim_num = 0;
self.parts[5].anim_num = 0;
} else {
self.parts[4].anim_num = 1;
self.parts[5].anim_num = 1;
}
} else {
self.parts[4].anim_num = 0;
self.parts[5].anim_num = 0;
}
self.parts[0].action_counter += 1;
if self.parts[0].action_counter % 100 == 1 {
state.npc_curly_counter = self.parts[0].rng.range(80..100) as u16;
state.npc_curly_target = (self.parts[11].x, self.parts[11].y)
}
if self.parts[0].action_counter < 200 && self.parts[0].action_counter % 20 == 1 {
let mut npc = NPC::create(179, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.parts[0].x + self.parts[0].rng.range(-48..-16) * 0x200;
npc.y = self.parts[0].y + self.parts[0].rng.range(-64..64) * 0x200;
let _ = npc_list.spawn(0x100, npc);
}
if self.parts[0].action_counter > 400
|| (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;
self.parts[5].anim_num = 0;
flag = true;
}
}
220 | 221 => {
if self.parts[0].action_num == 220 {
self.parts[0].action_num = 221;
self.parts[0].action_counter = 0;
self.parts[11].npc_flags.set_shootable(true);
state.sound_manager.loop_sfx_freq(40, 1000.0 / 2205.0);
state.sound_manager.loop_sfx_freq(41, 1100.0 / 2205.0);
state.quake_counter = 100;
state.quake_rumble_counter = 100;
state.npc_super_pos.1 = 1;
}
self.parts[0].action_counter += 1;
let idx = self.parts[0].get_closest_player_idx_mut(&players);
let mut npc = NPC::create(199, &state.npc_table);
npc.cond.set_alive(true);
npc.x = players[idx].x + self.parts[0].rng.range(-50..150) * 0x400;
npc.y = players[idx].y + self.parts[0].rng.range(-160..160) * 0x200;
let _ = npc_list.spawn(0x100, npc);
for player in players.iter_mut() {
player.vel_x -= 0x20;
player.cond.set_increase_acceleration(true);
}
if self.parts[0].shock > 0 {
*flash_counter += 1;
if (*flash_counter & 2) != 0 {
self.parts[4].anim_num = 0;
self.parts[5].anim_num = 0;
} else {
self.parts[4].anim_num = 1;
self.parts[5].anim_num = 1;
}
} else {
self.parts[4].anim_num = 0;
self.parts[5].anim_num = 0;
}
if [300, 350, 400].contains(&self.parts[0].action_counter) {
state.sound_manager.play_sfx(101);
let mut npc = NPC::create(218, &state.npc_table);
let angle = f64::atan2(
(self.parts[0].y - players[idx].y) as f64,
(self.parts[0].x - players[idx].x) as f64,
);
npc.cond.set_alive(true);
npc.x = self.parts[0].x - 0x5000;
npc.y = self.parts[0].y;
npc.vel_x = (angle.cos() * -1536.0) as i32;
npc.vel_y = (angle.sin() * -1536.0) as i32;
let _ = npc_list.spawn(0x100, npc);
} else if self.parts[0].action_counter > 400 {
self.parts[0].action_num = 200;
self.parts[4].anim_num = 2;
self.parts[5].anim_num = 0;
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;
state.quake_rumble_counter = 20;
state.sound_manager.stop_sfx(40);
state.sound_manager.stop_sfx(41);
state.sound_manager.stop_sfx(58);
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);
}
self.parts[0].x += if self.parts[0].action_counter & 0x02 == 0 { 0x200 } else { -0x200 };
self.parts[0].x += if self.parts[0].x < 0x7E000 { 0x80 } else { -0x80 };
self.parts[0].y += if self.parts[0].y < 0x16000 { 0x80 } else { -0x80 };
}
600 | 601 => {
if self.parts[0].action_num == 600 {
self.parts[0].action_num = 601;
self.parts[4].action_num = 50;
self.parts[5].action_num = 50;
self.parts[8].npc_flags.set_invulnerable(false);
self.parts[9].npc_flags.set_invulnerable(false);
self.parts[10].npc_flags.set_invulnerable(false);
self.parts[11].npc_flags.set_invulnerable(false);
}
self.parts[0].action_counter += 1;
self.parts[0].x += if self.parts[0].action_counter & 0x02 == 0 { 0x800 } else { -0x800 };
}
_ => {}
}
if flag {
state.quake_counter = 20;
state.quake_rumble_counter = 20;
state.sound_manager.play_sfx(26);
self.parts[1].action_num = 100;
self.parts[2].action_num = 100;
self.parts[3].action_num = 100;
self.parts[6].action_num = 100;
self.parts[7].action_num = 100;
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.y = self.parts[4].y;
for _ in 0..8 {
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);
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 == 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;
}
if self.parts[0].x < self.parts[0].target_x + 0x14000 {
self.parts[0].vel_x += 4;
}
if self.parts[0].x > self.parts[0].target_x + 0x14000 {
self.parts[0].vel_x -= 4;
}
if self.parts[0].y < self.parts[0].target_y {
self.parts[0].vel_y += 4;
}
if self.parts[0].y > self.parts[0].target_y {
self.parts[0].vel_y -= 4;
}
}
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);
self.tick_b04_core_hitbox(9);
self.tick_b04_core_hitbox(10);
self.tick_b04_core_hitbox(11);
}
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;
}
_ => {}
}
part.anim_rect = state.constants.npc.b04_core[part.anim_num as usize];
if part.action_num == 51 {
part.anim_rect.bottom = part.action_counter + part.anim_rect.top;
}
}
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;
}
_ => {}
}
part.anim_rect = state.constants.npc.b04_core[4 + part.anim_num as usize];
if part.action_num == 51 {
part.anim_rect.bottom = part.action_counter + part.anim_rect.top;
}
}
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) {
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;
}
_ => (),
}
}
}