add Monster X and some tweaks

This commit is contained in:
Alula 2020-12-15 23:09:14 +01:00
parent ff0046abbd
commit 091892b339
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
14 changed files with 931 additions and 47 deletions

View File

@ -61,14 +61,19 @@ Because methods used to extract game data from cartridge vary, you have to find
- [x] PixTone SFX
- [ ] NPCs/entities
- [x] Initial implementation
- [ ] Miscellaneous entities (~30% done)
- [ ] Bosses (~20% done)
- [ ] Miscellaneous entities (~40% done)
- [ ] Bosses (~30% done)
- [x] Omega
- [x] Balfrog
- [x] Monster X
- [x] First Cave
- [x] Mimiga Village
- [x] Egg Corridor
- [x] Grasstown
- [ ] Sand Zone (~30% done)
- [ ] Sand Zone (~50% done)
- [ ] Labirynth (~10% done)
- [x] Waterway
- [ ] Egg Corridor? (~20% done)
- [ ] Outer Wall
- [ ] Plantation
- [ ] Last Cave

View File

@ -43,8 +43,8 @@ impl BulletManager {
self.bullets.iter().filter(|b| b.owner == player_id && b.btype == btype).count()
}
pub fn count_bullets_all(&self, btype: u16) -> usize {
self.bullets.iter().filter(|b| b.btype == btype).count()
pub fn count_bullets_type_idx_all(&self, type_idx: u16) -> usize {
self.bullets.iter().filter(|b| (b.btype.saturating_sub(2) / 3) == type_idx).count()
}
pub fn count_bullets_multi(&self, btypes: [u16; 3], player_id: TargetPlayer) -> usize {

View File

@ -1,7 +1,7 @@
use std::fs::read_to_string;
use crate::bitfield;
use crate::common::{Condition, Direction, Rect, CDEG_RAD};
use crate::common::{CDEG_RAD, Condition, Direction, Rect};
use crate::engine_constants::EngineConstants;
use crate::rng::RNG;
@ -173,11 +173,11 @@ impl Caret {
}
match self.direction {
Direction::Left => self.x -= 0x400, // 2.0fix9
Direction::Up => self.y -= 0x400,
Direction::Right => self.x += 0x400,
Direction::Bottom => self.y += 0x400,
Direction::FacingPlayer => unreachable!(),
Direction::Left => self.x -= 0x400, // 2.0fix9
Direction::Up => self.y -= 0x400,
Direction::Right => self.x += 0x400,
Direction::Bottom => self.y += 0x400,
Direction::FacingPlayer => {},
}
}
CaretType::DrownedQuote => {
@ -185,8 +185,8 @@ impl Caret {
self.anim_counter = 1;
match self.direction {
Direction::Left => self.anim_rect = constants.caret.drowned_quote_left_rect,
Direction::Right => self.anim_rect = constants.caret.drowned_quote_right_rect,
Direction::Left => self.anim_rect = constants.caret.drowned_quote_left_rect,
Direction::Right => self.anim_rect = constants.caret.drowned_quote_right_rect,
Direction::FacingPlayer => unreachable!(),
_ => {}
}

View File

@ -1071,6 +1071,8 @@ pub struct NPCConsts {
#[serde(default = "default_b02_balfrog")]
pub b02_balfrog: [Rect<u16>; 18],
#[serde(default = "default_b03_monster_x")]
pub b03_monster_x: [Rect<u16>; 29],
}
fn default_n001_experience() -> [Rect<u16>; 6] {
@ -4788,3 +4790,37 @@ fn default_b02_balfrog() -> [Rect<u16>; 18] {
Rect { left: 120, top: 24, right: 160, bottom: 48 },
]
}
fn default_b03_monster_x() -> [Rect<u16>; 29] {
[
Rect { left: 216, top: 0, right: 320, bottom: 48 }, // face
Rect { left: 216, top: 48, right: 320, bottom: 96 },
Rect { left: 216, top: 144, right: 320, bottom: 192 },
Rect { left: 0, top: 0, right: 72, bottom: 32 }, // tracks up
Rect { left: 0, top: 32, right: 72, bottom: 64 },
Rect { left: 72, top: 0, right: 144, bottom: 32 },
Rect { left: 144, top: 0, right: 216, bottom: 32 },
Rect { left: 72, top: 32, right: 144, bottom: 64 },
Rect { left: 144, top: 32, right: 216, bottom: 64 },
Rect { left: 0, top: 64, right: 72, bottom: 96 }, // tracks down
Rect { left: 0, top: 96, right: 72, bottom: 128 },
Rect { left: 72, top: 64, right: 144, bottom: 96 },
Rect { left: 144, top: 64, right: 216, bottom: 96 },
Rect { left: 72, top: 96, right: 144, bottom: 128 },
Rect { left: 144, top: 96, right: 216, bottom: 128 },
Rect { left: 0, top: 128, right: 72, bottom: 160 }, // frame
Rect { left: 72, top: 128, right: 144, bottom: 160 },
Rect { left: 0, top: 160, right: 72, bottom: 192 },
Rect { left: 72, top: 160, right: 144, bottom: 192 },
Rect { left: 216, top: 96, right: 264, bottom: 144 }, // shield left
Rect { left: 264, top: 96, right: 312, bottom: 144 }, // shield right
Rect { left: 0, top: 192, right: 16, bottom: 208 }, // part 4
Rect { left: 16, top: 192, right: 32, bottom: 208 },
Rect { left: 32, top: 192, right: 48, bottom: 208 },
Rect { left: 48, top: 192, right: 64, bottom: 208 },
Rect { left: 0, top: 208, right: 16, bottom: 224 },
Rect { left: 16, top: 208, right: 32, bottom: 224 },
Rect { left: 32, top: 208, right: 48, bottom: 224 },
Rect { left: 48, top: 208, right: 64, bottom: 224 },
]
}

View File

@ -115,7 +115,7 @@ impl Game {
}
if self.loops != 0 {
scene.draw_tick(&mut self.state, ctx)?;
scene.draw_tick(&mut self.state)?;
self.last_tick = last_tick;
}

View File

@ -242,7 +242,6 @@ impl BossNPC {
let deg = f64::atan2(py as f64, px as f64)
+ self.parts[0].rng.range(-16..16) as f64 * CDEG_RAD;
// todo rand
let mut npc = NPCMap::create_npc(108, &state.npc_table);
npc.cond.set_alive(true);

View File

@ -24,9 +24,9 @@ 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],
pub parts: [NPC; 20],
pub hurt_sound: [u8; 20],
pub death_sound: [u8; 20],
}
impl BossNPC {
@ -35,7 +35,7 @@ impl BossNPC {
let mut part = NPC::empty();
part.cond.set_drs_boss(true);
part
}; 16];
}; 20];
parts[0].cond.set_alive(true);
for (i, part) in parts.iter_mut().enumerate() {
part.rng.load_state((i
@ -48,8 +48,8 @@ impl BossNPC {
BossNPC {
boss_type: 0,
parts,
hurt_sound: [0; 16],
death_sound: [0; 16],
hurt_sound: [0; 20],
death_sound: [0; 20],
}
}
}
@ -63,7 +63,7 @@ impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &Bu
match self.boss_type {
1 => self.tick_b01_omega(state, players, map, bullet_manager),
2 => self.tick_b02_balfrog(state, players),
3 => self.tick_b03_monster_x(),
3 => self.tick_b03_monster_x(state, players, map),
4 => self.tick_b04_core(),
5 => self.tick_b05_ironhead(),
6 => self.tick_b06_twins(),
@ -83,7 +83,7 @@ impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &Bu
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, state.npc_table.tex_npc2_name.as_str())?;
for npc in self.parts.iter() {
for npc in self.parts.iter().rev() {
if !npc.cond.alive() || npc.cond.hidden() {
continue;
}

View File

@ -1,7 +1,840 @@
use std::cell::RefCell;
use std::collections::BTreeMap;
use ggez::GameResult;
use num_traits::{abs, clamp};
use crate::caret::CaretType;
use crate::common::{CDEG_RAD, Direction, Rect};
use crate::npc::{NPC, NPCMap};
use crate::npc::boss::BossNPC;
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
impl BossNPC {
pub(crate) fn tick_b03_monster_x(&mut self) {
impl NPC {
pub(crate) fn tick_n158_fish_missile(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter2 = match self.direction {
Direction::Left => 0xa0,
Direction::Up => 0xe0,
Direction::Right => 0x20,
Direction::Bottom => 0x60,
Direction::FacingPlayer => 0,
};
}
let radians = self.action_counter2 as f64 * CDEG_RAD;
self.vel_x = 2 * (radians.cos() * 512.0) as isize;
self.vel_y = 2 * (radians.sin() * 512.0) as isize;
self.x += self.vel_x;
self.y += self.vel_y;
let player = self.get_closest_player_mut(players);
let direction = f64::atan2(-(self.y - player.y) as f64, -(self.x - player.x) as f64);
if direction < radians {
if radians - direction < std::f64::consts::PI {
self.action_counter2 = self.action_counter2.wrapping_sub(1) & 0xff;
} else {
self.action_counter2 = (self.action_counter2 + 1) & 0xff;
}
} else {
if direction - radians < std::f64::consts::PI {
self.action_counter2 = (self.action_counter2 + 1) & 0xff;
} else {
self.action_counter2 = self.action_counter2.wrapping_sub(1) & 0xff;
}
}
}
_ => {}
}
self.anim_counter += 1;
if self.anim_counter > 2 {
self.anim_counter = 0;
state.create_caret(self.x, self.y, CaretType::Exhaust, Direction::FacingPlayer);
}
self.anim_num = (self.action_counter2 + 0x10) / 0x20;
if self.anim_num > 7 {
self.anim_num = 7;
}
self.anim_rect = state.constants.npc.n158_fish_missile[self.anim_num as usize];
Ok(())
}
}
impl BossNPC {
pub(crate) fn tick_b03_monster_x(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_map: &BTreeMap<u16, RefCell<NPC>>) {
match self.parts[0].action_num {
0 => {
self.parts[0].life = 1;
self.parts[0].x = -320 * 0x200;
}
1 => {
self.parts[0].life = 700;
self.parts[0].exp = 1;
self.parts[0].action_num = 2;
self.parts[0].anim_num = 0;
self.parts[0].x = 2048 * 0x200;
self.parts[0].y = 200 * 0x200;
self.parts[0].size = 3;
self.parts[0].event_num = 1000;
self.parts[0].hit_bounds = Rect {
left: 24 * 0x200,
top: 24 * 0x200,
right: 24 * 0x200,
bottom: 24 * 0x200,
};
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_event_when_killed(true);
self.parts[0].npc_flags.set_show_damage(true);
self.hurt_sound[0] = 54;
self.parts[1].cond.set_alive(true);
self.parts[1].size = 3;
self.parts[1].direction = Direction::Left;
self.parts[1].display_bounds = Rect {
left: 24 * 0x200,
top: 24 * 0x200,
right: 24 * 0x200,
bottom: 24 * 0x200,
};
self.parts[1].npc_flags.set_ignore_solidity(true);
self.parts[2] = self.parts[1];
self.parts[2].direction = Direction::Right;
self.parts[3].cond.set_alive(true);
self.parts[3].life = 60;
self.parts[3].size = 2;
self.parts[3].target_x = 0;
self.parts[3].display_bounds = Rect {
left: 8 * 0x200,
top: 8 * 0x200,
right: 8 * 0x200,
bottom: 8 * 0x200,
};
self.parts[3].hit_bounds = Rect {
left: 5 * 0x200,
top: 5 * 0x200,
right: 5 * 0x200,
bottom: 5 * 0x200,
};
self.parts[3].npc_flags.set_ignore_solidity(true);
self.hurt_sound[3] = 54;
self.death_sound[3] = 71;
self.parts[4] = self.parts[3];
self.parts[3].target_x = 1;
self.parts[5] = self.parts[3];
self.parts[6] = self.parts[3];
self.parts[5].target_x = 2;
self.parts[6].target_x = 3;
self.parts[5].life = 100;
self.parts[6].life = 100;
self.parts[7].cond.set_alive(true);
self.parts[7].x = self.parts[0].x;
self.parts[7].y = self.parts[0].y;
self.parts[7].size = 3;
self.parts[7].anim_num = 0;
self.parts[7].display_bounds = Rect {
left: 52 * 0x200,
top: 24 * 0x200,
right: 52 * 0x200,
bottom: 24 * 0x200,
};
self.parts[7].hit_bounds = Rect {
left: 8 * 0x200,
top: 24 * 0x200,
right: 8 * 0x200,
bottom: 16 * 0x200,
};
self.parts[7].npc_flags.set_ignore_solidity(true);
self.parts[9].cond.set_alive(true);
self.parts[9].x = self.parts[0].x - 64 * 0x200;
self.parts[9].y = self.parts[0].y - 56 * 0x200;
self.parts[9].size = 3;
self.parts[9].action_num = 0;
self.parts[9].direction = Direction::Up;
self.parts[9].display_bounds = Rect {
left: 36 * 0x200,
top: 8 * 0x200,
right: 36 * 0x200,
bottom: 24 * 0x200,
};
self.parts[9].hit_bounds = Rect {
left: 28 * 0x200,
top: 8 * 0x200,
right: 28 * 0x200,
bottom: 16 * 0x200,
};
self.hurt_sound[9] = 52;
self.parts[9].npc_flags.set_rear_and_top_not_hurt(true);
self.parts[9].npc_flags.set_ignore_solidity(true);
self.parts[9].npc_flags.set_invulnerable(true);
self.parts[9].npc_flags.set_solid_soft(true);
self.parts[10] = self.parts[9];
self.parts[10].x = self.parts[0].x + 64 * 0x200;
self.parts[11] = self.parts[9];
self.parts[11].x = self.parts[0].x - 64 * 0x200;
self.parts[11].y = self.parts[0].y + 56 * 0x200;
self.parts[11].direction = Direction::Bottom;
self.parts[11].display_bounds.top = 24 * 0x200;
self.parts[11].display_bounds.bottom = 8 * 0x200;
self.parts[11].hit_bounds.top = 16 * 0x200;
self.parts[11].hit_bounds.bottom = 8 * 0x200;
self.parts[12] = self.parts[11];
self.parts[12].x = self.parts[0].x + 64 * 0x200;
self.parts[13] = self.parts[9];
self.parts[13].display_bounds = Rect {
left: 30 * 0x200,
top: 16 * 0x200,
right: 42 * 0x200,
bottom: 16 * 0x200,
};
self.parts[13].action_counter2 = 9;
self.parts[13].anim_num = 0;
self.parts[13].npc_flags.0 = 0;
self.parts[13].npc_flags.set_ignore_solidity(true);
self.parts[14] = self.parts[13];
self.parts[14].action_counter2 = 10;
self.parts[14].anim_num = 1;
self.parts[14].display_bounds.left = 42 * 0x200;
self.parts[14].display_bounds.right = 30 * 0x200;
self.parts[15] = self.parts[13];
self.parts[15].action_counter2 = 11;
self.parts[15].anim_num = 2;
self.parts[15].display_bounds.top = 16 * 0x200;
self.parts[15].display_bounds.bottom = 16 * 0x200;
self.parts[16] = self.parts[15];
self.parts[16].action_counter2 = 12;
self.parts[16].anim_num = 3;
self.parts[16].display_bounds.left = 42 * 0x200;
self.parts[16].display_bounds.right = 30 * 0x200;
}
10 | 11 => {
if self.parts[0].action_num == 10 {
self.parts[0].action_num = 11;
self.parts[0].action_counter = 0;
self.parts[0].action_counter2 = 0;
}
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 100 {
self.parts[0].action_counter = 0;
let player_idx = self.parts[0].get_closest_player_idx_mut(&players);
self.parts[0].action_num = if self.parts[0].x > players[player_idx].x { 100 } else { 200 };
}
}
100 | 101 => {
if self.parts[0].action_num == 100 {
self.parts[0].action_num = 101;
self.parts[0].action_counter = 0;
self.parts[0].action_counter2 += 1;
}
self.parts[0].action_counter += 1;
match self.parts[0].action_counter {
4 => self.parts[9].action_num = 100,
8 => self.parts[10].action_num = 100,
10 => self.parts[11].action_num = 100,
12 => self.parts[12].action_num = 100,
_ => {}
}
if self.parts[0].action_counter > 120 && self.parts[0].action_counter2 > 2 {
self.parts[0].action_num = 300;
}
let player_idx = self.parts[0].get_closest_player_idx_mut(&players);
if self.parts[0].action_counter > 121 && players[player_idx].x > self.parts[0].x {
self.parts[0].action_num = 200;
}
}
200 | 201 => {
if self.parts[0].action_num == 200 {
self.parts[0].action_num = 201;
self.parts[0].action_counter = 0;
self.parts[0].action_counter2 += 1;
}
self.parts[0].action_counter += 1;
match self.parts[0].action_counter {
4 => self.parts[9].action_num = 200,
8 => self.parts[10].action_num = 200,
10 => self.parts[11].action_num = 200,
12 => self.parts[12].action_num = 200,
_ => {}
}
if self.parts[0].action_counter > 120 && self.parts[0].action_counter2 > 2 {
self.parts[0].action_num = 400;
}
let player_idx = self.parts[0].get_closest_player_idx_mut(&players);
if self.parts[0].action_counter > 121 && players[player_idx].x < self.parts[0].x {
self.parts[0].action_num = 100;
}
}
300 | 301 => {
if self.parts[0].action_num == 300 {
self.parts[0].action_num = 301;
self.parts[0].action_counter = 0;
}
self.parts[0].action_counter += 1;
match self.parts[0].action_counter {
4 => self.parts[9].action_num = 300,
8 => self.parts[10].action_num = 300,
10 => self.parts[11].action_num = 300,
12 => self.parts[12].action_num = 300,
_ => {}
}
if self.parts[0].action_counter > 50 {
if !self.parts[3].cond.alive() && !self.parts[4].cond.alive()
&& !self.parts[5].cond.alive() && !self.parts[6].cond.alive() {
self.parts[0].action_num = 600;
} else {
self.parts[0].action_num = 500;
}
}
}
400 | 401 => {
if self.parts[0].action_num == 400 {
self.parts[0].action_num = 401;
self.parts[0].action_counter = 0;
}
self.parts[0].action_counter += 1;
match self.parts[0].action_counter {
4 => self.parts[9].action_num = 400,
8 => self.parts[10].action_num = 400,
10 => self.parts[11].action_num = 400,
12 => self.parts[12].action_num = 400,
_ => {}
}
if self.parts[0].action_counter > 50 {
if !self.parts[3].cond.alive() && !self.parts[4].cond.alive()
&& !self.parts[5].cond.alive() && !self.parts[6].cond.alive() {
self.parts[0].action_num = 600;
} else {
self.parts[0].action_num = 500;
}
}
}
500 | 501 => {
if self.parts[0].action_num == 500 {
self.parts[0].action_num = 501;
self.parts[0].action_counter = 0;
self.parts[1].action_num = 10;
self.parts[2].action_num = 10;
}
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 300 {
self.parts[0].action_num = 502;
self.parts[0].action_counter = 0;
}
if !self.parts[3].cond.alive() && !self.parts[4].cond.alive()
&& !self.parts[5].cond.alive() && !self.parts[6].cond.alive() {
self.parts[0].action_num = 502;
self.parts[0].action_counter = 0;
}
}
502 | 503 => {
if self.parts[0].action_num == 502 {
self.parts[0].action_num = 503;
self.parts[0].action_counter = 0;
self.parts[0].action_counter2 = 0;
self.parts[1].action_num = 20;
self.parts[2].action_num = 20;
}
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 50 {
let player_idx = self.parts[0].get_closest_player_idx_mut(&players);
self.parts[0].action_num = if self.parts[0].x > players[player_idx].x { 100 } else { 200 };
}
}
600 | 601 => {
if self.parts[0].action_num == 600 {
self.parts[0].action_num = 601;
self.parts[0].action_counter = 0;
self.parts[0].vel_y2 = self.parts[0].life as isize;
self.parts[1].action_num = 30;
self.parts[2].action_num = 30;
}
self.parts[0].action_counter += 1;
if (self.parts[0].life as isize) < self.parts[0].vel_y2.saturating_sub(200)
|| self.parts[0].action_counter > 300 {
self.parts[0].action_num = 602;
self.parts[0].action_counter = 0;
}
}
602 | 603 => {
if self.parts[0].action_num == 602 {
self.parts[0].action_num = 603;
self.parts[0].action_counter = 0;
self.parts[0].action_counter2 = 0;
self.parts[1].action_num = 40;
self.parts[2].action_num = 40;
}
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 50 {
let player_idx = self.parts[0].get_closest_player_idx_mut(&players);
self.parts[0].action_num = if self.parts[0].x > players[player_idx].x { 100 } else { 200 };
}
}
1000 => {
state.quake_counter = 2;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter % 8 == 0 {
state.sound_manager.play_sfx(52);
}
let mut npc = NPCMap::create_npc(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.parts[0].x + self.parts[0].rng.range(-72..72) as isize * 0x200;
npc.y = self.parts[0].y + self.parts[0].rng.range(-64..64) as isize * 0x200;
state.new_npcs.push(npc);
if self.parts[0].action_counter > 100 {
self.parts[0].action_num = 1001;
self.parts[0].action_counter = 0;
// todo flash
state.sound_manager.play_sfx(35);
}
}
1001 => {
state.quake_counter = 40;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 50 {
for part in self.parts.iter_mut() {
part.cond.set_alive(false);
}
for npc_cell in npc_map.values() {
let npc = npc_cell.borrow_mut();
if npc.cond.alive() && npc.npc_type == 158 {
npc.cond.set_alive(false);
}
}
let mut npc = NPCMap::create_npc(159, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.parts[0].x;
npc.y = self.parts[1].y - 24 * 0x200;
state.new_npcs.push(npc);
}
}
_ => {}
}
self.tick_b03_monster_x_track(9, state, &players);
self.tick_b03_monster_x_track(10, state, &players);
self.tick_b03_monster_x_track(11, state, &players);
self.tick_b03_monster_x_track(12, state, &players);
self.parts[0].x += (((self.parts[9].x + self.parts[10].x + self.parts[11].x + self.parts[12].x) / 4) - self.parts[0].x) / 16;
self.tick_b03_monster_x_face(7, state);
self.tick_b03_monster_x_frame(13, state);
self.tick_b03_monster_x_frame(14, state);
self.tick_b03_monster_x_frame(15, state);
self.tick_b03_monster_x_frame(16, state);
self.tick_b03_monster_x_shield(1, state);
self.tick_b03_monster_x_shield(2, state);
if self.parts[3].cond.alive() { self.tick_b03_monster_x_eye(3, state, &players); }
if self.parts[4].cond.alive() { self.tick_b03_monster_x_eye(4, state, &players); }
if self.parts[5].cond.alive() { self.tick_b03_monster_x_eye(5, state, &players); }
if self.parts[6].cond.alive() { self.tick_b03_monster_x_eye(6, state, &players); }
if self.parts[0].life == 0 && self.parts[0].action_num < 1000 {
self.parts[0].action_num = 1000;
self.parts[0].action_counter = 0;
self.parts[0].shock = 150;
self.parts[9].action_num = 300;
self.parts[10].action_num = 300;
self.parts[11].action_num = 300;
self.parts[12].action_num = 300;
}
}
fn tick_b03_monster_x_face(&mut self, i: usize, state: &mut SharedGameState) {
match self.parts[i].action_num {
0 => {
self.parts[0].npc_flags.set_shootable(false);
self.parts[i].anim_num = 2;
}
10 | 11 => {
if self.parts[i].action_num == 10 {
self.parts[i].action_num = 11;
self.parts[i].action_counter = (self.parts[i].target_x * 10 + 40) as u16;
self.parts[i].anim_num = 2;
self.parts[0].npc_flags.set_shootable(true);
}
if self.parts[0].shock > 0 {
self.parts[i].action_counter2 += 1;
if self.parts[i].action_counter2 / 2 % 2 != 0 {
self.parts[i].anim_num = 1;
} else {
self.parts[i].anim_num = 0;
}
} else {
self.parts[i].anim_num = 0;
}
}
_ => {}
}
self.parts[7].x = self.parts[0].x;
self.parts[7].y = self.parts[0].y;
self.parts[i].anim_rect = state.constants.npc.b03_monster_x[self.parts[i].anim_num as usize];
}
fn tick_b03_monster_x_track(&mut self, i: usize, state: &mut SharedGameState, players: &[&mut Player; 2]) {
match self.parts[i].action_num {
10 => {
self.parts[i].anim_num = 0;
self.parts[i].npc_flags.set_bouncy(false);
}
100 | 101 => {
if self.parts[i].action_num == 100 {
self.parts[i].action_num = 101;
self.parts[i].action_counter = 0;
self.parts[i].anim_num = 2;
self.parts[i].anim_counter = 0;
self.parts[i].npc_flags.set_bouncy(true);
}
self.parts[i].action_counter += 1;
if self.parts[i].action_counter > 30 {
self.parts[i].action_num = 102;
}
self.parts[i].animate(0, 2, 3);
self.parts[i].vel_x -= 0x20;
}
102 | 103 => {
if self.parts[i].action_num == 102 {
self.parts[i].action_num = 103;
self.parts[i].anim_num = 0;
self.parts[i].anim_counter = 0;
self.parts[i].npc_flags.set_bouncy(false);
}
self.parts[i].action_counter += 1;
self.parts[i].animate(1, 0, 1);
self.parts[i].vel_x -= 0x20;
}
200 | 201 => {
if self.parts[i].action_num == 200 {
self.parts[i].action_num = 201;
self.parts[i].action_counter = 0;
self.parts[i].anim_num = 4;
self.parts[i].anim_counter = 0;
self.parts[i].npc_flags.set_bouncy(true);
self.parts[i].npc_flags.set_rear_and_top_not_hurt(true);
}
self.parts[i].action_counter += 1;
if self.parts[i].action_counter > 30 {
self.parts[i].action_num = 202;
}
self.parts[i].animate(0, 4, 5);
self.parts[i].vel_x += 0x20;
}
202 | 203 => {
if self.parts[i].action_num == 202 {
self.parts[i].action_num = 203;
self.parts[i].anim_num = 0;
self.parts[i].anim_counter = 0;
self.parts[i].npc_flags.set_bouncy(false);
}
self.parts[i].action_counter += 1;
self.parts[i].animate(1, 0, 1);
self.parts[i].vel_x += 0x20;
}
300 | 301 => {
if self.parts[i].action_num == 300 {
self.parts[i].action_num = 301;
self.parts[i].anim_num = 4;
self.parts[i].anim_counter = 0;
self.parts[i].npc_flags.set_bouncy(true);
}
self.parts[i].animate(0, 4, 5);
self.parts[i].vel_x += 0x20;
if self.parts[i].vel_x > 0 {
self.parts[i].vel_x = 0;
self.parts[i].action_num = 10;
}
}
400 | 401 => {
if self.parts[i].action_num == 400 {
self.parts[i].action_num = 401;
self.parts[i].anim_num = 2;
self.parts[i].anim_counter = 0;
self.parts[i].npc_flags.set_bouncy(true);
}
self.parts[i].animate(0, 2, 3);
self.parts[i].vel_x -= 0x20;
if self.parts[i].vel_x < 0 {
self.parts[i].vel_x = 0;
self.parts[i].action_num = 10;
}
}
_ => {}
}
if self.parts[i].action_counter % 2 == 1 && [101, 201, 301, 401].contains(&self.parts[i].action_num) {
state.sound_manager.play_sfx(112);
}
if self.parts[i].action_counter % 4 == 1 && [103, 203].contains(&self.parts[i].action_num) {
state.sound_manager.play_sfx(111);
}
let player_idx = self.parts[i].get_closest_player_idx_mut(players);
if self.parts[i].action_num >= 100 && abs(players[player_idx].y - self.parts[i].y) < 4 * 0x200 {
self.parts[i].damage = 10;
self.parts[i].npc_flags.set_rear_and_top_not_hurt(true);
} else {
self.parts[i].damage = 0;
self.parts[i].npc_flags.set_rear_and_top_not_hurt(false);
}
self.parts[i].vel_x = clamp(self.parts[i].vel_x, -0x400, 0x400);
self.parts[i].x += self.parts[i].vel_x;
let dir_offset = if self.parts[i].direction == Direction::Up { 3 } else { 9 };
self.parts[i].anim_rect = state.constants.npc.b03_monster_x[self.parts[i].anim_num as usize + dir_offset];
}
fn tick_b03_monster_x_frame(&mut self, i: usize, state: &mut SharedGameState) {
match self.parts[i].action_num {
10 | 11 => {
if self.parts[i].action_num == 10 {
self.parts[i].action_num = 11;
self.parts[i].action_counter = (self.parts[i].anim_num * 30 + 30) as u16;
}
if self.parts[i].action_counter > 0 {
self.parts[i].action_counter -= 1;
} else {
self.parts[i].action_counter = 120;
state.sound_manager.play_sfx(39);
let mut npc = NPCMap::create_npc(158, &state.npc_table);
npc.cond.set_alive(true);
match self.parts[i].anim_num {
0 => {
npc.direction = Direction::Bottom;
npc.x = self.parts[i].x + -30 * 0x200;
npc.y = self.parts[i].y + 6 * 0x200;
}
1 => {
npc.direction = Direction::Right;
npc.x = self.parts[i].x + 30 * 0x200;
npc.y = self.parts[i].y + 6 * 0x200;
}
2 => {
npc.direction = Direction::Left;
npc.x = self.parts[i].x - 30 * 0x200;
npc.y = self.parts[i].y - 6 * 0x200;
}
3 => {
npc.direction = Direction::Up;
npc.x = self.parts[i].x + 30 * 0x200;
npc.y = self.parts[i].y - 6 * 0x200;
}
_ => {}
}
state.new_npcs.push(npc);
}
}
_ => {}
}
self.parts[i].x = (self.parts[0].x + self.parts[self.parts[i].action_counter2 as usize].x) / 2;
self.parts[i].y = (self.parts[0].y + self.parts[self.parts[i].action_counter2 as usize].y) / 2;
self.parts[i].anim_rect = state.constants.npc.b03_monster_x[15 + self.parts[i].anim_num as usize];
}
fn tick_b03_monster_x_shield(&mut self, i: usize, state: &mut SharedGameState) {
match self.parts[i].action_num {
10 => {
self.parts[i].target_x += 0x200;
if self.parts[i].target_x > 32 * 0x200 {
self.parts[i].target_x = 32 * 0x200;
self.parts[i].action_num = 0;
self.parts[3].action_num = 10;
self.parts[4].action_num = 10;
self.parts[5].action_num = 10;
self.parts[6].action_num = 10;
}
}
20 => {
self.parts[i].target_x -= 0x200;
if self.parts[i].target_x < 0 {
self.parts[i].target_x = 0;
self.parts[i].action_num = 0;
self.parts[3].action_num = 0;
self.parts[4].action_num = 0;
self.parts[5].action_num = 0;
self.parts[6].action_num = 0;
}
}
30 => {
self.parts[i].target_x += 0x200;
if self.parts[i].target_x > 20 * 0x200 {
self.parts[i].target_x = 20 * 0x200;
self.parts[i].action_num = 0;
self.parts[7].action_num = 10;
self.parts[13].action_num = 10;
self.parts[14].action_num = 10;
self.parts[15].action_num = 10;
self.parts[16].action_num = 10;
}
}
40 => {
self.parts[i].target_x -= 0x200;
if self.parts[i].target_x < 0 {
self.parts[i].target_x = 0;
self.parts[i].action_num = 0;
self.parts[7].action_num = 0;
self.parts[13].action_num = 0;
self.parts[14].action_num = 0;
self.parts[15].action_num = 0;
self.parts[16].action_num = 0;
}
}
_ => {}
}
self.parts[i].x = self.parts[0].x + self.parts[i].direction.vector_x() * (24 * 0x200 + self.parts[i].target_x);
self.parts[i].y = self.parts[0].y;
let dir_offset = if self.parts[i].direction == Direction::Left { 19 } else { 20 };
self.parts[i].anim_rect = state.constants.npc.b03_monster_x[dir_offset];
}
fn tick_b03_monster_x_eye(&mut self, i: usize, state: &mut SharedGameState, players: &[&mut Player; 2]) {
match self.parts[i].action_num {
0 => {
self.parts[i].npc_flags.set_shootable(false);
self.parts[i].anim_num = 0;
}
10 | 11 => {
if self.parts[i].action_num == 10 {
self.parts[i].action_num = 11;
self.parts[i].action_counter = (self.parts[i].target_x * 10 + 40) as u16;
self.parts[i].npc_flags.set_shootable(true);
}
self.parts[i].anim_num = if self.parts[i].action_counter < 16
&& self.parts[i].action_counter / 2 % 2 != 0 { 1 } else { 0 };
if self.parts[i].action_counter > 0 {
self.parts[i].action_counter -= 1;
} else {
let player_idx = self.parts[i].get_closest_player_idx_mut(players);
let px = self.parts[i].x - players[player_idx].x;
let py = self.parts[i].y - players[player_idx].y;
let deg = f64::atan2(py as f64, px as f64)
+ self.parts[i].rng.range(-2..2) as f64 * CDEG_RAD;
let mut npc = NPCMap::create_npc(156, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.parts[i].x;
npc.y = self.parts[i].y;
npc.vel_x = (deg.cos() * -1536.0) as isize;
npc.vel_y = (deg.sin() * -1536.0) as isize;
state.new_npcs.push(npc);
state.sound_manager.play_sfx(39);
self.parts[i].action_counter = 40;
}
}
_ => {}
}
match self.parts[i].target_x {
0 => {
self.parts[i].x = self.parts[0].x - 22 * 0x200;
self.parts[i].y = self.parts[0].y - 16 * 0x200;
}
1 => {
self.parts[i].x = self.parts[0].x + 28 * 0x200;
self.parts[i].y = self.parts[0].y - 16 * 0x200;
}
2 => {
self.parts[i].x = self.parts[0].x - 15 * 0x200;
self.parts[i].y = self.parts[0].y + 14 * 0x200;
}
3 => {
self.parts[i].x = self.parts[0].x + 17 * 0x200;
self.parts[i].y = self.parts[0].y + 14 * 0x200;
}
_ => {}
}
self.parts[i].anim_rect = state.constants.npc.b03_monster_x[21 + self.parts[i].target_x as usize + 4 * self.parts[i].anim_num as usize];
}
}

View File

@ -13,9 +13,8 @@ use crate::shared_game_state::SharedGameState;
impl NPC {
pub(crate) fn tick_n048_omega_projectiles(&mut self, state: &mut SharedGameState) -> GameResult {
if self.flags.hit_left_wall() && self.vel_x < 0 {
self.vel_x = -self.vel_x;
} else if self.flags.hit_right_wall() && self.vel_x > 0 {
if (self.flags.hit_left_wall() && self.vel_x < 0)
|| (self.flags.hit_right_wall() && self.vel_x > 0) {
self.vel_x = -self.vel_x;
} else if self.flags.hit_bottom_wall() {
self.action_counter2 += 1;
@ -202,7 +201,7 @@ impl BossNPC {
state.sound_manager.play_sfx(39);
}
if self.parts[0].action_counter == 200 || bullet_manager.count_bullets_all(6) > 0 {
if self.parts[0].action_counter == 200 || bullet_manager.count_bullets_type_idx_all(6) > 0 {
self.parts[0].action_num = 70;
self.parts[0].anim_counter = 0;
@ -275,7 +274,7 @@ impl BossNPC {
}
120 => {
self.parts[0].action_counter += 1;
if self.parts[0].action_counter == 50 || bullet_manager.count_bullets_all(6) > 0 {
if self.parts[0].action_counter == 50 || bullet_manager.count_bullets_type_idx_all(6) > 0 {
self.parts[0].action_num = 130;
self.parts[0].action_counter = 0;
self.parts[0].anim_counter = 0;

View File

@ -5,6 +5,7 @@ use crate::common::Direction;
use crate::npc::NPC;
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
use crate::caret::CaretType;
impl NPC {
pub(crate) fn tick_n154_gaudi_dead(&mut self, state: &mut SharedGameState) -> GameResult {
@ -14,6 +15,22 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n156_gaudi_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.anim_num = (self.anim_num + 1) % 3;
self.anim_rect = state.constants.npc.n156_gaudi_projectile[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n361_gaudi_dashing(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
match self.action_num {
0 | 1 => {

View File

@ -285,7 +285,9 @@ impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &Bu
150 => self.tick_n150_quote(state, players),
151 => self.tick_n151_blue_robot_standing(state),
154 => self.tick_n154_gaudi_dead(state),
156 => self.tick_n156_gaudi_projectile(state),
157 => self.tick_n157_vertical_moving_block(state, players),
158 => self.tick_n158_fish_missile(state, players),
192 => self.tick_n192_scooter(state),
193 => self.tick_n193_broken_scooter(state),
194 => self.tick_n194_broken_blue_robot(state),

View File

@ -1301,7 +1301,7 @@ impl Scene for GameScene {
Ok(())
}
fn draw_tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
fn draw_tick(&mut self, state: &mut SharedGameState) -> GameResult {
self.frame.prev_x = self.frame.x;
self.frame.prev_y = self.frame.y;
self.player1.prev_x = self.player1.x;

View File

@ -7,14 +7,21 @@ pub mod game_scene;
pub mod loading_scene;
pub mod title_scene;
/// Implement this trait on any object that represents an interactive game screen.
pub trait Scene {
/// Called when the scene is shown.
fn init(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
/// Called at game tick. Perform any game state updates there.
fn tick(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
fn draw_tick(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
/// Called before draws between two ticks to update previous positions used for interpolation.
/// DO NOT perform updates of the game state there.
fn draw_tick(&mut self, _state: &mut SharedGameState) -> GameResult { Ok(()) }
/// Called during frame rendering operation.
fn draw(&self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
/// Independent draw meant for debug overlay, that lets you mutate the game state.
fn debug_overlay_draw(&mut self, _game_ui: &mut Components, _state: &mut SharedGameState, _ctx: &mut Context, _frame: &mut imgui::Ui) -> GameResult { Ok(()) }
}

View File

@ -4,7 +4,7 @@ use std::io::{BufReader, Read, Seek, SeekFrom};
use ggez;
use ggez::{Context, GameError, GameResult, graphics};
use ggez::filesystem;
use ggez::graphics::{Color, Drawable, DrawMode, DrawParam, FilterMode, Image, Mesh, mint, Rect};
use ggez::graphics::{Drawable, DrawMode, DrawParam, FilterMode, Image, Mesh, mint, Rect};
use ggez::graphics::spritebatch::SpriteBatch;
use ggez::nalgebra::{Point2, Vector2};
use image::RgbaImage;
@ -182,20 +182,6 @@ impl TextureSet {
Image::from_rgba8(ctx, width as u16, height as u16, img.as_ref())
}
fn load_image_from_buf(&self, ctx: &mut Context, buf: &[u8]) -> GameResult<Image> {
let img = {
let image = image::load_from_memory(buf)?;
let mut rgba = image.to_rgba();
if image.color().channel_count() != 4 {
TextureSet::make_transparent(&mut rgba);
}
rgba
};
let (width, height) = img.dimensions();
Image::from_rgba8(ctx, width as u16, height as u16, img.as_ref())
}
pub fn load_texture(&self, ctx: &mut Context, constants: &EngineConstants, name: &str) -> GameResult<SizedBatch> {
let path = self.paths.iter().find_map(|s| FILE_TYPES
.iter()