1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-11-30 16:18:00 +00:00

Add complete implementation of Balfrog fight

This commit is contained in:
Alula 2020-11-04 10:06:02 +01:00
parent 30f47b17eb
commit c4a87f5a64
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
9 changed files with 676 additions and 106 deletions

View file

@ -230,6 +230,9 @@ pub struct NPCConsts {
pub n097_fan_up: [Rect<usize>; 3], pub n097_fan_up: [Rect<usize>; 3],
pub n098_fan_right: [Rect<usize>; 3], pub n098_fan_right: [Rect<usize>; 3],
pub n099_fan_down: [Rect<usize>; 3], pub n099_fan_down: [Rect<usize>; 3],
pub n104_frog: [Rect<usize>; 6],
pub n108_balfrog_projectile: [Rect<usize>; 3],
pub n110_puchi: [Rect<usize>; 6],
pub n111_quote_teleport_out: [Rect<usize>; 4], pub n111_quote_teleport_out: [Rect<usize>; 4],
pub n112_quote_teleport_in: [Rect<usize>; 4], pub n112_quote_teleport_in: [Rect<usize>; 4],
pub n129_fireball_snake_trail: [Rect<usize>; 18], pub n129_fireball_snake_trail: [Rect<usize>; 18],
@ -1299,6 +1302,27 @@ impl EngineConstants {
Rect { left: 288, top: 168, right: 304, bottom: 184 }, Rect { left: 288, top: 168, right: 304, bottom: 184 },
Rect { left: 304, top: 168, right: 320, 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: [ n111_quote_teleport_out: [
Rect { left: 0, top: 0, right: 16, bottom: 16 }, // left Rect { left: 0, top: 0, right: 16, bottom: 16 }, // left
Rect { left: 16, top: 0, right: 32, bottom: 16 }, Rect { left: 16, top: 0, right: 32, bottom: 16 },

View file

@ -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::boss::BossNPC;
use crate::npc::NPCMap;
use crate::player::Player; use crate::player::Player;
use crate::shared_game_state::SharedGameState; 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 { impl BossNPC {
pub(crate) fn tick_b02_balfrog(&mut self, state: &mut SharedGameState, player: &Player) { pub(crate) fn tick_b02_balfrog(&mut self, state: &mut SharedGameState, player: &Player) {
match self.parts[0].action_num { match self.parts[0].action_num {
@ -59,12 +87,12 @@ impl BossNPC {
} }
20 | 21 => { 20 | 21 => {
if self.parts[0].action_num == 20 { 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 = 0
} }
self.parts[0].action_counter += 1; 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; self.parts[0].anim_num = 3;
} else { } else {
self.parts[0].anim_num = 0; self.parts[0].anim_num = 0;
@ -138,7 +166,7 @@ impl BossNPC {
npc.cond.set_alive(true); npc.cond.set_alive(true);
npc.x = state.game_rng.range(4..16) as isize * 16 * 0x200; 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.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); state.new_npcs.push(npc);
@ -182,7 +210,7 @@ impl BossNPC {
if self.parts[0].anim_counter > 4 { if self.parts[0].anim_counter > 4 {
self.parts[0].action_num = 113; self.parts[0].action_num = 113;
self.parts[0].action_counter = 0; 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].anim_num = 3;
self.parts[0].target_x = self.parts[0].life as isize; self.parts[0].target_x = self.parts[0].life as isize;
self.parts[1].npc_flags.set_shootable(true); self.parts[1].npc_flags.set_shootable(true);
@ -205,24 +233,27 @@ impl BossNPC {
self.parts[0].action_counter += 1; self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 16 { if self.parts[0].action_counter > 16 {
self.parts[0].action_counter = 0; 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 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 // todo rand
let mut npc = NPCMap::create_npc(108, &state.npc_table); let mut npc = NPCMap::create_npc(108, &state.npc_table);
npc.cond.set_alive(true); npc.cond.set_alive(true);
npc.x = px; npc.x = self.parts[0].x + self.parts[0].direction.vector_x() * 2 * 16 * 0x200;
npc.y = py; npc.y = self.parts[0].y - 8 * 0x200;
npc.vel_x = (deg.cos() * 512.0) as isize; npc.vel_x = (deg.cos() * -512.0) as isize;
npc.vel_y = (deg.sin() * 512.0) as isize; npc.vel_y = (deg.sin() * -512.0) as isize;
state.new_npcs.push(npc);
state.sound_manager.play_sfx(39); 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_num = 114;
self.parts[0].action_counter = 0; self.parts[0].action_counter = 0;
self.parts[0].anim_num = 2; 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; self.parts[0].y += self.parts[0].vel_y;
let dir_offset = if self.parts[0].direction == Direction::Left { 0 } else { 9 }; 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]; 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;
}
_ => { }
}
} }
} }

View file

@ -29,12 +29,16 @@ pub struct BossNPC {
impl BossNPC { impl BossNPC {
pub fn new() -> BossNPC { pub fn new() -> BossNPC {
let mut part = NPC::empty(); let mut parts = [{
part.cond.set_drs_boss(true); let mut part = NPC::empty();
part.cond.set_drs_boss(true);
part
}; 16];
parts[0].cond.set_alive(true);
BossNPC { BossNPC {
boss_type: 0, boss_type: 0,
parts: [part; 16], parts,
hurt_sound: [0; 16], hurt_sound: [0; 16],
death_sound: [0; 16], death_sound: [0; 16],
} }
@ -43,6 +47,10 @@ impl BossNPC {
impl GameEntity<(&mut Player, &HashMap<u16, RefCell<NPC>>, &mut Stage)> for BossNPC { impl GameEntity<(&mut Player, &HashMap<u16, RefCell<NPC>>, &mut Stage)> for BossNPC {
fn tick(&mut self, state: &mut SharedGameState, (player, map, stage): (&mut Player, &HashMap<u16, RefCell<NPC>>, &mut Stage)) -> GameResult { fn tick(&mut self, state: &mut SharedGameState, (player, map, stage): (&mut Player, &HashMap<u16, RefCell<NPC>>, &mut Stage)) -> GameResult {
if !self.parts[0].cond.alive() {
return Ok(());
}
match self.boss_type { match self.boss_type {
1 => self.tick_b01_omega(), 1 => self.tick_b01_omega(),
2 => self.tick_b02_balfrog(state, player), 2 => self.tick_b02_balfrog(state, player),
@ -54,6 +62,12 @@ impl GameEntity<(&mut Player, &HashMap<u16, RefCell<NPC>>, &mut Stage)> for Boss
8 => self.tick_b09_ballos(), 8 => self.tick_b09_ballos(),
_ => {} _ => {}
} }
for part in self.parts.iter_mut() {
if part.shock > 0 {
part.shock -= 1;
}
}
Ok(()) Ok(())
} }

View file

@ -346,4 +346,244 @@ impl NPC {
Ok(()) 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(())
}
} }

View file

@ -308,6 +308,7 @@ impl NPC {
self.vel_x = self.direction.vector_x() * 0x100; self.vel_x = self.direction.vector_x() * 0x100;
} }
3 => { 3 => {
self.anim_num = 4;
self.vel_x = 0; self.vel_x = 0;
self.action_counter += 1; self.action_counter += 1;
@ -316,18 +317,16 @@ impl NPC {
self.action_num = 4; self.action_num = 4;
state.sound_manager.play_sfx(106); state.sound_manager.play_sfx(106);
} }
self.anim_num = 4;
} }
4 => { 4 => {
self.anim_num = 5;
self.damage = 10; self.damage = 10;
self.action_counter += 1; self.action_counter += 1;
if self.action_counter > 2 { if self.action_counter > 2 {
self.action_counter = 0; self.action_counter = 0;
self.action_num = 5; self.action_num = 5;
} }
self.anim_num = 5;
} }
5 => { 5 => {
self.action_counter += 1; self.action_counter += 1;

View file

@ -242,6 +242,9 @@ impl GameEntity<(&mut Player, &HashMap<u16, RefCell<NPC>>, &mut Stage)> for NPC
97 => self.tick_n097_fan_up(state, player), 97 => self.tick_n097_fan_up(state, player),
98 => self.tick_n098_fan_right(state, player), 98 => self.tick_n098_fan_right(state, player),
99 => self.tick_n099_fan_down(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), 111 => self.tick_n111_quote_teleport_out(state, player),
112 => self.tick_n112_quote_teleport_in(state, player), 112 => self.tick_n112_quote_teleport_in(state, player),
129 => self.tick_n129_fireball_snake_trail(state), 129 => self.tick_n129_fireball_snake_trail(state),
@ -574,7 +577,7 @@ impl NPCMap {
state.create_caret(x, y, CaretType::Explosion, Direction::Left); 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 { for id in list {
if let Some(npc_cell) = self.npcs.get(id) { if let Some(npc_cell) = self.npcs.get(id) {
let mut npc = npc_cell.borrow_mut(); 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() { if !state.new_npcs.is_empty() {
for mut npc in state.new_npcs.iter_mut() { for mut npc in state.new_npcs.iter_mut() {
let id = if npc.id == 0 { let id = if npc.id == 0 {
@ -662,6 +665,18 @@ impl NPCMap {
}; };
npc.id = id; 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.npc_ids.insert(id);
self.npcs.insert(id, RefCell::new(*npc)); self.npcs.insert(id, RefCell::new(*npc));
} }

View file

@ -7,7 +7,7 @@ use crate::common::{Condition, Direction, Flag, Rect};
use crate::inventory::{AddExperienceResult, Inventory}; use crate::inventory::{AddExperienceResult, Inventory};
use crate::npc::{NPC, NPCMap}; use crate::npc::{NPC, NPCMap};
use crate::physics::PhysicalEntity; use crate::physics::PhysicalEntity;
use crate::player::{Player, ControlMode}; use crate::player::{ControlMode, Player};
use crate::shared_game_state::SharedGameState; use crate::shared_game_state::SharedGameState;
impl PhysicalEntity for Player { impl PhysicalEntity for Player {
@ -241,7 +241,7 @@ impl Player {
flags = self.judge_hit_npc_non_solid(npc.borrow()); 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 { match npc.npc_type {
// experience pickup // experience pickup
1 => { 1 => {
@ -294,7 +294,14 @@ impl Player {
} }
if state.control_flags.control_enabled() && !npc.npc_flags.interactable() { 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); self.damage(npc.damage as isize, state);
} }
} }

View file

@ -1,3 +1,5 @@
use std::ops::Deref;
use itertools::Itertools; use itertools::Itertools;
use log::info; 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::graphics::{BlendMode, Color, Drawable, DrawParam, FilterMode, Vector2};
use crate::ggez::nalgebra::clamp; use crate::ggez::nalgebra::clamp;
use crate::inventory::{Inventory, TakeExperienceResult}; use crate::inventory::{Inventory, TakeExperienceResult};
use crate::npc::NPCMap; use crate::npc::{NPC, NPCMap};
use crate::physics::PhysicalEntity; use crate::physics::PhysicalEntity;
use crate::player::{Player, PlayerAppearance}; use crate::player::{Player, PlayerAppearance};
use crate::rng::RNG; use crate::rng::RNG;
@ -607,10 +609,10 @@ impl GameScene {
for npc_cell in self.npc_map.npcs.values() { for npc_cell in self.npc_map.npcs.values() {
let npc = npc_cell.borrow(); let npc = npc_cell.borrow();
if npc.x < (self.frame.x - 128 - npc.display_bounds.width() as isize * 0x200) if npc.x < (self.frame.x - 128 * 0x200 - 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.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 - npc.display_bounds.height() as isize * 0x200) && npc.y < (self.frame.y - 128 * 0x200 - 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) { || npc.y > (self.frame.y + 128 * 0x200 + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200) {
continue; continue;
} }
@ -971,7 +973,7 @@ impl GameScene {
if !dead_npcs.is_empty() { if !dead_npcs.is_empty() {
let missile = self.inventory.has_weapon(WeaponType::MissileLauncher) let missile = self.inventory.has_weapon(WeaponType::MissileLauncher)
| self.inventory.has_weapon(WeaponType::SuperMissileLauncher); | 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(); 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.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.npc_map.garbage_collect();
self.player.tick_map_collisions(state, &mut self.stage); 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() { 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.npc_map.garbage_collect();
self.tick_npc_bullet_collissions(state); 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); self.bullet_manager.tick_bullets(state, &self.player, &mut self.stage);
state.tick_carets(); state.tick_carets();
@ -1183,77 +1187,61 @@ impl GameScene {
Ok(()) Ok(())
} }
fn draw_debug_outlines(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { fn draw_debug_npc(&self, npc: &NPC, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
for npc in self.npc_map.npcs.values() { if npc.x < (self.frame.x - 128 - npc.display_bounds.width() as isize * 0x200)
let npc = npc.borrow(); || 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) let hit_rect_size = clamp(npc.hit_rect_size(), 1, 4);
&& npc.y < (self.frame.y - 128 - npc.display_bounds.height() as isize * 0x200) let hit_rect_size = hit_rect_size * hit_rect_size;
|| npc.y > (self.frame.y + 128 + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200) {
continue;
}
// todo faster way to draw dynamic rectangles let x = (npc.x + npc.offset_x()) / (16 * 0x200);
// // top let y = (npc.y + npc.offset_y()) / (16 * 0x200);
// state.texture_set.draw_rect(Rect::new_size((npc.x - npc.hit_bounds.right as isize - self.frame.x) / 0x200, let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?;
// (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 caret_rect = Rect::new_size(2, 74, 4, 4);
let hit_rect_size = clamp(npc.hit_rect_size(), 1, 4); let caret2_rect = Rect::new_size(65, 9, 6, 6);
let hit_rect_size = hit_rect_size * hit_rect_size;
let x = (npc.x + npc.offset_x()) / (16 * 0x200); for (idx, (&ox, &oy)) in crate::physics::OFF_X.iter()
let y = (npc.y + npc.offset_y()) / (16 * 0x200); .zip(crate::physics::OFF_Y.iter()).enumerate() {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Caret")?; if idx == hit_rect_size {
break;
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);
} }
batch.add_rect(((x + ox) * 16 - self.frame.x / 0x200) as f32 - 2.0,
batch.add_rect(((npc.x - self.frame.x) / 0x200) as f32 - 3.0, ((y + oy) * 16 - self.frame.y / 0x200) as f32 - 2.0,
((npc.y - self.frame.y) / 0x200) as f32 - 3.0, &caret_rect);
&caret2_rect);
batch.draw(ctx)?;
} }
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, batch.add_rect(((npc.x - self.frame.x) / 0x200) as f32 - 3.0,
(0, 255, (npc.id & 0xff) as u8), &state.constants, &mut state.texture_set, ctx)?; ((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(()) Ok(())
} }
} }
@ -1386,21 +1374,21 @@ impl Scene for GameScene {
self.draw_light_map(state, ctx)?; 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() { for npc_id in self.npc_map.npc_ids.iter() {
if let Some(npc_cell) = self.npc_map.npcs.get(npc_id) { if let Some(npc_cell) = self.npc_map.npcs.get(npc_id) {
let npc = npc_cell.borrow(); let npc = npc_cell.borrow();
if npc.x < (self.frame.x - 128 - npc.display_bounds.width() as isize * 0x200) if npc.x < (self.frame.x - 128 * 0x200 - 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.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 - npc.display_bounds.height() as isize * 0x200) && npc.y < (self.frame.y - 128 * 0x200 - 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) { || npc.y > (self.frame.y + 128 * 0x200 + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200) {
continue; continue;
} }
npc.draw(state, ctx, &self.frame)?; npc.draw(state, ctx, &self.frame)?;
} }
} }
self.npc_map.boss_map.draw(state, ctx, &self.frame)?;
self.draw_bullets(state, ctx)?; self.draw_bullets(state, ctx)?;
self.player.draw(state, ctx, &self.frame)?; self.player.draw(state, ctx, &self.frame)?;
self.draw_tiles(state, ctx, TileLayer::Foreground)?; self.draw_tiles(state, ctx, TileLayer::Foreground)?;

View file

@ -1150,7 +1150,7 @@ impl TextScriptVM {
if let Some(npc_cell) = game_scene.npc_map.npcs.get(npc_id) { if let Some(npc_cell) = game_scene.npc_map.npcs.get(npc_id) {
let npc = npc_cell.borrow(); 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); game_scene.boss_life_bar.set_npc_target(npc.id, &game_scene.npc_map);
break; break;
} }