mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-01-10 04:57:02 +00:00
Merge pull request #6 from doukutsu-rs/feature/co-op
Co-op gameplay support
This commit is contained in:
commit
9e21838a9d
|
@ -49,6 +49,10 @@ impl BMFontRenderer {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn line_height(&self, constants: &EngineConstants) -> f32 {
|
||||
self.font.line_height as f32 * constants.font_scale
|
||||
}
|
||||
|
||||
pub fn text_width<I: Iterator<Item=char>>(&self, iter: I, constants: &EngineConstants) -> f32 {
|
||||
let mut offset_x = 0.0;
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use num_traits::clamp;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Condition, Direction, Flag, Rect};
|
||||
use crate::common::{BulletFlag, Condition, Direction, Flag, Rect};
|
||||
use crate::engine_constants::{BulletData, EngineConstants};
|
||||
use crate::npc::NPCMap;
|
||||
use crate::physics::{OFF_X, OFF_Y, PhysicalEntity};
|
||||
use crate::player::TargetPlayer;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
|
||||
|
@ -20,30 +21,30 @@ impl BulletManager {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn create_bullet(&mut self, x: isize, y: isize, btype: u16, direction: Direction, constants: &EngineConstants) {
|
||||
self.bullets.push(Bullet::new(x, y, btype, direction, constants));
|
||||
pub fn create_bullet(&mut self, x: isize, y: isize, btype: u16, owner: TargetPlayer, direction: Direction, constants: &EngineConstants) {
|
||||
self.bullets.push(Bullet::new(x, y, btype, owner, direction, constants));
|
||||
}
|
||||
|
||||
pub fn tick_bullets(&mut self, state: &mut SharedGameState, player: &dyn PhysicalEntity, stage: &mut Stage) {
|
||||
pub fn tick_bullets(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2], stage: &mut Stage) {
|
||||
for bullet in self.bullets.iter_mut() {
|
||||
if bullet.life < 1 {
|
||||
bullet.cond.set_alive(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
bullet.tick(state, player);
|
||||
bullet.tick(state, players);
|
||||
bullet.tick_map_collisions(state, stage);
|
||||
}
|
||||
|
||||
self.bullets.retain(|b| !b.is_dead());
|
||||
}
|
||||
|
||||
pub fn count_bullets(&self, btype: u16) -> usize {
|
||||
self.bullets.iter().filter(|b| b.btype == btype).count()
|
||||
pub fn count_bullets(&self, btype: u16, player_id: TargetPlayer) -> usize {
|
||||
self.bullets.iter().filter(|b| b.owner == player_id && b.btype == btype).count()
|
||||
}
|
||||
|
||||
pub fn count_bullets_multi(&self, btypes: [u16; 3]) -> usize {
|
||||
self.bullets.iter().filter(|b| btypes.contains(&b.btype)).count()
|
||||
pub fn count_bullets_multi(&self, btypes: [u16; 3], player_id: TargetPlayer) -> usize {
|
||||
self.bullets.iter().filter(|b| b.owner == player_id && btypes.contains(&b.btype)).count()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,8 +61,9 @@ pub struct Bullet {
|
|||
pub life: u16,
|
||||
pub lifetime: u16,
|
||||
pub damage: u16,
|
||||
pub owner: TargetPlayer,
|
||||
pub cond: Condition,
|
||||
pub weapon_flags: Flag,
|
||||
pub weapon_flags: BulletFlag,
|
||||
pub flags: Flag,
|
||||
pub direction: Direction,
|
||||
pub anim_rect: Rect<u16>,
|
||||
|
@ -76,14 +78,14 @@ pub struct Bullet {
|
|||
}
|
||||
|
||||
impl Bullet {
|
||||
pub fn new(x: isize, y: isize, btype: u16, direction: Direction, constants: &EngineConstants) -> Bullet {
|
||||
pub fn new(x: isize, y: isize, btype: u16, owner: TargetPlayer, direction: Direction, constants: &EngineConstants) -> Bullet {
|
||||
let bullet = constants.weapon.bullet_table
|
||||
.get(btype as usize)
|
||||
.unwrap_or_else(|| &BulletData {
|
||||
damage: 0,
|
||||
life: 0,
|
||||
lifetime: 0,
|
||||
flags: Flag(0),
|
||||
flags: BulletFlag(0),
|
||||
enemy_hit_width: 0,
|
||||
enemy_hit_height: 0,
|
||||
block_hit_width: 0,
|
||||
|
@ -104,6 +106,7 @@ impl Bullet {
|
|||
life: bullet.life as u16,
|
||||
lifetime: bullet.lifetime,
|
||||
damage: bullet.damage as u16,
|
||||
owner,
|
||||
cond: Condition(0x80),
|
||||
weapon_flags: bullet.flags,
|
||||
flags: Flag(0),
|
||||
|
@ -161,7 +164,6 @@ impl Bullet {
|
|||
|
||||
self.anim_num = (self.anim_num + 1) % 3;
|
||||
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
|
||||
|
||||
self.anim_rect = state.constants.weapon.bullet_rects.b001_snake_l1[self.anim_num as usize + dir_offset];
|
||||
|
@ -247,7 +249,7 @@ impl Bullet {
|
|||
}
|
||||
}
|
||||
|
||||
fn tick_fireball(&mut self, state: &mut SharedGameState, player: &dyn PhysicalEntity) {
|
||||
fn tick_fireball(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2]) {
|
||||
self.action_counter += 1;
|
||||
if self.action_counter > self.lifetime {
|
||||
self.cond.set_alive(false);
|
||||
|
@ -285,7 +287,7 @@ impl Bullet {
|
|||
self.vel_x = 0x400;
|
||||
}
|
||||
Direction::Up => {
|
||||
self.vel_x = player.vel_x();
|
||||
self.vel_x = players[self.owner.index()].vel_x();
|
||||
|
||||
self.direction = if self.vel_x < 0 {
|
||||
Direction::Left
|
||||
|
@ -293,7 +295,7 @@ impl Bullet {
|
|||
Direction::Right
|
||||
};
|
||||
|
||||
self.vel_x += if player.direction() == Direction::Left {
|
||||
self.vel_x += if players[self.owner.index()].direction() == Direction::Left {
|
||||
-0x80
|
||||
} else {
|
||||
0x80
|
||||
|
@ -302,7 +304,7 @@ impl Bullet {
|
|||
self.vel_y = -0x5ff;
|
||||
}
|
||||
Direction::Bottom => {
|
||||
self.vel_x = player.vel_x();
|
||||
self.vel_x = players[self.owner.index()].vel_x();
|
||||
|
||||
self.direction = if self.vel_x < 0 {
|
||||
Direction::Left
|
||||
|
@ -366,7 +368,7 @@ impl Bullet {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, state: &mut SharedGameState, player: &dyn PhysicalEntity) {
|
||||
pub fn tick(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2]) {
|
||||
if self.lifetime == 0 {
|
||||
self.cond.set_alive(false);
|
||||
return;
|
||||
|
@ -375,16 +377,15 @@ impl Bullet {
|
|||
match self.btype {
|
||||
1 => self.tick_snake_1(state),
|
||||
4 | 5 | 6 => self.tick_polar_star(state),
|
||||
7 | 8 | 9 => self.tick_fireball(state, player),
|
||||
7 | 8 | 9 => self.tick_fireball(state, players),
|
||||
_ => self.cond.set_alive(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vanish(&mut self, state: &mut SharedGameState) {
|
||||
if self.btype != 37 && self.btype != 38 && self.btype != 39 {
|
||||
state.sound_manager.play_sfx(28);
|
||||
} else {
|
||||
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Up);
|
||||
match self.btype {
|
||||
37 | 38 | 39 => state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Up),
|
||||
_ => state.sound_manager.play_sfx(28),
|
||||
}
|
||||
|
||||
self.cond.set_alive(false);
|
||||
|
@ -397,7 +398,7 @@ impl Bullet {
|
|||
let block_y = (y * 16 + 8) * 0x200;
|
||||
|
||||
for (i, &attr) in hit_attribs.iter().enumerate() {
|
||||
if self.weapon_flags.snack_destroy() {
|
||||
if self.weapon_flags.flag_x40() {
|
||||
hits[i] = attr == 0x41 || attr == 0x61;
|
||||
} else {
|
||||
hits[i] = attr == 0x41 || attr == 0x43 || attr == 0x61;
|
||||
|
@ -468,15 +469,15 @@ impl Bullet {
|
|||
self.flags.set_hit_bottom_wall(true);
|
||||
}
|
||||
|
||||
if self.weapon_flags.hit_bottom_wall() {
|
||||
if self.weapon_flags.flag_x08() {
|
||||
if self.flags.hit_left_wall() {
|
||||
self.x = block_x + self.hit_bounds.right as isize;
|
||||
} else if self.flags.hit_right_wall() {
|
||||
self.x = block_x + self.hit_bounds.left as isize;
|
||||
} else if self.flags.hit_left_wall() {
|
||||
self.x = block_x + self.hit_bounds.right as isize;
|
||||
} else if self.flags.hit_right_wall() {
|
||||
self.x = block_x + self.hit_bounds.left as isize;
|
||||
self.x = block_x - self.hit_bounds.left as isize;
|
||||
} else if self.flags.hit_top_wall() {
|
||||
self.y = block_y + self.hit_bounds.bottom as isize;
|
||||
} else if self.flags.hit_bottom_wall() {
|
||||
self.y = block_y - self.hit_bounds.top as isize;
|
||||
}
|
||||
} else if self.flags.hit_left_wall() || self.flags.hit_top_wall()
|
||||
|| self.flags.hit_right_wall() || self.flags.hit_bottom_wall() {
|
||||
|
@ -555,7 +556,7 @@ impl PhysicalEntity for Bullet {
|
|||
false
|
||||
}
|
||||
|
||||
fn judge_hit_block(&mut self, state: &mut SharedGameState, x: isize, y: isize) {
|
||||
fn judge_hit_block(&mut self, _state: &mut SharedGameState, x: isize, y: isize) {
|
||||
if (self.x - self.hit_bounds.left as isize) < (x * 16 + 8) * 0x200
|
||||
&& (self.x + self.hit_bounds.right as isize) > (x * 16 - 8) * 0x200
|
||||
&& (self.y - self.hit_bounds.top as isize) < (y * 16 + 8) * 0x200
|
||||
|
@ -566,15 +567,15 @@ impl PhysicalEntity for Bullet {
|
|||
}
|
||||
|
||||
fn tick_map_collisions(&mut self, state: &mut SharedGameState, stage: &mut Stage) {
|
||||
self.flags().0 = 0;
|
||||
if self.weapon_flags.flag_x04() { // ???
|
||||
return;
|
||||
}
|
||||
|
||||
let x = clamp(self.x() / 16 / 0x200, 0, stage.map.width as isize);
|
||||
let y = clamp(self.y() / 16 / 0x200, 0, stage.map.height as isize);
|
||||
let mut hit_attribs = [0u8; 4];
|
||||
|
||||
self.flags().0 = 0;
|
||||
if self.weapon_flags.hit_right_wall() { // ???
|
||||
return;
|
||||
}
|
||||
|
||||
for (idx, (&ox, &oy)) in OFF_X.iter().zip(OFF_Y.iter()).enumerate() {
|
||||
if idx == 4 || !self.cond.alive() {
|
||||
break;
|
||||
|
@ -589,10 +590,12 @@ impl PhysicalEntity for Bullet {
|
|||
self.judge_hit_block(state, x + ox, y + oy);
|
||||
}
|
||||
0x43 => {
|
||||
let old_hit = self.flags;
|
||||
self.flags.0 = 0;
|
||||
self.judge_hit_block(state, x + ox, y + oy);
|
||||
|
||||
if self.flags.0 != 0 && (self.weapon_flags.hit_left_slope() || self.weapon_flags.snack_destroy()) {
|
||||
if !self.weapon_flags.snack_destroy() {
|
||||
if self.flags.weapon_hit_block() && (self.weapon_flags.flag_x20() || self.weapon_flags.flag_x40()) {
|
||||
if !self.weapon_flags.flag_x40() {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
|
||||
|
@ -616,6 +619,8 @@ impl PhysicalEntity for Bullet {
|
|||
*tile = tile.wrapping_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
self.flags.0 |= old_hit.0;
|
||||
}
|
||||
// Slopes
|
||||
0x50 | 0x70 => {
|
||||
|
|
30
src/caret.rs
30
src/caret.rs
|
@ -1,7 +1,7 @@
|
|||
use std::fs::read_to_string;
|
||||
|
||||
use crate::bitfield;
|
||||
use crate::common::{Condition, Direction, Rect};
|
||||
use crate::common::{Condition, Direction, Rect, CDEG_RAD};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::rng::RNG;
|
||||
|
||||
|
@ -40,6 +40,7 @@ pub struct Caret {
|
|||
pub cond: Condition,
|
||||
pub direction: Direction,
|
||||
pub anim_rect: Rect<u16>,
|
||||
action_num: u16,
|
||||
anim_num: u16,
|
||||
anim_counter: u16,
|
||||
}
|
||||
|
@ -61,6 +62,7 @@ impl Caret {
|
|||
cond: Condition(0x80),
|
||||
direction: direct,
|
||||
anim_rect: Rect::new(0, 0, 0, 0),
|
||||
action_num: 0,
|
||||
anim_num: 0,
|
||||
anim_counter: 0,
|
||||
}
|
||||
|
@ -233,7 +235,31 @@ impl Caret {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
CaretType::HurtParticles => {}
|
||||
CaretType::HurtParticles => {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
let angle = rng.range(0..255) as f64 * CDEG_RAD;
|
||||
self.vel_x = (angle.cos() * 1024.0) as isize;
|
||||
self.vel_y = (angle.sin() * 1024.0) as isize;
|
||||
}
|
||||
|
||||
self.x += self.vel_x;
|
||||
self.y += self.vel_y;
|
||||
|
||||
if self.anim_counter == 0 {
|
||||
self.anim_rect = constants.caret.hurt_particles_rects[self.anim_num as usize];
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 2 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num >= constants.caret.hurt_particles_rects.len() as u16 {
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
CaretType::Explosion => {
|
||||
if self.anim_counter == 0 {
|
||||
self.anim_rect = constants.caret.explosion_rects[self.anim_num as usize];
|
||||
|
|
|
@ -92,9 +92,24 @@ bitfield! {
|
|||
pub credits_running, set_credits_running: 3; // 0x08
|
||||
|
||||
// engine specific flags
|
||||
pub friendly_fire, set_friendly_fire: 14;
|
||||
pub wind, set_wind: 15;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BulletFlag(u16);
|
||||
impl Debug;
|
||||
pub flag_x01, set_flag_x01: 0; // 0x01
|
||||
pub flag_x02, set_flag_x02: 1; // 0x02
|
||||
pub flag_x04, set_flag_x04: 2; // 0x04
|
||||
pub flag_x08, set_flag_x08: 3; // 0x08
|
||||
pub flag_x10, set_flag_x10: 4; // 0x10
|
||||
pub flag_x20, set_flag_x20: 5; // 0x20
|
||||
pub flag_x40, set_flag_x40: 6; // 0x40
|
||||
pub flag_x80, set_flag_x80: 7; // 0x80
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum FadeDirection {
|
||||
|
|
27
src/components/draw_common.rs
Normal file
27
src/components/draw_common.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use std::io::Cursor;
|
||||
|
||||
use ggez::{Context, GameResult};
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum Alignment {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
pub fn draw_number(x: f32, y: f32, val: usize, align: Alignment, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let n = val.to_string();
|
||||
let align_offset = if align == Alignment::Right { n.len() as f32 * 8.0 } else { 0.0 };
|
||||
|
||||
for (offset, chr) in n.chars().enumerate() {
|
||||
let idx = chr as u16 - '0' as u16;
|
||||
batch.add_rect(x - align_offset + offset as f32 * 8.0, y, &Rect::new_size(idx * 8, 56, 8, 8));
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
Ok(())
|
||||
}
|
234
src/components/hud.rs
Normal file
234
src/components/hud.rs
Normal file
|
@ -0,0 +1,234 @@
|
|||
use ggez::{Context, GameResult};
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::components::draw_common::{Alignment, draw_number};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::inventory::Inventory;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
pub struct HUD {
|
||||
pub alignment: Alignment,
|
||||
pub weapon_x_pos: usize,
|
||||
pub visible: bool,
|
||||
pub has_player2: bool,
|
||||
ammo: u16,
|
||||
max_ammo: u16,
|
||||
xp: u16,
|
||||
max_xp: u16,
|
||||
max_level: bool,
|
||||
life: u16,
|
||||
max_life: u16,
|
||||
life_bar: u16,
|
||||
life_bar_counter: u16,
|
||||
air: u16,
|
||||
air_counter: u16,
|
||||
current_level: usize,
|
||||
weapon_count: usize,
|
||||
current_weapon: isize,
|
||||
weapon_types: [u8; 16],
|
||||
}
|
||||
|
||||
impl HUD {
|
||||
pub fn new(alignment: Alignment) -> HUD {
|
||||
HUD {
|
||||
alignment,
|
||||
weapon_x_pos: 16,
|
||||
visible: false,
|
||||
has_player2: false,
|
||||
ammo: 0,
|
||||
max_ammo: 0,
|
||||
xp: 0,
|
||||
max_xp: 0,
|
||||
max_level: false,
|
||||
life: 0,
|
||||
max_life: 0,
|
||||
life_bar: 0,
|
||||
life_bar_counter: 0,
|
||||
air: 0,
|
||||
air_counter: 0,
|
||||
current_level: 0,
|
||||
weapon_count: 0,
|
||||
current_weapon: 0,
|
||||
weapon_types: [0; 16],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&Player, &Inventory)> for HUD {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (player, inventory): (&Player, &Inventory)) -> GameResult {
|
||||
let (ammo, max_ammo) = inventory.get_current_ammo();
|
||||
let (xp, max_xp, max_level) = inventory.get_current_max_exp(&state.constants);
|
||||
|
||||
self.ammo = ammo;
|
||||
self.max_ammo = max_ammo;
|
||||
self.xp = xp;
|
||||
self.max_xp = max_xp;
|
||||
self.max_level = max_level;
|
||||
|
||||
self.life = player.life;
|
||||
self.max_life = player.max_life;
|
||||
self.air = player.air;
|
||||
self.air_counter = player.air_counter;
|
||||
self.weapon_count = inventory.get_weapon_count();
|
||||
self.current_weapon = inventory.get_current_weapon_idx() as isize;
|
||||
|
||||
self.current_level = inventory.get_current_level() as usize;
|
||||
|
||||
for (a, slot) in self.weapon_types.iter_mut().enumerate() {
|
||||
*slot = if let Some(weapon) = inventory.get_weapon(a) {
|
||||
weapon.wtype as u8
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
|
||||
// update health bar
|
||||
if self.life_bar < self.life as u16 {
|
||||
self.life_bar = self.life as u16;
|
||||
}
|
||||
|
||||
if self.life_bar > self.life as u16 {
|
||||
self.life_bar_counter += 1;
|
||||
if self.life_bar_counter > 30 {
|
||||
self.life_bar -= 1;
|
||||
}
|
||||
} else {
|
||||
self.life_bar_counter = 0;
|
||||
}
|
||||
|
||||
if self.weapon_x_pos > 16 {
|
||||
self.weapon_x_pos -= 2;
|
||||
} else if self.weapon_x_pos < 16 {
|
||||
self.weapon_x_pos += 2;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult {
|
||||
if !self.visible {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// none
|
||||
let weap_x = self.weapon_x_pos as f32;
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let (bar_offset, num_offset, weapon_offset) = match self.alignment {
|
||||
Alignment::Left => (0.0, 0.0, 0.0),
|
||||
Alignment::Right => (state.canvas_size.0 - 112.0, state.canvas_size.0 - 48.0, state.canvas_size.0 - 104.0),
|
||||
};
|
||||
let air_offset = if self.has_player2 {
|
||||
50.0 * match self.alignment {
|
||||
Alignment::Left => -1.0,
|
||||
Alignment::Right => 1.0,
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
if self.max_ammo == 0 {
|
||||
batch.add_rect(bar_offset + weap_x + 48.0, 16.0,
|
||||
&Rect::new_size(80, 48, 16, 8));
|
||||
batch.add_rect(bar_offset + weap_x + 48.0, 24.0,
|
||||
&Rect::new_size(80, 48, 16, 8));
|
||||
}
|
||||
|
||||
// per
|
||||
batch.add_rect(bar_offset + weap_x + 32.0, 24.0,
|
||||
&Rect::new_size(72, 48, 8, 8));
|
||||
// lv
|
||||
batch.add_rect(num_offset + weap_x, 32.0,
|
||||
&Rect::new_size(80, 80, 16, 8));
|
||||
// xp box
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0,
|
||||
&Rect::new_size(0, 72, 40, 8));
|
||||
|
||||
if self.max_level {
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0,
|
||||
&Rect::new_size(40, 72, 40, 8));
|
||||
} else if self.max_xp > 0 {
|
||||
// xp bar
|
||||
let bar_width = (self.xp as f32 / self.max_xp as f32 * 40.0) as u16;
|
||||
|
||||
batch.add_rect(bar_offset + weap_x + 24.0, 32.0,
|
||||
&Rect::new_size(0, 80, bar_width, 8));
|
||||
}
|
||||
|
||||
if self.max_life != 0 {
|
||||
// heart/hp number box
|
||||
batch.add_rect(num_offset + 16.0, 40.0,
|
||||
&Rect::new_size(0, 40, 24, 8));
|
||||
// life box
|
||||
batch.add_rect(bar_offset + 40.0, 40.0,
|
||||
&Rect::new_size(24, 40, 40, 8));
|
||||
// yellow bar
|
||||
batch.add_rect(bar_offset + 40.0, 40.0,
|
||||
&Rect::new_size(0, 32, (self.life_bar * 40) / self.max_life, 8));
|
||||
// life
|
||||
batch.add_rect(bar_offset + 40.0, 40.0,
|
||||
&Rect::new_size(0, 24, (self.life * 40) / self.max_life, 8));
|
||||
}
|
||||
|
||||
if self.air_counter > 0 {
|
||||
let rect = if self.air % 30 > 10 {
|
||||
Rect::new_size(112, 72, 32, 8)
|
||||
} else {
|
||||
Rect::new_size(112, 80, 32, 8)
|
||||
};
|
||||
|
||||
batch.add_rect((state.canvas_size.0 / 2.0).floor() - 40.0 + air_offset,
|
||||
(state.canvas_size.1 / 2.0).floor(), &rect);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
|
||||
|
||||
if self.weapon_count != 0 {
|
||||
let mut rect = Rect::new(0, 0, 0, 16);
|
||||
|
||||
for a in 0..self.weapon_count {
|
||||
let mut pos_x = ((a as isize - self.current_weapon) as f32 * 16.0) + weap_x;
|
||||
|
||||
if pos_x < 8.0 {
|
||||
pos_x += 48.0 + self.weapon_count as f32 * 16.0;
|
||||
} else if pos_x >= 24.0 {
|
||||
pos_x += 48.0;
|
||||
}
|
||||
|
||||
if pos_x >= 72.0 + ((self.weapon_count - 1) as f32 * 16.0) {
|
||||
pos_x -= 48.0 + self.weapon_count as f32 * 16.0;
|
||||
} else if pos_x < 72.0 && pos_x >= 24.0 {
|
||||
pos_x -= 48.0;
|
||||
}
|
||||
|
||||
let wtype = unsafe { *self.weapon_types.get_unchecked(a) };
|
||||
|
||||
if wtype != 0 {
|
||||
rect.left = wtype as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
batch.add_rect(pos_x + weapon_offset, 16.0, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
if self.air_counter > 0 && self.air_counter % 6 < 4 {
|
||||
draw_number((state.canvas_size.0 / 2.0).floor() + 8.0 + air_offset,
|
||||
(state.canvas_size.1 / 2.0).floor(),
|
||||
(self.air / 10) as usize, Alignment::Left, state, ctx)?;
|
||||
}
|
||||
|
||||
if self.max_ammo != 0 {
|
||||
draw_number(num_offset + weap_x + 64.0, 16.0, self.ammo as usize, Alignment::Right, state, ctx)?;
|
||||
draw_number(num_offset + weap_x + 64.0, 24.0, self.max_ammo as usize, Alignment::Right, state, ctx)?;
|
||||
}
|
||||
draw_number(num_offset + weap_x + 24.0, 32.0, self.current_level, Alignment::Right, state, ctx)?;
|
||||
draw_number(num_offset + 40.0, 40.0, self.life_bar as usize, Alignment::Right, state, ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,2 +1,4 @@
|
|||
pub mod boss_life_bar;
|
||||
pub mod draw_common;
|
||||
pub mod hud;
|
||||
pub mod stage_select;
|
||||
|
|
|
@ -24,7 +24,6 @@ impl StageSelect {
|
|||
|
||||
pub fn reset(&mut self) {
|
||||
self.stage_select_text_y_pos = 54;
|
||||
self.current_teleport_slot = 0;
|
||||
self.tick = 0;
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +34,10 @@ impl GameEntity<(&Player, &Player)> for StageSelect {
|
|||
.filter(|&&(index, _event_num)| index != 0)
|
||||
.count();
|
||||
|
||||
if slot_count <= self.current_teleport_slot as usize {
|
||||
self.current_teleport_slot = 0;
|
||||
}
|
||||
|
||||
if self.stage_select_text_y_pos > 46 {
|
||||
self.stage_select_text_y_pos -= 1;
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ use case_insensitive_hashmap::CaseInsensitiveHashMap;
|
|||
use log::info;
|
||||
|
||||
use crate::case_insensitive_hashmap;
|
||||
use crate::common::{Flag, Rect};
|
||||
use crate::common::{BulletFlag, Flag, Rect};
|
||||
use crate::engine_constants::npcs::NPCConsts;
|
||||
use crate::player::ControlMode;
|
||||
use crate::str;
|
||||
use crate::text_script::TextScriptEncoding;
|
||||
use crate::engine_constants::npcs::NPCConsts;
|
||||
|
||||
mod npcs;
|
||||
|
||||
|
@ -59,6 +59,7 @@ pub struct CaretConsts {
|
|||
pub drowned_quote_right_rect: Rect<u16>,
|
||||
pub level_up_rects: Vec<Rect<u16>>,
|
||||
pub level_down_rects: Vec<Rect<u16>>,
|
||||
pub hurt_particles_rects: Vec<Rect<u16>>,
|
||||
pub explosion_rects: Vec<Rect<u16>>,
|
||||
pub little_particles_rects: Vec<Rect<u16>>,
|
||||
pub exhaust_rects: Vec<Rect<u16>>,
|
||||
|
@ -81,6 +82,7 @@ impl Clone for CaretConsts {
|
|||
drowned_quote_right_rect: self.drowned_quote_right_rect,
|
||||
level_up_rects: self.level_up_rects.clone(),
|
||||
level_down_rects: self.level_down_rects.clone(),
|
||||
hurt_particles_rects: self.hurt_particles_rects.clone(),
|
||||
explosion_rects: self.explosion_rects.clone(),
|
||||
little_particles_rects: self.little_particles_rects.clone(),
|
||||
exhaust_rects: self.exhaust_rects.clone(),
|
||||
|
@ -96,7 +98,7 @@ pub struct BulletData {
|
|||
pub damage: u8,
|
||||
pub life: u8,
|
||||
pub lifetime: u16,
|
||||
pub flags: Flag,
|
||||
pub flags: BulletFlag,
|
||||
pub enemy_hit_width: u16,
|
||||
pub enemy_hit_height: u16,
|
||||
pub block_hit_width: u16,
|
||||
|
@ -383,6 +385,15 @@ impl EngineConstants {
|
|||
Rect { left: 0, top: 96, right: 56, bottom: 112 },
|
||||
Rect { left: 0, top: 112, right: 56, bottom: 128 },
|
||||
],
|
||||
hurt_particles_rects: vec![
|
||||
Rect { left: 56, top: 8, right: 64, bottom: 16 },
|
||||
Rect { left: 64, top: 8, right: 72, bottom: 16 },
|
||||
Rect { left: 72, top: 8, right: 80, bottom: 16 },
|
||||
Rect { left: 80, top: 8, right: 88, bottom: 16 },
|
||||
Rect { left: 88, top: 8, right: 96, bottom: 16 },
|
||||
Rect { left: 96, top: 8, right: 104, bottom: 16 },
|
||||
Rect { left: 104, top: 8, right: 112, bottom: 16 },
|
||||
],
|
||||
explosion_rects: vec![
|
||||
Rect { left: 112, top: 0, right: 144, bottom: 32 },
|
||||
Rect { left: 144, top: 0, right: 176, bottom: 32 },
|
||||
|
@ -410,71 +421,71 @@ impl EngineConstants {
|
|||
weapon: WeaponConsts {
|
||||
bullet_table: vec![
|
||||
// Null
|
||||
BulletData { damage: 0, life: 0, lifetime: 0, flags: Flag(0), enemy_hit_width: 0, enemy_hit_height: 0, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 0, life: 0, lifetime: 0, flags: BulletFlag(0), enemy_hit_width: 0, enemy_hit_height: 0, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
// Snake
|
||||
BulletData { damage: 4, life: 1, lifetime: 20, flags: Flag(36), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 6, life: 1, lifetime: 23, flags: Flag(36), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 8, life: 1, lifetime: 30, flags: Flag(36), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 1, lifetime: 20, flags: BulletFlag(36), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 6, life: 1, lifetime: 23, flags: BulletFlag(36), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 8, life: 1, lifetime: 30, flags: BulletFlag(36), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
// Polar Star
|
||||
BulletData { damage: 1, life: 1, lifetime: 8, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 2, life: 1, lifetime: 12, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 1, lifetime: 16, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 1, life: 1, lifetime: 8, flags: BulletFlag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 2, life: 1, lifetime: 12, flags: BulletFlag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 1, lifetime: 16, flags: BulletFlag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
// Fireball
|
||||
BulletData { damage: 2, life: 2, lifetime: 100, flags: Flag(8), enemy_hit_width: 8, enemy_hit_height: 16, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 3, life: 2, lifetime: 100, flags: Flag(8), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 3, life: 2, lifetime: 100, flags: Flag(8), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 2, life: 2, lifetime: 100, flags: BulletFlag(8), enemy_hit_width: 8, enemy_hit_height: 16, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 3, life: 2, lifetime: 100, flags: BulletFlag(8), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 3, life: 2, lifetime: 100, flags: BulletFlag(8), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
// Machine Gun
|
||||
BulletData { damage: 2, life: 1, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 1, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 6, life: 1, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 2, life: 1, lifetime: 20, flags: BulletFlag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 1, lifetime: 20, flags: BulletFlag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 6, life: 1, lifetime: 20, flags: BulletFlag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
// Missile Launcher
|
||||
BulletData { damage: 0, life: 10, lifetime: 50, flags: Flag(40), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 70, flags: Flag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 90, flags: Flag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 50, flags: BulletFlag(40), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 70, flags: BulletFlag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 90, flags: BulletFlag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
// Missile Launcher explosion
|
||||
BulletData { damage: 1, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 16, enemy_hit_height: 16, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 1, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 16, enemy_hit_height: 16, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 1, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 16, enemy_hit_height: 16, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 1, life: 100, lifetime: 100, flags: BulletFlag(20), enemy_hit_width: 16, enemy_hit_height: 16, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 1, life: 100, lifetime: 100, flags: BulletFlag(20), enemy_hit_width: 16, enemy_hit_height: 16, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 1, life: 100, lifetime: 100, flags: BulletFlag(20), enemy_hit_width: 16, enemy_hit_height: 16, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
// Bubbler
|
||||
BulletData { damage: 1, life: 1, lifetime: 20, flags: Flag(8), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 2, life: 1, lifetime: 20, flags: Flag(8), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 2, life: 1, lifetime: 20, flags: Flag(8), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 1, life: 1, lifetime: 20, flags: BulletFlag(8), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 2, life: 1, lifetime: 20, flags: BulletFlag(8), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 2, life: 1, lifetime: 20, flags: BulletFlag(8), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
// Bubbler level 3 thorns
|
||||
BulletData { damage: 3, life: 1, lifetime: 32, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 3, life: 1, lifetime: 32, flags: BulletFlag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
// Blade slashes
|
||||
BulletData { damage: 0, life: 100, lifetime: 0, flags: Flag(36), enemy_hit_width: 8, enemy_hit_height: 8, block_hit_width: 8, block_hit_height: 8, display_bounds: Rect { left: 12, top: 12, right: 12, bottom: 12 } },
|
||||
BulletData { damage: 0, life: 100, lifetime: 0, flags: BulletFlag(36), enemy_hit_width: 8, enemy_hit_height: 8, block_hit_width: 8, block_hit_height: 8, display_bounds: Rect { left: 12, top: 12, right: 12, bottom: 12 } },
|
||||
// Falling spike
|
||||
BulletData { damage: 127, life: 1, lifetime: 2, flags: Flag(4), enemy_hit_width: 8, enemy_hit_height: 4, block_hit_width: 8, block_hit_height: 4, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 127, life: 1, lifetime: 2, flags: BulletFlag(4), enemy_hit_width: 8, enemy_hit_height: 4, block_hit_width: 8, block_hit_height: 4, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
// Blade
|
||||
BulletData { damage: 15, life: 1, lifetime: 30, flags: Flag(36), enemy_hit_width: 8, enemy_hit_height: 8, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 6, life: 3, lifetime: 18, flags: Flag(36), enemy_hit_width: 10, enemy_hit_height: 10, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 12, top: 12, right: 12, bottom: 12 } },
|
||||
BulletData { damage: 1, life: 100, lifetime: 30, flags: Flag(36), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 12, top: 12, right: 12, bottom: 12 } },
|
||||
BulletData { damage: 15, life: 1, lifetime: 30, flags: BulletFlag(36), enemy_hit_width: 8, enemy_hit_height: 8, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 6, life: 3, lifetime: 18, flags: BulletFlag(36), enemy_hit_width: 10, enemy_hit_height: 10, block_hit_width: 4, block_hit_height: 2, display_bounds: Rect { left: 12, top: 12, right: 12, bottom: 12 } },
|
||||
BulletData { damage: 1, life: 100, lifetime: 30, flags: BulletFlag(36), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 12, top: 12, right: 12, bottom: 12 } },
|
||||
// Super Missile Launcher
|
||||
BulletData { damage: 0, life: 10, lifetime: 30, flags: Flag(40), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 40, flags: Flag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 40, flags: Flag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 30, flags: BulletFlag(40), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 40, flags: BulletFlag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 4, block_hit_height: 4, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 0, life: 10, lifetime: 40, flags: BulletFlag(40), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
// Super Missile Launcher explosion
|
||||
BulletData { damage: 2, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 12, enemy_hit_height: 12, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 2, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 12, enemy_hit_height: 12, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 2, life: 100, lifetime: 100, flags: Flag(20), enemy_hit_width: 12, enemy_hit_height: 12, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 2, life: 100, lifetime: 100, flags: BulletFlag(20), enemy_hit_width: 12, enemy_hit_height: 12, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 2, life: 100, lifetime: 100, flags: BulletFlag(20), enemy_hit_width: 12, enemy_hit_height: 12, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 2, life: 100, lifetime: 100, flags: BulletFlag(20), enemy_hit_width: 12, enemy_hit_height: 12, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
// Nemesis
|
||||
BulletData { damage: 4, life: 4, lifetime: 20, flags: Flag(32), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 2, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } },
|
||||
BulletData { damage: 1, life: 1, lifetime: 20, flags: Flag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 4, lifetime: 20, flags: BulletFlag(32), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 2, lifetime: 20, flags: BulletFlag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } },
|
||||
BulletData { damage: 1, life: 1, lifetime: 20, flags: BulletFlag(32), enemy_hit_width: 2, enemy_hit_height: 2, block_hit_width: 2, block_hit_height: 2, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } },
|
||||
// Spur
|
||||
BulletData { damage: 4, life: 4, lifetime: 30, flags: Flag(64), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 8, life: 8, lifetime: 30, flags: Flag(64), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 12, life: 12, lifetime: 30, flags: Flag(64), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 4, lifetime: 30, flags: BulletFlag(64), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 8, life: 8, lifetime: 30, flags: BulletFlag(64), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
BulletData { damage: 12, life: 12, lifetime: 30, flags: BulletFlag(64), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 8, bottom: 8 } },
|
||||
// Spur trail
|
||||
BulletData { damage: 3, life: 100, lifetime: 30, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 6, life: 100, lifetime: 30, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 11, life: 100, lifetime: 30, flags: Flag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 3, life: 100, lifetime: 30, flags: BulletFlag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 6, life: 100, lifetime: 30, flags: BulletFlag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
BulletData { damage: 11, life: 100, lifetime: 30, flags: BulletFlag(32), enemy_hit_width: 6, enemy_hit_height: 6, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 4, top: 4, right: 4, bottom: 4 } },
|
||||
// Curly's Nemesis
|
||||
BulletData { damage: 4, life: 4, lifetime: 20, flags: Flag(32), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } },
|
||||
BulletData { damage: 4, life: 4, lifetime: 20, flags: BulletFlag(32), enemy_hit_width: 4, enemy_hit_height: 4, block_hit_width: 3, block_hit_height: 3, display_bounds: Rect { left: 8, top: 8, right: 24, bottom: 8 } },
|
||||
// EnemyClear?
|
||||
BulletData { damage: 0, life: 4, lifetime: 4, flags: Flag(4), enemy_hit_width: 0, enemy_hit_height: 0, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
BulletData { damage: 0, life: 4, lifetime: 4, flags: BulletFlag(4), enemy_hit_width: 0, enemy_hit_height: 0, block_hit_width: 0, block_hit_height: 0, display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 } },
|
||||
// Whimsical Star
|
||||
BulletData { damage: 1, life: 1, lifetime: 1, flags: Flag(36), enemy_hit_width: 1, enemy_hit_height: 1, block_hit_width: 1, block_hit_height: 1, display_bounds: Rect { left: 1, top: 1, right: 1, bottom: 1 } },
|
||||
BulletData { damage: 1, life: 1, lifetime: 1, flags: BulletFlag(36), enemy_hit_width: 1, enemy_hit_height: 1, block_hit_width: 1, block_hit_height: 1, display_bounds: Rect { left: 1, top: 1, right: 1, bottom: 1 } },
|
||||
],
|
||||
bullet_rects: BulletRects {
|
||||
b001_snake_l1: [
|
||||
|
|
|
@ -60,7 +60,7 @@ impl LiveDebugger {
|
|||
.resizable(false)
|
||||
.collapsed(true, Condition::FirstUseEver)
|
||||
.position([5.0, 5.0], Condition::FirstUseEver)
|
||||
.size([380.0, 170.0], Condition::FirstUseEver)
|
||||
.size([400.0, 170.0], Condition::FirstUseEver)
|
||||
.build(ui, || {
|
||||
ui.text(format!(
|
||||
"Player position: ({:.1},{:.1}), velocity: ({:.1},{:.1})",
|
||||
|
@ -91,6 +91,7 @@ impl LiveDebugger {
|
|||
speed = 1.0
|
||||
}
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
if state.settings.speed != speed {
|
||||
state.set_speed(speed);
|
||||
}
|
||||
|
@ -113,6 +114,15 @@ impl LiveDebugger {
|
|||
if ui.button(im_str!("Flags"), [0.0, 0.0]) {
|
||||
self.flags_visible = !self.flags_visible;
|
||||
}
|
||||
|
||||
ui.same_line(0.0);
|
||||
if game_scene.player2.cond.alive() {
|
||||
if ui.button(im_str!("Drop Player 2"), [0.0, 0.0]) {
|
||||
game_scene.drop_player2();
|
||||
}
|
||||
} else if ui.button(im_str!("Add Player 2"), [0.0, 0.0]) {
|
||||
game_scene.add_player2();
|
||||
}
|
||||
});
|
||||
|
||||
if self.map_selector_visible {
|
||||
|
@ -140,6 +150,8 @@ impl LiveDebugger {
|
|||
match GameScene::new(state, ctx, self.selected_stage as usize) {
|
||||
Ok(mut scene) => {
|
||||
scene.inventory_player1 = game_scene.inventory_player1.clone();
|
||||
scene.inventory_player2 = game_scene.inventory_player2.clone();
|
||||
|
||||
scene.player1 = game_scene.player1.clone();
|
||||
scene.player1.x = (scene.stage.map.width / 2 * 16 * 0x200) as isize;
|
||||
scene.player1.y = (scene.stage.map.height / 2 * 16 * 0x200) as isize;
|
||||
|
@ -148,6 +160,14 @@ impl LiveDebugger {
|
|||
scene.player1.life = scene.player1.max_life;
|
||||
}
|
||||
|
||||
scene.player2 = game_scene.player2.clone();
|
||||
scene.player2.x = (scene.stage.map.width / 2 * 16 * 0x200) as isize;
|
||||
scene.player2.y = (scene.stage.map.height / 2 * 16 * 0x200) as isize;
|
||||
|
||||
if scene.player2.life == 0 {
|
||||
scene.player2.life = scene.player1.max_life;
|
||||
}
|
||||
|
||||
state.next_scene = Some(Box::new(scene));
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
|
@ -179,8 +179,6 @@ impl Menu {
|
|||
}
|
||||
|
||||
pub fn tick(&mut self, controller: &mut CombinedMenuController, state: &mut SharedGameState) -> MenuSelectionResult {
|
||||
controller.update_trigger();
|
||||
|
||||
if controller.trigger_back() {
|
||||
state.sound_manager.play_sfx(5);
|
||||
return MenuSelectionResult::Canceled;
|
||||
|
|
|
@ -74,7 +74,9 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n010_balrog_shooting(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n010_balrog_shooting(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -201,11 +203,13 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n012_balrog_cutscene(&mut self, state: &mut SharedGameState, player: &Player, map: &BTreeMap<u16, RefCell<NPC>>, stage: &mut Stage) -> GameResult {
|
||||
pub(crate) fn tick_n012_balrog_cutscene(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], map: &BTreeMap<u16, RefCell<NPC>>, stage: &mut Stage) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
if self.direction == Direction::FacingPlayer {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if self.x <= player.x {
|
||||
self.direction = Direction::Right;
|
||||
} else {
|
||||
|
@ -233,6 +237,8 @@ impl NPC {
|
|||
10 | 11 => {
|
||||
if self.action_num == 10 {
|
||||
if self.direction == Direction::FacingPlayer {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if self.x <= player.x {
|
||||
self.direction = Direction::Right;
|
||||
} else {
|
||||
|
@ -269,6 +275,8 @@ impl NPC {
|
|||
20 | 21 => {
|
||||
if self.action_num == 20 {
|
||||
if self.direction == Direction::FacingPlayer {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if self.x <= player.x {
|
||||
self.direction = Direction::Right;
|
||||
} else {
|
||||
|
@ -332,6 +340,8 @@ impl NPC {
|
|||
40 | 41 => {
|
||||
if self.action_num == 40 {
|
||||
if self.direction == Direction::FacingPlayer {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if self.x <= player.x {
|
||||
self.direction = Direction::Right;
|
||||
} else {
|
||||
|
@ -354,6 +364,8 @@ impl NPC {
|
|||
42 | 43 => {
|
||||
if self.action_num == 42 {
|
||||
if self.direction == Direction::FacingPlayer {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if self.x <= player.x {
|
||||
self.direction = Direction::Right;
|
||||
} else {
|
||||
|
@ -645,7 +657,9 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n036_balrog_hover(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n036_balrog_hover(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -799,7 +813,8 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n068_balrog_running(&mut self, state: &mut SharedGameState, player: &mut Player) -> GameResult {
|
||||
/// note: vel_y2 stores currently caught player
|
||||
pub(crate) fn tick_n068_balrog_running(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -807,6 +822,7 @@ impl NPC {
|
|||
self.anim_num = 0;
|
||||
self.action_counter = 30;
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x > player.x {
|
||||
self.direction = Direction::Left;
|
||||
} else {
|
||||
|
@ -845,12 +861,14 @@ impl NPC {
|
|||
|
||||
self.vel_x += 0x10 * self.direction.vector_x(); // 0.03125fix9
|
||||
|
||||
if self.action_counter >= 8 && (player.x - self.x).abs() < 12 * 0x200 // 12.0fix9
|
||||
&& self.y - 12 * 0x200 < player.y && self.y + 8 * 0x200 > player.y { // 12.0fix9 / 8.0fix9
|
||||
let pi = self.get_closest_player_idx_mut(&players);
|
||||
if self.action_counter >= 8 && (players[pi].x - self.x).abs() < 12 * 0x200 // 12.0fix9
|
||||
&& self.y - 12 * 0x200 < players[pi].y && self.y + 8 * 0x200 > players[pi].y { // 12.0fix9 / 8.0fix9
|
||||
self.action_num = 10;
|
||||
self.anim_num = 5;
|
||||
player.cond.set_hidden(true);
|
||||
player.damage(2, state);
|
||||
self.vel_y2 = pi as isize;
|
||||
players[pi].cond.set_hidden(true);
|
||||
players[pi].damage(2, state);
|
||||
} else {
|
||||
self.action_counter += 1;
|
||||
|
||||
|
@ -872,12 +890,14 @@ impl NPC {
|
|||
state.sound_manager.play_sfx(26);
|
||||
}
|
||||
|
||||
if self.action_counter >= 8 && (player.x - self.x).abs() < 12 * 0x200
|
||||
&& self.y - 12 * 0x200 < player.y && self.y + 8 * 0x200 > player.y {
|
||||
let pi = self.get_closest_player_idx_mut(&players);
|
||||
if self.action_counter >= 8 && (players[pi].x - self.x).abs() < 12 * 0x200
|
||||
&& self.y - 12 * 0x200 < players[pi].y && self.y + 8 * 0x200 > players[pi].y {
|
||||
self.action_num = 10;
|
||||
self.anim_num = 5;
|
||||
player.cond.set_hidden(true);
|
||||
player.damage(2, state);
|
||||
self.vel_y2 = pi as isize;
|
||||
players[pi].cond.set_hidden(true);
|
||||
players[pi].damage(2, state);
|
||||
}
|
||||
}
|
||||
9 => {
|
||||
|
@ -888,6 +908,7 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
10 => {
|
||||
let player = &mut players[self.vel_y2 as usize];
|
||||
player.x = self.x;
|
||||
player.y = self.y;
|
||||
|
||||
|
@ -901,6 +922,7 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
11 => {
|
||||
let player = &mut players[self.vel_y2 as usize];
|
||||
player.x = self.x;
|
||||
player.y = self.y;
|
||||
|
||||
|
@ -921,6 +943,7 @@ impl NPC {
|
|||
}
|
||||
20 | 21 => {
|
||||
if self.action_num == 20 {
|
||||
let player = &mut players[self.vel_y2 as usize];
|
||||
state.sound_manager.play_sfx(25);
|
||||
player.cond.set_hidden(false);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::player::Player;
|
|||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n093_chaco(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n093_chaco(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -22,6 +22,7 @@ impl NPC {
|
|||
self.anim_num = 1;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (self.x - player.x).abs() < 32 * 0x200
|
||||
&& self.y - 32 * 0x200 < player.y
|
||||
&& self.y + 16 * 0x200 > player.y {
|
||||
|
|
|
@ -7,13 +7,15 @@ use crate::player::Player;
|
|||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n029_cthulhu(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n029_cthulhu(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if abs(self.x - player.x) < 48 * 0x200 && self.y - 48 * 0x200 < player.y && self.y + 16 * 0x200 > player.y {
|
||||
self.anim_num = 1;
|
||||
} else {
|
||||
|
|
|
@ -95,7 +95,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n005_green_critter(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n005_green_critter(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -105,6 +105,8 @@ impl NPC {
|
|||
self.anim_rect = state.constants.npc.n005_green_critter[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }];
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if self.x > player.x {
|
||||
self.direction = Direction::Left;
|
||||
} else {
|
||||
|
@ -307,9 +309,10 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n007_basil(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n007_basil(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
self.x = player.x;
|
||||
|
||||
if self.direction == Direction::Left {
|
||||
|
@ -321,6 +324,7 @@ impl NPC {
|
|||
1 => {
|
||||
self.vel_x -= 0x40;
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x < (player.x - 192 * 0x200) {
|
||||
self.action_num = 2;
|
||||
}
|
||||
|
@ -333,6 +337,7 @@ impl NPC {
|
|||
2 => {
|
||||
self.vel_x += 0x40;
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x > (player.x + 192 * 0x200) {
|
||||
self.action_num = 1;
|
||||
}
|
||||
|
@ -368,9 +373,11 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n008_blue_beetle(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n008_blue_beetle(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if player.x < self.x + 16 * 0x200 && player.x > self.x - 16 * 0x200 {
|
||||
self.npc_flags.set_shootable(true);
|
||||
self.vel_y = -0x100;
|
||||
|
@ -401,6 +408,8 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
1 => {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if self.x > player.x {
|
||||
self.direction = Direction::Left;
|
||||
self.vel_x -= 0x10;
|
||||
|
@ -535,7 +544,9 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n058_basu(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n058_basu(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
0 => {
|
||||
if player.x < self.x + 16 * 0x200 && player.x > self.x - 16 * 0x200 {
|
||||
|
|
|
@ -7,15 +7,14 @@ use crate::player::Player;
|
|||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n059_eye_door(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
self.npc_flags.set_event_when_touched(true);
|
||||
|
||||
pub(crate) fn tick_n059_eye_door(&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;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x - (64 * 0x200) < player.x
|
||||
&& self.x + (64 * 0x200) > player.x
|
||||
&& self.y - (64 * 0x200) < player.y
|
||||
|
@ -36,6 +35,7 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
3 => {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if !(self.x - (64 * 0x200) < player.x
|
||||
&& self.x + (64 * 0x200) > player.x
|
||||
&& self.y - (64 * 0x200) < player.y
|
||||
|
@ -67,7 +67,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
|
||||
pub(crate) fn tick_n064_first_cave_critter(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n064_first_cave_critter(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -77,6 +77,7 @@ impl NPC {
|
|||
self.anim_rect = state.constants.npc.n064_first_cave_critter[self.anim_num as usize + if self.direction == Direction::Right { 3 } else { 0 }];
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x > player.x {
|
||||
self.direction = Direction::Left;
|
||||
} else {
|
||||
|
@ -180,7 +181,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n065_first_cave_bat(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n065_first_cave_bat(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -199,6 +200,7 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
2 => {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x > player.x {
|
||||
self.direction = Direction::Left;
|
||||
} else {
|
||||
|
|
|
@ -10,7 +10,9 @@ use crate::player::Player;
|
|||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n024_power_critter(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n024_power_critter(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -140,7 +142,9 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n026_bat_flying(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n026_bat_flying(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -215,7 +219,9 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n028_flying_critter(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n028_flying_critter(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -349,7 +355,9 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n031_bat_hanging(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n031_bat_hanging(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -496,7 +504,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n094_kulala(&mut self, state: &SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n094_kulala(&mut self, state: &SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.anim_num = 4;
|
||||
|
@ -579,6 +587,8 @@ impl NPC {
|
|||
|
||||
self.vel_x += self.direction.vector_x() * 0x80;
|
||||
} else {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
self.vel_x2 = 50;
|
||||
self.direction = if self.x > player.x {
|
||||
Direction::Left
|
||||
|
@ -747,7 +757,7 @@ impl NPC {
|
|||
|
||||
pub(crate) fn tick_n103_mannan_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
if self.action_num == 0 {
|
||||
self.action_num == 1;
|
||||
self.action_num = 1;
|
||||
}
|
||||
|
||||
self.vel_x += self.direction.vector_x() * 0x20;
|
||||
|
@ -779,7 +789,9 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n104_frog(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n104_frog(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.action_num = 1;
|
||||
|
@ -1068,7 +1080,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n109_malco_powered_on(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n109_malco_powered_on(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -1088,6 +1100,8 @@ impl NPC {
|
|||
self.anim_num = 1;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if abs(self.x - player.x) < 32 * 0x200
|
||||
&& self.y - 32 * 0x200 < player.y
|
||||
&& self.y + 16 * 0x200 > player.y {
|
||||
|
@ -1134,7 +1148,9 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n110_puchi(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n110_puchi(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.action_num = 1;
|
||||
|
|
|
@ -87,7 +87,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n088_igor_boss(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n088_igor_boss(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -112,6 +112,7 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
2 | 3 => {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.action_num == 2 {
|
||||
self.action_num = 3;
|
||||
self.action_counter = 0;
|
||||
|
@ -226,6 +227,7 @@ impl NPC {
|
|||
self.action_num = 10;
|
||||
self.action_counter = 0;
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
self.direction = if player.x < self.x { Direction::Left } else { Direction::Right };
|
||||
}
|
||||
|
||||
|
@ -273,12 +275,13 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n089_igor_dead(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n089_igor_dead(&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;
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use ggez::GameResult;
|
||||
use num_traits::{abs, clamp};
|
||||
|
||||
use crate::common::Direction;
|
||||
use ggez::GameResult;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
@ -14,7 +14,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n361_gaudi_dashing(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n361_gaudi_dashing(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -23,6 +23,7 @@ impl NPC {
|
|||
self.action_num = 1;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (self.direction == Direction::Right && player.x > self.x + 272 * 0x200 && player.x < self.x + 288 * 0x200)
|
||||
|| (self.direction == Direction::Left && player.x < self.x - 272 * 0x200 && player.x > self.x - 288 * 0x200) {
|
||||
self.action_num = 10;
|
||||
|
@ -37,6 +38,7 @@ impl NPC {
|
|||
self.damage = 5;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x > player.x {
|
||||
self.direction = Direction::Left;
|
||||
} else {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use ggez::GameResult;
|
||||
use num_traits::{abs, clamp};
|
||||
|
||||
use crate::common::Direction;
|
||||
use ggez::GameResult;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
@ -154,7 +154,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n075_kanpachi(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n075_kanpachi(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
|
@ -162,6 +162,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
if self.action_num == 1 {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (self.x - (48 * 0x200) < player.x) && (self.x + (48 * 0x200) > player.x)
|
||||
&& (self.y - (48 * 0x200) < player.y) && (self.y + (48 * 0x200) > player.y) {
|
||||
self.anim_num = 1;
|
||||
|
@ -209,7 +210,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n079_mahin(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n079_mahin(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.action_num = 1;
|
||||
|
@ -224,6 +225,7 @@ impl NPC {
|
|||
self.anim_num = 1;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (self.x - (32 * 0x200) < player.x) && (self.x + (32 * 0x200) > player.x)
|
||||
&& (self.y - (32 * 0x200) < player.y) && (self.y + (16 * 0x200) > player.y) {
|
||||
if self.x > player.x {
|
||||
|
@ -258,7 +260,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n080_gravekeeper(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n080_gravekeeper(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -270,6 +272,7 @@ impl NPC {
|
|||
|
||||
self.anim_num = 0;
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if abs(player.x - self.x) < 128 * 0x200
|
||||
&& self.y - 48 * 0x200 < player.y && self.y + 32 * 0x200 > player.y {
|
||||
self.anim_counter = 0;
|
||||
|
@ -295,6 +298,7 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if abs(player.x - self.x) < 16 * 0x200 {
|
||||
self.hit_bounds.left = 18 * 0x200;
|
||||
self.action_counter = 0;
|
||||
|
@ -356,7 +360,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n081_giant_pignon(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n081_giant_pignon(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 6 };
|
||||
|
||||
match self.action_num {
|
||||
|
@ -441,6 +445,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
if self.shock > 0 && [1, 2, 4].contains(&self.action_num) {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
self.vel_x = if self.x < player.x { 0x100 } else { -0x100 };
|
||||
self.vel_y = -0x200;
|
||||
self.anim_num = 5;
|
||||
|
@ -467,7 +472,7 @@ impl NPC {
|
|||
self.y += 16 * 0x200;
|
||||
self.anim_rect = state.constants.npc.n091_mimiga_cage;
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
165
src/npc/misc.rs
165
src/npc/misc.rs
|
@ -1,9 +1,9 @@
|
|||
use ggez::GameResult;
|
||||
use num_traits::{abs, clamp};
|
||||
use num_traits::real::Real;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::Direction;
|
||||
use ggez::GameResult;
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
@ -528,9 +528,11 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n046_hv_trigger(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n046_hv_trigger(&mut self, players: [&mut Player; 2]) -> GameResult {
|
||||
self.npc_flags.set_event_when_touched(true);
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if self.direction == Direction::Left {
|
||||
if self.x < player.x {
|
||||
self.x += 0x5ff;
|
||||
|
@ -555,12 +557,13 @@ impl NPC {
|
|||
}
|
||||
|
||||
|
||||
pub(crate) fn tick_n072_sprinkler(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n072_sprinkler(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
if self.direction == Direction::Left {
|
||||
self.anim_counter = (self.anim_counter + 1) % 4;
|
||||
self.anim_num = self.anim_counter / 2;
|
||||
self.anim_rect = state.constants.npc.n072_sprinkler[self.anim_num as usize];
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.anim_num % 2 == 0 && (player.x - self.x).abs() < 480 * 0x200 {
|
||||
self.action_counter = self.action_counter.wrapping_add(1);
|
||||
|
||||
|
@ -643,10 +646,12 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n085_terminal(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n085_terminal(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.anim_num = 0;
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
if abs(player.x - self.x) < 8 * 0x200 && player.y < self.y + 8 * 0x200 && player.y > self.y - 16 * 0x200 {
|
||||
state.sound_manager.play_sfx(43);
|
||||
self.action_num = 1;
|
||||
|
@ -658,7 +663,7 @@ impl NPC {
|
|||
self.anim_num = 1;
|
||||
}
|
||||
}
|
||||
_ =>{ }
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 3 };
|
||||
|
@ -667,7 +672,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n096_fan_left(&mut self, state: &mut SharedGameState, player: &mut Player) -> GameResult {
|
||||
pub(crate) fn tick_n096_fan_left(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 && self.direction == Direction::Right {
|
||||
|
@ -686,19 +691,28 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
|
||||
if abs(player.x - self.x) < 480 * 0x200 && abs(player.y - self.y) < 240 * 0x200
|
||||
&& state.game_rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Left;
|
||||
particle.x = self.x;
|
||||
particle.y = self.y + (state.game_rng.range(-8..8) * 0x200) as isize;
|
||||
state.new_npcs.push(particle);
|
||||
{
|
||||
let i = self.get_closest_player_idx_mut(&players);
|
||||
if abs(players[i].x - self.x) < 480 * 0x200 && abs(players[i].y - self.y) < 240 * 0x200
|
||||
&& state.game_rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Left;
|
||||
particle.x = self.x;
|
||||
particle.y = self.y + (state.game_rng.range(-8..8) * 0x200) as isize;
|
||||
state.new_npcs.push(particle);
|
||||
}
|
||||
}
|
||||
|
||||
if abs(player.y - self.y) < 8 * 0x200 && player.x < self.x && player.x > self.x - 96 * 0x200 {
|
||||
player.vel_x -= 0x88;
|
||||
player.cond.set_increase_acceleration(true);
|
||||
for player in players.iter_mut() {
|
||||
if !player.cond.alive() || player.cond.hidden() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if abs(player.y - self.y) < 8 * 0x200 && player.x < self.x && player.x > self.x - 96 * 0x200 {
|
||||
player.vel_x -= 0x88;
|
||||
player.cond.set_increase_acceleration(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -711,7 +725,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n097_fan_up(&mut self, state: &mut SharedGameState, player: &mut Player) -> GameResult {
|
||||
pub(crate) fn tick_n097_fan_up(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 && self.direction == Direction::Right {
|
||||
|
@ -730,18 +744,27 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
|
||||
if abs(player.x - self.x) < 480 * 0x200 && abs(player.y - self.y) < 240 * 0x200
|
||||
&& state.game_rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Up;
|
||||
particle.x = self.x + (state.game_rng.range(-8..8) * 0x200) as isize;
|
||||
particle.y = self.y;
|
||||
state.new_npcs.push(particle);
|
||||
{
|
||||
let i = self.get_closest_player_idx_mut(&players);
|
||||
if abs(players[i].x - self.x) < 480 * 0x200 && abs(players[i].y - self.y) < 240 * 0x200
|
||||
&& state.game_rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Up;
|
||||
particle.x = self.x + (state.game_rng.range(-8..8) * 0x200) as isize;
|
||||
particle.y = self.y;
|
||||
state.new_npcs.push(particle);
|
||||
}
|
||||
}
|
||||
|
||||
if abs(player.x - self.x) < 8 * 0x200 && player.y < self.y && player.y > self.y - 96 * 0x200 {
|
||||
player.vel_y -= 0x88;
|
||||
for player in players.iter_mut() {
|
||||
if !player.cond.alive() || player.cond.hidden() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if abs(player.x - self.x) < 8 * 0x200 && player.y < self.y && player.y > self.y - 96 * 0x200 {
|
||||
player.vel_y -= 0x88;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -754,7 +777,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n098_fan_right(&mut self, state: &mut SharedGameState, player: &mut Player) -> GameResult {
|
||||
pub(crate) fn tick_n098_fan_right(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 && self.direction == Direction::Right {
|
||||
|
@ -773,19 +796,24 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
|
||||
if abs(player.x - self.x) < 480 * 0x200 && abs(player.y - self.y) < 240 * 0x200
|
||||
&& state.game_rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Right;
|
||||
particle.x = self.x;
|
||||
particle.y = self.y + (state.game_rng.range(-8..8) * 0x200) as isize;
|
||||
state.new_npcs.push(particle);
|
||||
{
|
||||
let i = self.get_closest_player_idx_mut(&players);
|
||||
if abs(players[i].x - self.x) < 480 * 0x200 && abs(players[i].y - self.y) < 240 * 0x200
|
||||
&& state.game_rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Right;
|
||||
particle.x = self.x;
|
||||
particle.y = self.y + (state.game_rng.range(-8..8) * 0x200) as isize;
|
||||
state.new_npcs.push(particle);
|
||||
}
|
||||
}
|
||||
|
||||
if abs(player.y - self.y) < 8 * 0x200 && player.x > self.x && player.x < self.x + 96 * 0x200 {
|
||||
player.vel_x += 0x88;
|
||||
player.cond.set_increase_acceleration(true);
|
||||
for player in players.iter_mut() {
|
||||
if abs(player.y - self.y) < 8 * 0x200 && player.x > self.x && player.x < self.x + 96 * 0x200 {
|
||||
player.vel_x += 0x88;
|
||||
player.cond.set_increase_acceleration(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -798,7 +826,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n099_fan_down(&mut self, state: &mut SharedGameState, player: &mut Player) -> GameResult {
|
||||
pub(crate) fn tick_n099_fan_down(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 && self.direction == Direction::Right {
|
||||
|
@ -817,18 +845,23 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
|
||||
if abs(player.x - self.x) < 480 * 0x200 && abs(player.y - self.y) < 240 * 0x200
|
||||
&& state.game_rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Bottom;
|
||||
particle.x = self.x + (state.game_rng.range(-8..8) * 0x200) as isize;
|
||||
particle.y = self.y;
|
||||
state.new_npcs.push(particle);
|
||||
{
|
||||
let i = self.get_closest_player_idx_mut(&players);
|
||||
if abs(players[i].x - self.x) < 480 * 0x200 && abs(players[i].y - self.y) < 240 * 0x200
|
||||
&& state.game_rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Bottom;
|
||||
particle.x = self.x + (state.game_rng.range(-8..8) * 0x200) as isize;
|
||||
particle.y = self.y;
|
||||
state.new_npcs.push(particle);
|
||||
}
|
||||
}
|
||||
|
||||
if abs(player.x - self.x) < 8 * 0x200 && player.y > self.y && player.y < self.y + 96 * 0x200 {
|
||||
player.vel_y -= 0x88;
|
||||
for player in players.iter_mut() {
|
||||
if abs(player.x - self.x) < 8 * 0x200 && player.y > self.y && player.y < self.y + 96 * 0x200 {
|
||||
player.vel_y -= 0x88;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -870,7 +903,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n114_press(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n114_press(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -886,7 +919,7 @@ impl NPC {
|
|||
}
|
||||
10 => {
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 2{
|
||||
if self.anim_counter > 2 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
if self.anim_num > 2 {
|
||||
|
@ -894,12 +927,14 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
|
||||
if player.y > self.y {
|
||||
self.npc_flags.set_solid_hard(false);
|
||||
self.damage = 127;
|
||||
} else {
|
||||
self.npc_flags.set_solid_hard(true);
|
||||
self.damage = 0;
|
||||
for player in players.iter() {
|
||||
if player.y > self.y {
|
||||
self.npc_flags.set_solid_hard(false);
|
||||
self.damage = 127;
|
||||
} else {
|
||||
self.npc_flags.set_solid_hard(true);
|
||||
self.damage = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if self.flags.hit_bottom_wall() {
|
||||
|
@ -941,7 +976,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n149_horizontal_moving_block(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n149_horizontal_moving_block(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.x += 8 * 0x200;
|
||||
|
@ -954,6 +989,7 @@ impl NPC {
|
|||
10 => {
|
||||
self.npc_flags.set_rear_and_top_not_hurt(false);
|
||||
self.damage = 0;
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (player.x < self.x + 25 * 0x200) && (player.x > self.x - 25 * 16 * 0x200)
|
||||
&& (player.y < self.y + 25 * 0x200) && (player.y > self.y - 25 * 0x200) {
|
||||
self.action_num = 11;
|
||||
|
@ -986,6 +1022,7 @@ impl NPC {
|
|||
state.new_npcs.push(npc);
|
||||
}
|
||||
} else {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if player.flags.hit_left_wall() {
|
||||
self.npc_flags.set_rear_and_top_not_hurt(true);
|
||||
self.damage = 100;
|
||||
|
@ -1001,6 +1038,7 @@ impl NPC {
|
|||
self.npc_flags.set_rear_and_top_not_hurt(false);
|
||||
self.damage = 0;
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (player.x > self.x - 25 * 0x200) && (player.x < self.x + 25 * 16 * 0x200)
|
||||
&& (player.y < self.y + 25 * 0x200) && (player.y > self.y - 25 * 0x200) {
|
||||
self.action_num = 21;
|
||||
|
@ -1033,6 +1071,7 @@ impl NPC {
|
|||
state.new_npcs.push(npc);
|
||||
}
|
||||
} else {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if player.flags.hit_right_wall() {
|
||||
self.npc_flags.set_rear_and_top_not_hurt(true);
|
||||
self.damage = 100;
|
||||
|
@ -1058,7 +1097,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n157_vertical_moving_block(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n157_vertical_moving_block(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.x += 8 * 0x200;
|
||||
|
@ -1071,6 +1110,7 @@ impl NPC {
|
|||
10 => {
|
||||
self.npc_flags.set_rear_and_top_not_hurt(false);
|
||||
self.damage = 0;
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (player.y < self.y + 25 * 0x200) && (player.y > self.y - 25 * 16 * 0x200)
|
||||
&& (player.x < self.x + 25 * 0x200) && (player.x > self.x - 25 * 0x200) {
|
||||
self.action_num = 11;
|
||||
|
@ -1103,6 +1143,7 @@ impl NPC {
|
|||
state.new_npcs.push(npc);
|
||||
}
|
||||
} else {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if player.flags.hit_top_wall() {
|
||||
self.npc_flags.set_rear_and_top_not_hurt(true);
|
||||
self.damage = 100;
|
||||
|
@ -1118,6 +1159,7 @@ impl NPC {
|
|||
self.npc_flags.set_rear_and_top_not_hurt(false);
|
||||
self.damage = 0;
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (player.y > self.y - 25 * 0x200) && (player.y < self.y + 25 * 16 * 0x200)
|
||||
&& (player.x < self.x + 25 * 0x200) && (player.x > self.x - 25 * 0x200) {
|
||||
self.action_num = 21;
|
||||
|
@ -1150,6 +1192,7 @@ impl NPC {
|
|||
state.new_npcs.push(npc);
|
||||
}
|
||||
} else {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if player.flags.hit_bottom_wall() {
|
||||
self.npc_flags.set_rear_and_top_not_hurt(true);
|
||||
self.damage = 100;
|
||||
|
|
|
@ -11,7 +11,7 @@ use num_traits::abs;
|
|||
|
||||
use crate::bitfield;
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Condition, fix9_scale, interpolate_fix9_scale, Rect};
|
||||
use crate::common::{Condition, interpolate_fix9_scale, Rect};
|
||||
use crate::common::Direction;
|
||||
use crate::common::Flag;
|
||||
use crate::entity::GameEntity;
|
||||
|
@ -37,6 +37,7 @@ pub mod maze;
|
|||
pub mod mimiga_village;
|
||||
pub mod misc;
|
||||
pub mod misery;
|
||||
pub mod npc_utils;
|
||||
pub mod pickups;
|
||||
pub mod quote;
|
||||
pub mod sand_zone;
|
||||
|
@ -163,22 +164,22 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
|
||||
impl GameEntity<(&mut Player, &BTreeMap<u16, RefCell<NPC>>, &mut Stage)> for NPC {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (player, map, stage): (&mut Player, &BTreeMap<u16, RefCell<NPC>>, &mut Stage)) -> GameResult {
|
||||
impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage)> for NPC {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (players, map, stage): ([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage)) -> GameResult {
|
||||
match self.npc_type {
|
||||
0 => self.tick_n000_null(),
|
||||
1 => self.tick_n001_experience(state),
|
||||
2 => self.tick_n002_behemoth(state),
|
||||
3 => self.tick_n003_dead_enemy(),
|
||||
4 => self.tick_n004_smoke(state),
|
||||
5 => self.tick_n005_green_critter(state, player),
|
||||
5 => self.tick_n005_green_critter(state, players),
|
||||
6 => self.tick_n006_green_beetle(state),
|
||||
7 => self.tick_n007_basil(state, player),
|
||||
8 => self.tick_n008_blue_beetle(state, player),
|
||||
7 => self.tick_n007_basil(state, players),
|
||||
8 => self.tick_n008_blue_beetle(state, players),
|
||||
9 => self.tick_n009_balrog_falling_in(state),
|
||||
10 => self.tick_n010_balrog_shooting(state, player),
|
||||
10 => self.tick_n010_balrog_shooting(state, players),
|
||||
11 => self.tick_n011_balrogs_projectile(state),
|
||||
12 => self.tick_n012_balrog_cutscene(state, player, map, stage),
|
||||
12 => self.tick_n012_balrog_cutscene(state, players, map, stage),
|
||||
13 => self.tick_n013_forcefield(state),
|
||||
14 => self.tick_n014_key(state),
|
||||
15 => self.tick_n015_chest_closed(state),
|
||||
|
@ -190,89 +191,89 @@ impl GameEntity<(&mut Player, &BTreeMap<u16, RefCell<NPC>>, &mut Stage)> for NPC
|
|||
21 => self.tick_n021_chest_open(state),
|
||||
22 => self.tick_n022_teleporter(state),
|
||||
23 => self.tick_n023_teleporter_lights(state),
|
||||
24 => self.tick_n024_power_critter(state, player),
|
||||
24 => self.tick_n024_power_critter(state, players),
|
||||
25 => self.tick_n025_lift(state),
|
||||
26 => self.tick_n026_bat_flying(state, player),
|
||||
26 => self.tick_n026_bat_flying(state, players),
|
||||
27 => self.tick_n027_death_trap(state),
|
||||
28 => self.tick_n028_flying_critter(state, player),
|
||||
29 => self.tick_n029_cthulhu(state, player),
|
||||
28 => self.tick_n028_flying_critter(state, players),
|
||||
29 => self.tick_n029_cthulhu(state, players),
|
||||
30 => self.tick_n030_gunsmith(state),
|
||||
31 => self.tick_n031_bat_hanging(state, player),
|
||||
31 => self.tick_n031_bat_hanging(state, players),
|
||||
32 => self.tick_n032_life_capsule(state),
|
||||
33 => self.tick_n033_balrog_bouncing_projectile(state),
|
||||
34 => self.tick_n034_bed(state),
|
||||
35 => self.tick_n035_mannan(state),
|
||||
36 => self.tick_n036_balrog_hover(state, player),
|
||||
36 => self.tick_n036_balrog_hover(state, players),
|
||||
37 => self.tick_n037_sign(state),
|
||||
38 => self.tick_n038_fireplace(state),
|
||||
39 => self.tick_n039_save_sign(state),
|
||||
40 => self.tick_n040_santa(state, player),
|
||||
40 => self.tick_n040_santa(state, players),
|
||||
41 => self.tick_n041_busted_door(state),
|
||||
42 => self.tick_n042_sue(state, player, map),
|
||||
42 => self.tick_n042_sue(state, players, map),
|
||||
43 => self.tick_n043_chalkboard(state),
|
||||
46 => self.tick_n046_hv_trigger(state, player),
|
||||
46 => self.tick_n046_hv_trigger(players),
|
||||
52 => self.tick_n052_sitting_blue_robot(state),
|
||||
55 => self.tick_n055_kazuma(state),
|
||||
58 => self.tick_n058_basu(state, player),
|
||||
59 => self.tick_n059_eye_door(state, player),
|
||||
60 => self.tick_n060_toroko(state, player),
|
||||
58 => self.tick_n058_basu(state, players),
|
||||
59 => self.tick_n059_eye_door(state, players),
|
||||
60 => self.tick_n060_toroko(state, players),
|
||||
61 => self.tick_n061_king(state),
|
||||
62 => self.tick_n062_kazuma_computer(state),
|
||||
63 => self.tick_n063_toroko_stick(state),
|
||||
64 => self.tick_n064_first_cave_critter(state, player),
|
||||
65 => self.tick_n065_first_cave_bat(state, player),
|
||||
64 => self.tick_n064_first_cave_critter(state, players),
|
||||
65 => self.tick_n065_first_cave_bat(state, players),
|
||||
66 => self.tick_n066_misery_bubble(state, map),
|
||||
67 => self.tick_n067_misery_floating(state),
|
||||
68 => self.tick_n068_balrog_running(state, player),
|
||||
68 => self.tick_n068_balrog_running(state, players),
|
||||
69 => self.tick_n069_pignon(state),
|
||||
70 => self.tick_n070_sparkle(state),
|
||||
71 => self.tick_n071_chinfish(state),
|
||||
72 => self.tick_n072_sprinkler(state, player),
|
||||
72 => self.tick_n072_sprinkler(state, players),
|
||||
73 => self.tick_n073_water_droplet(state, stage),
|
||||
74 => self.tick_n074_jack(state),
|
||||
75 => self.tick_n075_kanpachi(state, player),
|
||||
75 => self.tick_n075_kanpachi(state, players),
|
||||
76 => self.tick_n076_flowers(),
|
||||
77 => self.tick_n077_yamashita(state),
|
||||
78 => self.tick_n078_pot(state),
|
||||
79 => self.tick_n079_mahin(state, player),
|
||||
80 => self.tick_n080_gravekeeper(state, player),
|
||||
81 => self.tick_n081_giant_pignon(state, player),
|
||||
79 => self.tick_n079_mahin(state, players),
|
||||
80 => self.tick_n080_gravekeeper(state, players),
|
||||
81 => self.tick_n081_giant_pignon(state, players),
|
||||
82 => self.tick_n082_misery_standing(state),
|
||||
83 => self.tick_n083_igor_cutscene(state),
|
||||
84 => self.tick_n084_basu_projectile(state),
|
||||
85 => self.tick_n085_terminal(state, player),
|
||||
85 => self.tick_n085_terminal(state, players),
|
||||
86 => self.tick_n086_missile_pickup(state),
|
||||
87 => self.tick_n087_heart_pickup(state),
|
||||
88 => self.tick_n088_igor_boss(state, player),
|
||||
89 => self.tick_n089_igor_dead(state, player),
|
||||
88 => self.tick_n088_igor_boss(state, players),
|
||||
89 => self.tick_n089_igor_dead(state, players),
|
||||
91 => self.tick_n091_mimiga_cage(state),
|
||||
92 => self.tick_n092_sue_at_pc(state),
|
||||
93 => self.tick_n093_chaco(state, player),
|
||||
94 => self.tick_n094_kulala(state, player),
|
||||
93 => self.tick_n093_chaco(state, players),
|
||||
94 => self.tick_n094_kulala(state, players),
|
||||
95 => self.tick_n095_jelly(state),
|
||||
96 => self.tick_n096_fan_left(state, player),
|
||||
97 => self.tick_n097_fan_up(state, player),
|
||||
98 => self.tick_n098_fan_right(state, player),
|
||||
99 => self.tick_n099_fan_down(state, player),
|
||||
96 => self.tick_n096_fan_left(state, players),
|
||||
97 => self.tick_n097_fan_up(state, players),
|
||||
98 => self.tick_n098_fan_right(state, players),
|
||||
99 => self.tick_n099_fan_down(state, players),
|
||||
100 => self.tick_n100_grate(state),
|
||||
101 => self.tick_n101_malco_screen(state),
|
||||
102 => self.tick_n102_malco_computer_wave(state),
|
||||
103 => self.tick_n103_mannan_projectile(state),
|
||||
104 => self.tick_n104_frog(state, player),
|
||||
104 => self.tick_n104_frog(state, players),
|
||||
105 => self.tick_n105_hey_bubble_low(state),
|
||||
106 => self.tick_n106_hey_bubble_high(state),
|
||||
107 => self.tick_n107_malco_broken(state),
|
||||
108 => self.tick_n108_balfrog_projectile(state),
|
||||
109 => self.tick_n109_malco_powered_on(state, player),
|
||||
110 => self.tick_n110_puchi(state, player),
|
||||
111 => self.tick_n111_quote_teleport_out(state, player),
|
||||
112 => self.tick_n112_quote_teleport_in(state, player),
|
||||
114 => self.tick_n114_press(state, player),
|
||||
109 => self.tick_n109_malco_powered_on(state, players),
|
||||
110 => self.tick_n110_puchi(state, players),
|
||||
111 => self.tick_n111_quote_teleport_out(state, players),
|
||||
112 => self.tick_n112_quote_teleport_in(state, players),
|
||||
114 => self.tick_n114_press(state, players),
|
||||
129 => self.tick_n129_fireball_snake_trail(state),
|
||||
149 => self.tick_n149_horizontal_moving_block(state, player),
|
||||
150 => self.tick_n150_quote(state, player),
|
||||
149 => self.tick_n149_horizontal_moving_block(state, players),
|
||||
150 => self.tick_n150_quote(state, players),
|
||||
154 => self.tick_n154_gaudi_dead(state),
|
||||
157 => self.tick_n157_vertical_moving_block(state, player),
|
||||
157 => self.tick_n157_vertical_moving_block(state, players),
|
||||
192 => self.tick_n192_scooter(state),
|
||||
193 => self.tick_n193_broken_scooter(state),
|
||||
194 => self.tick_n194_broken_blue_robot(state),
|
||||
|
@ -281,7 +282,7 @@ impl GameEntity<(&mut Player, &BTreeMap<u16, RefCell<NPC>>, &mut Stage)> for NPC
|
|||
298 => self.tick_n298_intro_doctor(state),
|
||||
299 => self.tick_n299_intro_balrog_misery(state),
|
||||
300 => self.tick_n300_intro_demon_crown(state),
|
||||
361 => self.tick_n361_gaudi_dashing(state, player),
|
||||
361 => self.tick_n361_gaudi_dashing(state, players),
|
||||
_ => Ok(()),
|
||||
}?;
|
||||
|
||||
|
|
50
src/npc/npc_utils.rs
Normal file
50
src/npc/npc_utils.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use num_traits::abs;
|
||||
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
|
||||
impl NPC {
|
||||
pub fn get_closest_player_idx_mut<'a>(&self, players: &[&'a mut Player; 2]) -> usize {
|
||||
let mut max_dist = f64::MAX;
|
||||
let mut player_idx = 0;
|
||||
|
||||
for (idx, player) in players.iter().enumerate() {
|
||||
if !player.cond.alive() || player.cond.hidden() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dist_x = abs(self.x - player.x) as f64;
|
||||
let dist_y = abs(self.y - player.y) as f64;
|
||||
let dist = (dist_x * dist_x + dist_y * dist_y).sqrt();
|
||||
|
||||
if dist < max_dist {
|
||||
max_dist = dist;
|
||||
player_idx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
player_idx
|
||||
}
|
||||
|
||||
pub fn get_closest_player_mut<'a>(&self, players: [&'a mut Player; 2]) -> &'a mut Player {
|
||||
let mut max_dist = f64::MAX;
|
||||
let mut player_idx = 0;
|
||||
|
||||
for (idx, player) in players.iter().enumerate() {
|
||||
if !player.cond.alive() || player.cond.hidden() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dist_x = abs(self.x - player.x) as f64;
|
||||
let dist_y = abs(self.y - player.y) as f64;
|
||||
let dist = (dist_x * dist_x + dist_y * dist_y).sqrt();
|
||||
|
||||
if dist < max_dist {
|
||||
max_dist = dist;
|
||||
player_idx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
players[player_idx]
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ use crate::player::Player;
|
|||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub fn tick_n111_quote_teleport_out(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub fn tick_n111_quote_teleport_out(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.action_num = 1;
|
||||
|
@ -58,10 +58,9 @@ impl NPC {
|
|||
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
|
||||
self.anim_rect = state.constants.npc.n111_quote_teleport_out[self.anim_num as usize + dir_offset];
|
||||
|
||||
if player.equip.has_mimiga_mask() {
|
||||
self.anim_rect.top += 32;
|
||||
self.anim_rect.bottom += 32;
|
||||
}
|
||||
let offset = players[state.textscript_vm.executor_player.index()].get_texture_offset();
|
||||
self.anim_rect.top += offset;
|
||||
self.anim_rect.bottom += offset;
|
||||
|
||||
if self.action_num == 4 {
|
||||
self.anim_rect.bottom = self.anim_rect.top + self.action_counter / 4;
|
||||
|
@ -74,7 +73,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tick_n112_quote_teleport_in(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub fn tick_n112_quote_teleport_in(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.action_num = 1;
|
||||
|
@ -116,10 +115,9 @@ impl NPC {
|
|||
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
|
||||
self.anim_rect = state.constants.npc.n111_quote_teleport_out[self.anim_num as usize + dir_offset];
|
||||
|
||||
if player.equip.has_mimiga_mask() {
|
||||
self.anim_rect.top += 32;
|
||||
self.anim_rect.bottom += 32;
|
||||
}
|
||||
let offset = players[state.textscript_vm.executor_player.index()].get_texture_offset();
|
||||
self.anim_rect.top += offset;
|
||||
self.anim_rect.bottom += offset;
|
||||
|
||||
if self.action_num == 1 {
|
||||
self.anim_rect.bottom = self.anim_rect.top + self.action_counter / 4;
|
||||
|
@ -132,13 +130,14 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n150_quote(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n150_quote(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
|
||||
if self.tsc_direction > 10 {
|
||||
let player = &players[state.textscript_vm.executor_player.index()];
|
||||
self.x = player.x;
|
||||
self.y = player.y;
|
||||
|
||||
|
@ -278,10 +277,9 @@ impl NPC {
|
|||
self.anim_rect.bottom = self.anim_rect.top + self.action_counter / 4;
|
||||
}
|
||||
|
||||
if player.equip.has_mimiga_mask() {
|
||||
self.anim_rect.top += 32;
|
||||
self.anim_rect.bottom += 32;
|
||||
}
|
||||
let offset = players[state.textscript_vm.executor_player.index()].get_texture_offset();
|
||||
self.anim_rect.top += offset;
|
||||
self.anim_rect.bottom += offset;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use num_traits::abs;
|
|||
use crate::common::Direction;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n040_santa(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n040_santa(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 =>{
|
||||
if self.action_num == 0 {
|
||||
|
@ -22,6 +22,7 @@ impl NPC {
|
|||
self.anim_num = 1;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if abs(self.x - player.x) < 32 * 0x200
|
||||
&& self.y - 32 * 0x200 < player.y && self.y + 16 * 0x200 > player.y {
|
||||
self.direction = if self.x > player.x {
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::player::Player;
|
|||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub fn tick_n042_sue(&mut self, state: &mut SharedGameState, player: &Player, map: &BTreeMap<u16, RefCell<NPC>>) -> GameResult {
|
||||
pub fn tick_n042_sue(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], map: &BTreeMap<u16, RefCell<NPC>>) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -182,6 +182,7 @@ impl NPC {
|
|||
|
||||
self.vel_x = self.direction.vector_x() * 0x400;
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if self.x < player.x - 8 * 0x200 {
|
||||
self.direction = Direction::Right;
|
||||
self.action_num = 0;
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::player::Player;
|
|||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n060_toroko(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
|
||||
pub(crate) fn tick_n060_toroko(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -23,6 +23,7 @@ impl NPC {
|
|||
self.anim_num = 1;
|
||||
}
|
||||
|
||||
let player = self.get_closest_player_mut(players);
|
||||
if (self.x - (16 * 0x200) < player.x) && (self.x + (16 * 0x200) > player.x)
|
||||
&& (self.y - (16 * 0x200) < player.y) && (self.y + (16 * 0x200) > player.y) {
|
||||
if self.x > player.x {
|
||||
|
|
|
@ -107,7 +107,7 @@ impl Player {
|
|||
prev_y: 0,
|
||||
life: constants.my_char.life,
|
||||
max_life: constants.my_char.max_life,
|
||||
cond: Condition(0x80),
|
||||
cond: Condition(0),
|
||||
flags: Flag(0),
|
||||
equip: Equipment(0),
|
||||
direction: Direction::Right,
|
||||
|
@ -477,12 +477,12 @@ impl Player {
|
|||
// camera
|
||||
self.index_x = clamp(self.index_x + self.direction.vector_x() * 0x200, -0x8000, 0x8000);
|
||||
|
||||
if state.control_flags.control_enabled() && self.controller.move_up() {
|
||||
if state.control_flags.control_enabled() && self.controller.look_up() {
|
||||
self.index_y -= 0x200; // 1.0fix9
|
||||
if self.index_y < -0x8000 { // -64.0fix9
|
||||
self.index_y = -0x8000;
|
||||
}
|
||||
} else if state.control_flags.control_enabled() && self.controller.move_down() {
|
||||
} else if state.control_flags.control_enabled() && self.controller.look_down() {
|
||||
self.index_y += 0x200; // 1.0fix9
|
||||
if self.index_y > 0x8000 { // -64.0fix9
|
||||
self.index_y = 0x8000;
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::common::{Condition, Direction, Flag, Rect};
|
|||
use crate::inventory::{AddExperienceResult, Inventory};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::{ControlMode, Player};
|
||||
use crate::player::{ControlMode, Player, TargetPlayer};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl PhysicalEntity for Player {
|
||||
|
@ -236,7 +236,7 @@ impl Player {
|
|||
flags
|
||||
}
|
||||
|
||||
fn tick_npc_collision(&mut self, state: &mut SharedGameState, npc: &mut NPC, inventory: &mut Inventory) {
|
||||
fn tick_npc_collision(&mut self, id: TargetPlayer, state: &mut SharedGameState, npc: &mut NPC, inventory: &mut Inventory) {
|
||||
let flags: Flag;
|
||||
|
||||
if npc.npc_flags.solid_soft() {
|
||||
|
@ -289,6 +289,7 @@ impl Player {
|
|||
if npc.npc_flags.interactable() && !state.control_flags.interactions_disabled() && flags.0 != 0 && self.cond.interacted() {
|
||||
state.control_flags.set_tick_world(true);
|
||||
state.control_flags.set_interactions_disabled(true);
|
||||
state.textscript_vm.executor_player = id;
|
||||
state.textscript_vm.start_script(npc.event_num);
|
||||
self.cond.set_interacted(false);
|
||||
self.vel_x = 0;
|
||||
|
@ -298,6 +299,7 @@ impl Player {
|
|||
if npc.npc_flags.event_when_touched() && !state.control_flags.interactions_disabled() && flags.0 != 0 {
|
||||
state.control_flags.set_tick_world(true);
|
||||
state.control_flags.set_interactions_disabled(true);
|
||||
state.textscript_vm.executor_player = id;
|
||||
state.textscript_vm.start_script(npc.event_num);
|
||||
}
|
||||
|
||||
|
@ -315,17 +317,17 @@ impl Player {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tick_npc_collisions(&mut self, state: &mut SharedGameState, npc_map: &mut NPCMap, inventory: &mut Inventory) {
|
||||
pub fn tick_npc_collisions(&mut self, id: TargetPlayer, state: &mut SharedGameState, npc_map: &mut NPCMap, inventory: &mut Inventory) {
|
||||
for npc_cell in npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
if !npc.cond.alive() { continue; }
|
||||
|
||||
self.tick_npc_collision(state, npc.borrow_mut(), inventory);
|
||||
self.tick_npc_collision(id, state, npc.borrow_mut(), inventory);
|
||||
}
|
||||
|
||||
for boss_npc in npc_map.boss_map.parts.iter_mut() {
|
||||
if boss_npc.cond.alive() {
|
||||
self.tick_npc_collision(state, boss_npc, inventory);
|
||||
self.tick_npc_collision(id, state, boss_npc, inventory);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,6 @@ impl GameProfile {
|
|||
}
|
||||
|
||||
game_scene.player1.equip.0 = self.equipment as u16;
|
||||
game_scene.player1.cond.0 = 0x80;
|
||||
|
||||
game_scene.player1.x = self.pos_x as isize;
|
||||
game_scene.player1.y = self.pos_y as isize;
|
||||
|
@ -105,6 +104,11 @@ impl GameProfile {
|
|||
game_scene.player1.life = self.life;
|
||||
game_scene.player1.max_life = self.max_life;
|
||||
game_scene.player1.stars = clamp(self.stars, 0, 3) as u8;
|
||||
|
||||
game_scene.player2 = game_scene.player1.clone();
|
||||
game_scene.inventory_player2 = game_scene.inventory_player1.clone();
|
||||
|
||||
game_scene.player1.cond.0 = 0x80;
|
||||
}
|
||||
|
||||
pub fn dump(state: &mut SharedGameState, game_scene: &mut GameScene) -> GameProfile {
|
||||
|
|
|
@ -5,18 +5,21 @@ use ggez::graphics::{BlendMode, Color, Drawable, DrawParam, FilterMode, mint};
|
|||
use ggez::graphics::spritebatch::SpriteBatch;
|
||||
use ggez::nalgebra::{clamp, Vector2};
|
||||
use log::info;
|
||||
use num_traits::abs;
|
||||
|
||||
use crate::bullet::BulletManager;
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect};
|
||||
use crate::components::boss_life_bar::BossLifeBar;
|
||||
use crate::components::draw_common::{Alignment, draw_number};
|
||||
use crate::components::hud::HUD;
|
||||
use crate::components::stage_select::StageSelect;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::{Frame, UpdateTarget};
|
||||
use crate::inventory::{Inventory, TakeExperienceResult};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::{Player, PlayerAppearance};
|
||||
use crate::player::{Player, PlayerAppearance, TargetPlayer};
|
||||
use crate::rng::RNG;
|
||||
use crate::scene::Scene;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
|
@ -32,6 +35,8 @@ pub struct GameScene {
|
|||
pub stage: Stage,
|
||||
pub boss_life_bar: BossLifeBar,
|
||||
pub stage_select: StageSelect,
|
||||
pub hud_player1: HUD,
|
||||
pub hud_player2: HUD,
|
||||
pub frame: Frame,
|
||||
pub player1: Player,
|
||||
pub player2: Player,
|
||||
|
@ -44,10 +49,7 @@ pub struct GameScene {
|
|||
water_visible: bool,
|
||||
tex_background_name: String,
|
||||
tex_tileset_name: String,
|
||||
life_bar: u16,
|
||||
life_bar_counter: u16,
|
||||
map_name_counter: u16,
|
||||
weapon_x_pos: isize,
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
|
@ -58,14 +60,10 @@ pub enum TileLayer {
|
|||
Snack,
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum Alignment {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
static FACE_TEX: &str = "Face";
|
||||
static SWITCH_FACE_TEX: [&str; 4] = ["Face1", "Face2", "Face3", "Face4"];
|
||||
const FACE_TEX: &str = "Face";
|
||||
const SWITCH_FACE_TEX: [&str; 4] = ["Face1", "Face2", "Face3", "Face4"];
|
||||
const P2_LEFT_TEXT: &str = "< P2";
|
||||
const P2_RIGHT_TEXT: &str = "P2 >";
|
||||
|
||||
impl GameScene {
|
||||
pub fn new(state: &mut SharedGameState, ctx: &mut Context, id: usize) -> GameResult<Self> {
|
||||
|
@ -85,6 +83,8 @@ impl GameScene {
|
|||
inventory_player2: Inventory::new(),
|
||||
boss_life_bar: BossLifeBar::new(),
|
||||
stage_select: StageSelect::new(),
|
||||
hud_player1: HUD::new(Alignment::Left),
|
||||
hud_player2: HUD::new(Alignment::Right),
|
||||
frame: Frame {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
@ -102,10 +102,7 @@ impl GameScene {
|
|||
water_visible: true,
|
||||
tex_background_name,
|
||||
tex_tileset_name,
|
||||
life_bar: 0,
|
||||
life_bar_counter: 0,
|
||||
map_name_counter: 0,
|
||||
weapon_x_pos: 16,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -113,125 +110,18 @@ impl GameScene {
|
|||
self.map_name_counter = ticks;
|
||||
}
|
||||
|
||||
fn draw_number(&self, x: f32, y: f32, val: usize, align: Alignment, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
let n = val.to_string();
|
||||
let align_offset = if align == Alignment::Right { n.len() as f32 * 8.0 } else { 0.0 };
|
||||
|
||||
for (offset, chr) in n.chars().enumerate() {
|
||||
let idx = chr as u16 - '0' as u16;
|
||||
batch.add_rect(x - align_offset + offset as f32 * 8.0, y, &Rect::new_size(idx * 8, 56, 8, 8));
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
Ok(())
|
||||
pub fn add_player2(&mut self) {
|
||||
self.player2.cond.set_alive(true);
|
||||
self.player2.cond.set_hidden(self.player1.cond.hidden());
|
||||
self.player2.appearance = PlayerAppearance::YellowQuote;
|
||||
self.player2.x = self.player1.x;
|
||||
self.player2.y = self.player1.y;
|
||||
self.player2.vel_x = self.player1.vel_x;
|
||||
self.player2.vel_y = self.player1.vel_y;
|
||||
}
|
||||
|
||||
fn draw_hud(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
// none
|
||||
let weap_x = self.weapon_x_pos as f32;
|
||||
let (ammo, max_ammo) = self.inventory_player1.get_current_ammo();
|
||||
let (xp, max_xp, max_level) = self.inventory_player1.get_current_max_exp(&state.constants);
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
if max_ammo == 0 {
|
||||
batch.add_rect(weap_x + 48.0, 16.0,
|
||||
&Rect::new_size(80, 48, 16, 8));
|
||||
batch.add_rect(weap_x + 48.0, 24.0,
|
||||
&Rect::new_size(80, 48, 16, 8));
|
||||
}
|
||||
|
||||
// per
|
||||
batch.add_rect(weap_x + 32.0, 24.0,
|
||||
&Rect::new_size(72, 48, 8, 8));
|
||||
// lv
|
||||
batch.add_rect(weap_x, 32.0,
|
||||
&Rect::new_size(80, 80, 16, 8));
|
||||
// xp box
|
||||
batch.add_rect(weap_x + 24.0, 32.0,
|
||||
&Rect::new_size(0, 72, 40, 8));
|
||||
|
||||
if max_level {
|
||||
batch.add_rect(weap_x + 24.0, 32.0,
|
||||
&Rect::new_size(40, 72, 40, 8));
|
||||
} else if max_xp > 0 {
|
||||
// xp bar
|
||||
let bar_width = (xp as f32 / max_xp as f32 * 40.0) as u16;
|
||||
|
||||
batch.add_rect(weap_x + 24.0, 32.0,
|
||||
&Rect::new_size(0, 80, bar_width, 8));
|
||||
}
|
||||
|
||||
if self.player1.max_life != 0 {
|
||||
// life box
|
||||
batch.add_rect(16.0, 40.0,
|
||||
&Rect::new_size(0, 40, 64, 8));
|
||||
// yellow bar
|
||||
batch.add_rect(40.0, 40.0,
|
||||
&Rect::new_size(0, 32, (self.life_bar * 40) / self.player1.max_life, 8));
|
||||
// life
|
||||
batch.add_rect(40.0, 40.0,
|
||||
&Rect::new_size(0, 24, (self.player1.life * 40) / self.player1.max_life, 8));
|
||||
}
|
||||
|
||||
if self.player1.air_counter > 0 {
|
||||
let rect = if self.player1.air % 30 > 10 {
|
||||
Rect::new_size(112, 72, 32, 8)
|
||||
} else {
|
||||
Rect::new_size(112, 80, 32, 8)
|
||||
};
|
||||
|
||||
batch.add_rect((state.canvas_size.0 / 2.0).floor() - 40.0,
|
||||
(state.canvas_size.1 / 2.0).floor(), &rect);
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
|
||||
|
||||
let weapon_count = self.inventory_player1.get_weapon_count();
|
||||
if weapon_count != 0 {
|
||||
let current_weapon = self.inventory_player1.get_current_weapon_idx() as isize;
|
||||
let mut rect = Rect::new(0, 0, 0, 16);
|
||||
|
||||
for a in 0..weapon_count {
|
||||
let mut pos_x = ((a as isize - current_weapon) as f32 * 16.0) + weap_x;
|
||||
|
||||
if pos_x < 8.0 {
|
||||
pos_x += 48.0 + weapon_count as f32 * 16.0;
|
||||
} else if pos_x >= 24.0 {
|
||||
pos_x += 48.0;
|
||||
}
|
||||
|
||||
if pos_x >= 72.0 + ((weapon_count - 1) as f32 * 16.0) {
|
||||
pos_x -= 48.0 + weapon_count as f32 * 16.0;
|
||||
} else if pos_x < 72.0 && pos_x >= 24.0 {
|
||||
pos_x -= 48.0;
|
||||
}
|
||||
|
||||
if let Some(weapon) = self.inventory_player1.get_weapon(a) {
|
||||
rect.left = weapon.wtype as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
batch.add_rect(pos_x, 16.0, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
if self.player1.air_counter > 0 && self.player1.air_counter % 6 < 4 {
|
||||
self.draw_number((state.canvas_size.0 / 2.0).floor() + 8.0,
|
||||
(state.canvas_size.1 / 2.0).floor(),
|
||||
(self.player1.air / 10) as usize, Alignment::Left, state, ctx)?;
|
||||
}
|
||||
|
||||
if max_ammo != 0 {
|
||||
self.draw_number(weap_x + 64.0, 16.0, ammo as usize, Alignment::Right, state, ctx)?;
|
||||
self.draw_number(weap_x + 64.0, 24.0, max_ammo as usize, Alignment::Right, state, ctx)?;
|
||||
}
|
||||
self.draw_number(weap_x + 24.0, 32.0, self.inventory_player1.get_current_level() as usize, Alignment::Right, state, ctx)?;
|
||||
self.draw_number(40.0, 40.0, self.life_bar as usize, Alignment::Right, state, ctx)?;
|
||||
|
||||
Ok(())
|
||||
pub fn drop_player2(&mut self) {
|
||||
self.player2.cond.set_alive(false);
|
||||
}
|
||||
|
||||
fn draw_background(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
|
@ -970,7 +860,7 @@ impl GameScene {
|
|||
// todo show damage
|
||||
}
|
||||
}
|
||||
} else if !bullet.weapon_flags.hit_right_slope()
|
||||
} else if !bullet.weapon_flags.flag_x10()
|
||||
&& bullet.btype != 13 && bullet.btype != 14 && bullet.btype != 15
|
||||
&& bullet.btype != 28 && bullet.btype != 29 && bullet.btype != 30 {
|
||||
state.create_caret((bullet.x + npc.x) / 2, (bullet.y + npc.y) / 2, CaretType::ProjectileDissipation, Direction::Right);
|
||||
|
@ -1067,7 +957,7 @@ impl GameScene {
|
|||
}
|
||||
} 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() {
|
||||
} else if !bullet.weapon_flags.flag_x10() {
|
||||
state.create_caret(bullet.x, bullet.y, CaretType::ProjectileDissipation, Direction::Right);
|
||||
state.sound_manager.play_sfx(31);
|
||||
bullet.life = 0;
|
||||
|
@ -1089,6 +979,11 @@ impl GameScene {
|
|||
}
|
||||
|
||||
fn tick_world(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
self.hud_player1.visible = self.player1.cond.alive();
|
||||
self.hud_player2.visible = self.player2.cond.alive();
|
||||
self.hud_player1.has_player2 = self.player2.cond.alive() && !self.player2.cond.hidden();
|
||||
self.hud_player2.has_player2 = self.player1.cond.alive() && !self.player1.cond.hidden();
|
||||
|
||||
self.player1.current_weapon = {
|
||||
if let Some(weapon) = self.inventory_player1.get_current_weapon_mut() {
|
||||
weapon.wtype as u8
|
||||
|
@ -1096,7 +991,15 @@ impl GameScene {
|
|||
0
|
||||
}
|
||||
};
|
||||
self.player2.current_weapon = {
|
||||
if let Some(weapon) = self.inventory_player2.get_current_weapon_mut() {
|
||||
weapon.wtype as u8
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
self.player1.tick(state, ())?;
|
||||
self.player2.tick(state, ())?;
|
||||
|
||||
if self.player1.damage > 0 {
|
||||
let xp_loss = self.player1.damage * if self.player1.equip.has_arms_barrier() { 1 } else { 2 };
|
||||
|
@ -1110,11 +1013,25 @@ impl GameScene {
|
|||
self.player1.damage = 0;
|
||||
}
|
||||
|
||||
for npc_cell in self.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
if self.player2.damage > 0 {
|
||||
let xp_loss = self.player2.damage * if self.player2.equip.has_arms_barrier() { 1 } else { 2 };
|
||||
match self.inventory_player2.take_xp(xp_loss, state) {
|
||||
TakeExperienceResult::LevelDown if self.player2.life > 0 => {
|
||||
state.create_caret(self.player2.x, self.player2.y, CaretType::LevelUp, Direction::Right);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if npc.cond.alive() {
|
||||
npc.tick(state, (&mut self.player1, &self.npc_map.npcs, &mut self.stage))?;
|
||||
self.player2.damage = 0;
|
||||
}
|
||||
|
||||
{
|
||||
for npc_cell in self.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
if npc.cond.alive() {
|
||||
npc.tick(state, ([&mut self.player1, &mut self.player2], &self.npc_map.npcs, &mut self.stage))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.npc_map.boss_map.tick(state, (&mut self.player1, &self.npc_map.npcs, &mut self.stage))?;
|
||||
|
@ -1122,7 +1039,10 @@ impl GameScene {
|
|||
self.npc_map.garbage_collect();
|
||||
|
||||
self.player1.tick_map_collisions(state, &mut self.stage);
|
||||
self.player1.tick_npc_collisions(state, &mut self.npc_map, &mut self.inventory_player1);
|
||||
self.player1.tick_npc_collisions(TargetPlayer::Player1, state, &mut self.npc_map, &mut self.inventory_player1);
|
||||
|
||||
self.player2.tick_map_collisions(state, &mut self.stage);
|
||||
self.player2.tick_npc_collisions(TargetPlayer::Player2, state, &mut self.npc_map, &mut self.inventory_player2);
|
||||
|
||||
for npc_cell in self.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
@ -1142,13 +1062,23 @@ impl GameScene {
|
|||
self.tick_npc_bullet_collissions(state);
|
||||
self.npc_map.process_npc_changes(&self.player1, state);
|
||||
|
||||
self.bullet_manager.tick_bullets(state, &self.player1, &mut self.stage);
|
||||
self.bullet_manager.tick_bullets(state, [&self.player1, &self.player2], &mut self.stage);
|
||||
state.tick_carets();
|
||||
|
||||
match self.frame.update_target {
|
||||
UpdateTarget::Player => {
|
||||
self.frame.target_x = self.player1.target_x;
|
||||
self.frame.target_y = self.player1.target_y;
|
||||
if self.player2.cond.alive() && !self.player2.cond.hidden()
|
||||
&& abs(self.player1.x - self.player2.x) < 240 * 0x200
|
||||
&& abs(self.player1.y - self.player2.y) < 200 * 0x200 {
|
||||
self.frame.target_x = (self.player1.target_x * 2 + self.player2.target_x) / 3;
|
||||
self.frame.target_y = (self.player1.target_y * 2 + self.player2.target_y) / 3;
|
||||
|
||||
self.frame.target_x = clamp(self.frame.target_x, self.player1.x - 0x8000, self.player1.x + 0x8000);
|
||||
self.frame.target_y = clamp(self.frame.target_y, self.player1.y, self.player1.y);
|
||||
} else {
|
||||
self.frame.target_x = self.player1.target_x;
|
||||
self.frame.target_y = self.player1.target_y;
|
||||
}
|
||||
}
|
||||
UpdateTarget::NPC(npc_id) => {
|
||||
if let Some(npc_cell) = self.npc_map.npcs.get(&npc_id) {
|
||||
|
@ -1173,35 +1103,43 @@ impl GameScene {
|
|||
|
||||
if state.control_flags.control_enabled() {
|
||||
if let Some(weapon) = self.inventory_player1.get_current_weapon_mut() {
|
||||
weapon.shoot_bullet(&self.player1, &mut self.bullet_manager, state);
|
||||
weapon.shoot_bullet(&self.player1, TargetPlayer::Player1, &mut self.bullet_manager, state);
|
||||
}
|
||||
|
||||
if self.player1.controller.trigger_next_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
self.inventory_player1.next_weapon();
|
||||
self.weapon_x_pos = 32;
|
||||
if let Some(weapon) = self.inventory_player2.get_current_weapon_mut() {
|
||||
weapon.shoot_bullet(&self.player2, TargetPlayer::Player2, &mut self.bullet_manager, state);
|
||||
}
|
||||
|
||||
if self.player1.controller.trigger_prev_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
self.inventory_player1.prev_weapon();
|
||||
self.weapon_x_pos = 0;
|
||||
}
|
||||
|
||||
// update health bar
|
||||
if self.life_bar < self.player1.life as u16 {
|
||||
self.life_bar = self.player1.life as u16;
|
||||
}
|
||||
|
||||
if self.life_bar > self.player1.life as u16 {
|
||||
self.life_bar_counter += 1;
|
||||
if self.life_bar_counter > 30 {
|
||||
self.life_bar -= 1;
|
||||
if self.player1.cond.alive() {
|
||||
if self.player1.controller.trigger_next_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
self.inventory_player1.next_weapon();
|
||||
self.hud_player1.weapon_x_pos = 32;
|
||||
}
|
||||
|
||||
if self.player1.controller.trigger_prev_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
self.inventory_player1.prev_weapon();
|
||||
self.hud_player1.weapon_x_pos = 0;
|
||||
}
|
||||
} else {
|
||||
self.life_bar_counter = 0;
|
||||
}
|
||||
|
||||
if self.player2.cond.alive() {
|
||||
if self.player2.controller.trigger_next_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
self.inventory_player2.next_weapon();
|
||||
self.hud_player2.weapon_x_pos = 32;
|
||||
}
|
||||
|
||||
if self.player2.controller.trigger_prev_weapon() {
|
||||
state.sound_manager.play_sfx(4);
|
||||
self.inventory_player2.prev_weapon();
|
||||
self.hud_player2.weapon_x_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
self.hud_player1.tick(state, (&self.player1, &self.inventory_player1))?;
|
||||
self.hud_player2.tick(state, (&self.player2, &self.inventory_player2))?;
|
||||
self.boss_life_bar.tick(state, &self.npc_map)?;
|
||||
}
|
||||
|
||||
|
@ -1324,6 +1262,8 @@ impl Scene for GameScene {
|
|||
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
self.player1.controller.update(state, ctx)?;
|
||||
self.player1.controller.update_trigger();
|
||||
self.player2.controller.update(state, ctx)?;
|
||||
self.player2.controller.update_trigger();
|
||||
|
||||
if self.intro_mode && (self.player1.controller.trigger_menu_ok() || self.tick >= 500) {
|
||||
state.next_scene = Some(Box::new(TitleScene::new()));
|
||||
|
@ -1339,12 +1279,6 @@ impl Scene for GameScene {
|
|||
self.map_name_counter -= 1;
|
||||
}
|
||||
|
||||
if self.weapon_x_pos > 16 {
|
||||
self.weapon_x_pos -= 2;
|
||||
} else if self.weapon_x_pos < 16 {
|
||||
self.weapon_x_pos += 2;
|
||||
}
|
||||
|
||||
match state.fade_state {
|
||||
FadeState::FadeOut(tick, direction) if tick < 15 => {
|
||||
state.fade_state = FadeState::FadeOut(tick + 1, direction);
|
||||
|
@ -1371,6 +1305,8 @@ impl Scene for GameScene {
|
|||
self.frame.prev_y = self.frame.y;
|
||||
self.player1.prev_x = self.player1.x;
|
||||
self.player1.prev_y = self.player1.y;
|
||||
self.player2.prev_x = self.player2.x;
|
||||
self.player2.prev_y = self.player2.y;
|
||||
|
||||
for npc_cell in self.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
@ -1431,6 +1367,7 @@ impl Scene for GameScene {
|
|||
npc.draw(state, ctx, &self.frame)?;
|
||||
}
|
||||
self.draw_bullets(state, ctx)?;
|
||||
self.player2.draw(state, ctx, &self.frame)?;
|
||||
self.player1.draw(state, ctx, &self.frame)?;
|
||||
if state.settings.shader_effects && self.water_visible {
|
||||
self.draw_water(state, ctx)?;
|
||||
|
@ -1451,8 +1388,34 @@ impl Scene for GameScene {
|
|||
self.draw_black_bars(state, ctx)?;
|
||||
|
||||
if state.control_flags.control_enabled() {
|
||||
self.draw_hud(state, ctx)?;
|
||||
self.hud_player1.draw(state, ctx, &self.frame)?;
|
||||
self.hud_player2.draw(state, ctx, &self.frame)?;
|
||||
self.boss_life_bar.draw(state, ctx, &self.frame)?;
|
||||
|
||||
if self.player2.cond.alive() && !self.player2.cond.hidden() {
|
||||
let y = interpolate_fix9_scale(self.player2.prev_y - self.frame.prev_y, self.player2.y - self.frame.y, state.frame_time);
|
||||
let y = clamp(y, 8.0, state.canvas_size.1 - 8.0 - state.font.line_height(&state.constants));
|
||||
|
||||
if self.player2.x + 8 * 0x200 < self.frame.x {
|
||||
state.font.draw_colored_text(P2_LEFT_TEXT.chars(),
|
||||
9.0, y + 1.0,
|
||||
(0, 0, 130), &state.constants, &mut state.texture_set, ctx)?;
|
||||
|
||||
state.font.draw_colored_text(P2_LEFT_TEXT.chars(),
|
||||
8.0, y,
|
||||
(96, 96, 255), &state.constants, &mut state.texture_set, ctx)?;
|
||||
} else if self.player2.x - 8 * 0x200 > self.frame.x + state.canvas_size.0 as isize * 0x200 {
|
||||
let width = state.font.text_width(P2_RIGHT_TEXT.chars(), &state.constants);
|
||||
|
||||
state.font.draw_colored_text(P2_RIGHT_TEXT.chars(),
|
||||
state.canvas_size.0 - width - 8.0 + 1.0, y + 1.0,
|
||||
(0, 0, 130), &state.constants, &mut state.texture_set, ctx)?;
|
||||
|
||||
state.font.draw_colored_text(P2_RIGHT_TEXT.chars(),
|
||||
state.canvas_size.0 - width - 8.0, y,
|
||||
(96, 96, 255), &state.constants, &mut state.texture_set, ctx)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if state.textscript_vm.mode == ScriptMode::StageSelect {
|
||||
|
@ -1479,7 +1442,7 @@ impl Scene for GameScene {
|
|||
self.draw_debug_outlines(state, ctx)?;
|
||||
}
|
||||
|
||||
self.draw_number(state.canvas_size.0 - 8.0, 8.0, timer::fps(ctx) as usize, Alignment::Right, state, ctx)?;
|
||||
draw_number(state.canvas_size.0 - 8.0, 8.0, timer::fps(ctx) as usize, Alignment::Right, state, ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -115,11 +115,15 @@ impl Scene for TitleScene {
|
|||
self.option_menu.push_entry(MenuEntry::Active("Back".to_string()));
|
||||
self.option_menu.height = self.option_menu.entries.len() as u16 * 14 + 6;
|
||||
|
||||
self.controller.update(state, ctx)?;
|
||||
self.controller.update_trigger();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
self.controller.update(state, ctx)?;
|
||||
self.controller.update_trigger();
|
||||
|
||||
self.main_menu.x = ((state.canvas_size.0 - self.main_menu.width as f32) / 2.0).floor() as isize;
|
||||
self.main_menu.y = ((state.canvas_size.1 + 70.0 - self.main_menu.height as f32) / 2.0).floor() as isize;
|
||||
|
|
|
@ -204,6 +204,7 @@ impl SharedGameState {
|
|||
|
||||
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
|
||||
let mut next_scene = GameScene::new(self, ctx, 13)?;
|
||||
next_scene.player1.cond.set_alive(true);
|
||||
next_scene.player1.x = 10 * 16 * 0x200;
|
||||
next_scene.player1.y = 8 * 16 * 0x200;
|
||||
self.fade_state = FadeState::Hidden;
|
||||
|
|
|
@ -22,7 +22,7 @@ use crate::engine_constants::EngineConstants;
|
|||
use crate::entity::GameEntity;
|
||||
use crate::frame::UpdateTarget;
|
||||
use crate::npc::NPCMap;
|
||||
use crate::player::ControlMode;
|
||||
use crate::player::{ControlMode, TargetPlayer};
|
||||
use crate::profile::GameProfile;
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
|
@ -267,7 +267,8 @@ pub enum OpCode {
|
|||
// ---- Cave Story+ (Switch) specific opcodes ----
|
||||
/// <HM2, HMC for player 2
|
||||
HM2,
|
||||
/// <2MVxxxx, looks like MOV for player 2, purpose of xxxx operand is still unknown
|
||||
/// <2MVxxxx, Move player 2, operand is either direction (for 0000-0003),
|
||||
/// or event number/npc id/anything else?
|
||||
#[strum(serialize = "2MV")]
|
||||
S2MV,
|
||||
/// <INJxxxx:yyyy:zzzz, Jumps to event zzzz if amount of item xxxx equals yyyy
|
||||
|
@ -282,9 +283,9 @@ pub enum OpCode {
|
|||
PSH,
|
||||
/// <POP, Restores text script state from stack and resumes previous event.
|
||||
POP,
|
||||
/// <KE2, Seen in ArmsItem.tsc, unknown purpose, related to puppies
|
||||
/// <KEY related to player 2?
|
||||
KE2,
|
||||
/// <FR2, likely related to <KE2, seen at end of events using it
|
||||
/// <FRE related to player 2?
|
||||
FR2,
|
||||
|
||||
// ---- Custom opcodes, for use by modders ----
|
||||
|
@ -364,6 +365,8 @@ pub struct TextScriptVM {
|
|||
pub stack: Vec<TextScriptExecutionState>,
|
||||
pub flags: TextScriptFlags,
|
||||
pub mode: ScriptMode,
|
||||
/// The player who triggered the event.
|
||||
pub executor_player: TargetPlayer,
|
||||
/// Toggle for non-strict TSC parsing because English versions of CS+ (both AG and Nicalis release)
|
||||
/// modified the events carelessly and since original Pixel's engine hasn't enforced constraints
|
||||
/// while parsing no one noticed them.
|
||||
|
@ -446,16 +449,17 @@ impl TextScriptVM {
|
|||
},
|
||||
state: TextScriptExecutionState::Ended,
|
||||
stack: Vec::with_capacity(6),
|
||||
flags: TextScriptFlags(0),
|
||||
mode: ScriptMode::Map,
|
||||
executor_player: TargetPlayer::Player1,
|
||||
strict_mode: false,
|
||||
suspend: true,
|
||||
flags: TextScriptFlags(0),
|
||||
item: 0,
|
||||
face: 0,
|
||||
item: 0,
|
||||
current_line: TextScriptLine::Line1,
|
||||
line_1: Vec::with_capacity(24),
|
||||
line_2: Vec::with_capacity(24),
|
||||
line_3: Vec::with_capacity(24),
|
||||
mode: ScriptMode::Map,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -704,6 +708,7 @@ impl TextScriptVM {
|
|||
state.textscript_vm.stack.clear();
|
||||
|
||||
game_scene.player1.cond.set_interacted(false);
|
||||
game_scene.player2.cond.set_interacted(false);
|
||||
game_scene.frame.update_target = UpdateTarget::Player;
|
||||
|
||||
exec_state = TextScriptExecutionState::Ended;
|
||||
|
@ -758,21 +763,38 @@ impl TextScriptVM {
|
|||
let new_direction = read_cur_varint(&mut cursor)? as usize;
|
||||
if let Some(direction) = Direction::from_int(new_direction) {
|
||||
game_scene.player1.direction = direction;
|
||||
game_scene.player2.direction = direction;
|
||||
}
|
||||
|
||||
game_scene.player1.vel_x = 0;
|
||||
game_scene.player2.vel_x = 0;
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::MYB => {
|
||||
let new_direction = read_cur_varint(&mut cursor)? as usize;
|
||||
|
||||
game_scene.player1.vel_y = -0x200;
|
||||
game_scene.player2.vel_y = -0x200;
|
||||
|
||||
if let Some(direction) = Direction::from_int_facing(new_direction) {
|
||||
match direction {
|
||||
Direction::Left => game_scene.player1.vel_x = 0x200,
|
||||
Direction::Up => game_scene.player1.vel_y = -0x200,
|
||||
Direction::Right => game_scene.player1.vel_x = -0x200,
|
||||
Direction::Bottom => game_scene.player1.vel_y = 0x200,
|
||||
Direction::Left => {
|
||||
game_scene.player1.vel_x = 0x200;
|
||||
game_scene.player2.vel_x = 0x200;
|
||||
},
|
||||
Direction::Up => {
|
||||
game_scene.player1.vel_y = -0x200;
|
||||
game_scene.player2.vel_y = -0x200;
|
||||
},
|
||||
Direction::Right => {
|
||||
game_scene.player1.vel_x = -0x200;
|
||||
game_scene.player2.vel_x = -0x200;
|
||||
},
|
||||
Direction::Bottom => {
|
||||
game_scene.player1.vel_y = 0x200;
|
||||
game_scene.player2.vel_y = 0x200;
|
||||
},
|
||||
Direction::FacingPlayer => {
|
||||
// todo npc direction dependent bump
|
||||
}
|
||||
|
@ -783,11 +805,13 @@ impl TextScriptVM {
|
|||
}
|
||||
OpCode::SMC => {
|
||||
game_scene.player1.cond.set_hidden(false);
|
||||
game_scene.player2.cond.set_hidden(false);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::HMC => {
|
||||
game_scene.player1.cond.set_hidden(true);
|
||||
game_scene.player2.cond.set_hidden(true);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -936,6 +960,8 @@ impl TextScriptVM {
|
|||
let life = read_cur_varint(&mut cursor)? as u16;
|
||||
game_scene.player1.life += life;
|
||||
game_scene.player1.max_life += life;
|
||||
game_scene.player2.life += life;
|
||||
game_scene.player2.max_life += life;
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -1009,11 +1035,17 @@ impl TextScriptVM {
|
|||
let mut new_scene = GameScene::new(state, ctx, map_id)?;
|
||||
new_scene.intro_mode = game_scene.intro_mode;
|
||||
new_scene.inventory_player1 = game_scene.inventory_player1.clone();
|
||||
new_scene.inventory_player2 = game_scene.inventory_player2.clone();
|
||||
new_scene.player1 = game_scene.player1.clone();
|
||||
new_scene.player1.vel_x = 0;
|
||||
new_scene.player1.vel_y = 0;
|
||||
new_scene.player1.x = pos_x;
|
||||
new_scene.player1.y = pos_y;
|
||||
new_scene.player2 = game_scene.player2.clone();
|
||||
new_scene.player2.vel_x = 0;
|
||||
new_scene.player2.vel_y = 0;
|
||||
new_scene.player2.x = pos_x;
|
||||
new_scene.player2.y = pos_y;
|
||||
|
||||
state.control_flags.set_tick_world(true);
|
||||
state.textscript_vm.flags.0 = 0;
|
||||
|
@ -1033,10 +1065,34 @@ impl TextScriptVM {
|
|||
let pos_x = read_cur_varint(&mut cursor)? as isize * 16 * 0x200;
|
||||
let pos_y = read_cur_varint(&mut cursor)? as isize * 16 * 0x200;
|
||||
|
||||
game_scene.player1.vel_x = 0;
|
||||
game_scene.player1.vel_y = 0;
|
||||
game_scene.player1.x = pos_x;
|
||||
game_scene.player1.y = pos_y;
|
||||
for player in [&mut game_scene.player1, &mut game_scene.player2].iter_mut() {
|
||||
player.vel_x = 0;
|
||||
player.vel_y = 0;
|
||||
player.x = pos_x;
|
||||
player.y = pos_y;
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::S2MV => {
|
||||
let param = read_cur_varint(&mut cursor)? as usize;
|
||||
|
||||
let (executor, partner) = match state.textscript_vm.executor_player {
|
||||
TargetPlayer::Player1 => (&game_scene.player1, &mut game_scene.player2),
|
||||
TargetPlayer::Player2 => (&game_scene.player2, &mut game_scene.player1),
|
||||
};
|
||||
|
||||
match param {
|
||||
0 | 1 => {
|
||||
partner.vel_x = 0;
|
||||
partner.vel_y = 0;
|
||||
partner.x = executor.x + if param == 0 { -16 * 0x200 } else { 16 * 0x200 };
|
||||
partner.y = executor.y;
|
||||
}
|
||||
_ => {
|
||||
log::warn!("stub: <2MV unknown param");
|
||||
}
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -1320,6 +1376,7 @@ impl TextScriptVM {
|
|||
let life = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
game_scene.player1.life = clamp(game_scene.player1.life + life, 0, game_scene.player1.max_life);
|
||||
game_scene.player2.life = clamp(game_scene.player2.life + life, 0, game_scene.player2.max_life);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -1330,6 +1387,10 @@ impl TextScriptVM {
|
|||
game_scene.inventory_player1.add_item(item_id);
|
||||
}
|
||||
|
||||
if !game_scene.inventory_player2.has_item(item_id) {
|
||||
game_scene.inventory_player2.add_item(item_id);
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::IpN => {
|
||||
|
@ -1340,12 +1401,17 @@ impl TextScriptVM {
|
|||
game_scene.inventory_player1.add_item(item_id);
|
||||
}
|
||||
|
||||
if game_scene.inventory_player2.has_item_amount(item_id, Ordering::Less, amount) {
|
||||
game_scene.inventory_player2.add_item(item_id);
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::ITm => {
|
||||
let item_id = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
game_scene.inventory_player1.consume_item(item_id);
|
||||
game_scene.inventory_player2.consume_item(item_id);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -1356,6 +1422,7 @@ impl TextScriptVM {
|
|||
|
||||
if let Some(wtype) = weapon_type {
|
||||
game_scene.inventory_player1.add_weapon(wtype, max_ammo);
|
||||
game_scene.inventory_player2.add_weapon(wtype, max_ammo);
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
@ -1366,17 +1433,20 @@ impl TextScriptVM {
|
|||
|
||||
if let Some(wtype) = weapon_type {
|
||||
game_scene.inventory_player1.remove_weapon(wtype);
|
||||
game_scene.inventory_player2.remove_weapon(wtype);
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::AEp => {
|
||||
game_scene.inventory_player1.refill_all_ammo();
|
||||
game_scene.inventory_player2.refill_all_ammo();
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::ZAM => {
|
||||
game_scene.inventory_player1.reset_all_weapon_xp();
|
||||
game_scene.inventory_player2.reset_all_weapon_xp();
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -1428,7 +1498,7 @@ impl TextScriptVM {
|
|||
// One operand codes
|
||||
OpCode::NUM | OpCode::MPp | OpCode::SKm | OpCode::SKp |
|
||||
OpCode::UNJ | OpCode::MPJ | OpCode::XX1 | OpCode::SIL |
|
||||
OpCode::SSS | OpCode::ACH | OpCode::S2MV => {
|
||||
OpCode::SSS | OpCode::ACH => {
|
||||
let par_a = read_cur_varint(&mut cursor)?;
|
||||
|
||||
log::warn!("unimplemented opcode: {:?} {}", op, par_a);
|
||||
|
@ -1464,7 +1534,7 @@ impl TextScriptVM {
|
|||
|
||||
if tick_npc != 0 {
|
||||
if let Some(npc) = game_scene.npc_map.npcs.get(&tick_npc) {
|
||||
npc.borrow_mut().tick(state, (&mut game_scene.player1, &game_scene.npc_map.npcs, &mut game_scene.stage))?;
|
||||
npc.borrow_mut().tick(state, ([&mut game_scene.player1, &mut game_scene.player2], &game_scene.npc_map.npcs, &mut game_scene.stage))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use num_derive::FromPrimitive;
|
|||
use crate::bullet::BulletManager;
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::Direction;
|
||||
use crate::player::Player;
|
||||
use crate::player::{Player, TargetPlayer};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, FromPrimitive)]
|
||||
|
@ -84,8 +84,8 @@ impl Weapon {
|
|||
false
|
||||
}
|
||||
|
||||
fn shoot_bullet_snake(&mut self, player: &Player, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([1, 2, 3]) < 4 {
|
||||
fn shoot_bullet_snake(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([1, 2, 3], player_id) < 4 {
|
||||
let btype = match self.level {
|
||||
WeaponLevel::Level1 => { 1 }
|
||||
WeaponLevel::Level2 => { 2 }
|
||||
|
@ -101,11 +101,11 @@ impl Weapon {
|
|||
if player.up {
|
||||
match player.direction {
|
||||
Direction::Left => {
|
||||
bullet_manager.create_bullet(player.x - 3 * 0x200, player.y - 10 * 0x200, btype, Direction::Up, &state.constants);
|
||||
bullet_manager.create_bullet(player.x - 3 * 0x200, player.y - 10 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||
state.create_caret(player.x - 3 * 0x200, player.y - 10 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
Direction::Right => {
|
||||
bullet_manager.create_bullet(player.x + 3 * 0x200, player.y - 10 * 0x200, btype, Direction::Up, &state.constants);
|
||||
bullet_manager.create_bullet(player.x + 3 * 0x200, player.y - 10 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||
state.create_caret(player.x + 3 * 0x200, player.y - 10 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -113,11 +113,11 @@ impl Weapon {
|
|||
} else if player.down {
|
||||
match player.direction {
|
||||
Direction::Left => {
|
||||
bullet_manager.create_bullet(player.x - 3 * 0x200, player.y + 10 * 0x200, btype, Direction::Bottom, &state.constants);
|
||||
bullet_manager.create_bullet(player.x - 3 * 0x200, player.y + 10 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||
state.create_caret(player.x - 3 * 0x200, player.y + 10 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
Direction::Right => {
|
||||
bullet_manager.create_bullet(player.x + 3 * 0x200, player.y + 10 * 0x200, btype, Direction::Bottom, &state.constants);
|
||||
bullet_manager.create_bullet(player.x + 3 * 0x200, player.y + 10 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||
state.create_caret(player.x + 3 * 0x200, player.y + 10 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -125,11 +125,11 @@ impl Weapon {
|
|||
} else {
|
||||
match player.direction {
|
||||
Direction::Left => {
|
||||
bullet_manager.create_bullet(player.x - 6 * 0x200, player.y + 2 * 0x200, btype, Direction::Left, &state.constants);
|
||||
bullet_manager.create_bullet(player.x - 6 * 0x200, player.y + 2 * 0x200, btype, player_id, Direction::Left, &state.constants);
|
||||
state.create_caret(player.x - 12 * 0x200, player.y + 2 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
Direction::Right => {
|
||||
bullet_manager.create_bullet(player.x + 6 * 0x200, player.y + 2 * 0x200, btype, Direction::Right, &state.constants);
|
||||
bullet_manager.create_bullet(player.x + 6 * 0x200, player.y + 2 * 0x200, btype, player_id, Direction::Right, &state.constants);
|
||||
state.create_caret(player.x + 12 * 0x200, player.y + 2 * 0x200, CaretType::Shoot, Direction::Right);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -140,8 +140,8 @@ impl Weapon {
|
|||
}
|
||||
}
|
||||
|
||||
fn shoot_bullet_polar_star(&mut self, player: &Player, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([4, 5, 6]) < 2 {
|
||||
fn shoot_bullet_polar_star(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([4, 5, 6], player_id) < 2 {
|
||||
let btype = match self.level {
|
||||
WeaponLevel::Level1 => { 4 }
|
||||
WeaponLevel::Level2 => { 5 }
|
||||
|
@ -157,11 +157,11 @@ impl Weapon {
|
|||
if player.up {
|
||||
match player.direction {
|
||||
Direction::Left => {
|
||||
bullet_manager.create_bullet(player.x - 0x200, player.y - 8 * 0x200, btype, Direction::Up, &state.constants);
|
||||
bullet_manager.create_bullet(player.x - 0x200, player.y - 8 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||
state.create_caret(player.x - 0x200, player.y - 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
Direction::Right => {
|
||||
bullet_manager.create_bullet(player.x + 0x200, player.y - 8 * 0x200, btype, Direction::Up, &state.constants);
|
||||
bullet_manager.create_bullet(player.x + 0x200, player.y - 8 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||
state.create_caret(player.x + 0x200, player.y - 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -169,11 +169,11 @@ impl Weapon {
|
|||
} else if player.down {
|
||||
match player.direction {
|
||||
Direction::Left => {
|
||||
bullet_manager.create_bullet(player.x - 0x200, player.y + 8 * 0x200, btype, Direction::Bottom, &state.constants);
|
||||
bullet_manager.create_bullet(player.x - 0x200, player.y + 8 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||
state.create_caret(player.x - 0x200, player.y + 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
Direction::Right => {
|
||||
bullet_manager.create_bullet(player.x + 0x200, player.y + 8 * 0x200, btype, Direction::Bottom, &state.constants);
|
||||
bullet_manager.create_bullet(player.x + 0x200, player.y + 8 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||
state.create_caret(player.x + 0x200, player.y + 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -181,11 +181,11 @@ impl Weapon {
|
|||
} else {
|
||||
match player.direction {
|
||||
Direction::Left => {
|
||||
bullet_manager.create_bullet(player.x - 6 * 0x200, player.y + 3 * 0x200, btype, Direction::Left, &state.constants);
|
||||
bullet_manager.create_bullet(player.x - 6 * 0x200, player.y + 3 * 0x200, btype, player_id, Direction::Left, &state.constants);
|
||||
state.create_caret(player.x - 6 * 0x200, player.y + 3 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
Direction::Right => {
|
||||
bullet_manager.create_bullet(player.x + 6 * 0x200, player.y + 3 * 0x200, btype, Direction::Right, &state.constants);
|
||||
bullet_manager.create_bullet(player.x + 6 * 0x200, player.y + 3 * 0x200, btype, player_id, Direction::Right, &state.constants);
|
||||
state.create_caret(player.x + 6 * 0x200, player.y + 3 * 0x200, CaretType::Shoot, Direction::Right);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -201,9 +201,9 @@ impl Weapon {
|
|||
}
|
||||
|
||||
|
||||
fn shoot_bullet_fireball(&mut self, player: &Player, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||
fn shoot_bullet_fireball(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||
let max_bullets = self.level as usize + 1;
|
||||
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([7, 8, 9]) < max_bullets {
|
||||
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([7, 8, 9], player_id) < max_bullets {
|
||||
let btype = match self.level {
|
||||
WeaponLevel::Level1 => { 7 }
|
||||
WeaponLevel::Level2 => { 8 }
|
||||
|
@ -219,11 +219,11 @@ impl Weapon {
|
|||
if player.up {
|
||||
match player.direction {
|
||||
Direction::Left => {
|
||||
bullet_manager.create_bullet(player.x - 4 * 0x200, player.y - 8 * 0x200, btype, Direction::Up, &state.constants);
|
||||
bullet_manager.create_bullet(player.x - 4 * 0x200, player.y - 8 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||
state.create_caret(player.x - 4 * 0x200, player.y - 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
Direction::Right => {
|
||||
bullet_manager.create_bullet(player.x + 4 * 0x200, player.y - 8 * 0x200, btype, Direction::Up, &state.constants);
|
||||
bullet_manager.create_bullet(player.x + 4 * 0x200, player.y - 8 * 0x200, btype, player_id, Direction::Up, &state.constants);
|
||||
state.create_caret(player.x + 4 * 0x200, player.y - 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -231,11 +231,11 @@ impl Weapon {
|
|||
} else if player.down {
|
||||
match player.direction {
|
||||
Direction::Left => {
|
||||
bullet_manager.create_bullet(player.x - 4 * 0x200, player.y + 8 * 0x200, btype, Direction::Bottom, &state.constants);
|
||||
bullet_manager.create_bullet(player.x - 4 * 0x200, player.y + 8 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||
state.create_caret(player.x - 4 * 0x200, player.y + 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
Direction::Right => {
|
||||
bullet_manager.create_bullet(player.x + 4 * 0x200, player.y + 8 * 0x200, btype, Direction::Bottom, &state.constants);
|
||||
bullet_manager.create_bullet(player.x + 4 * 0x200, player.y + 8 * 0x200, btype, player_id, Direction::Bottom, &state.constants);
|
||||
state.create_caret(player.x + 4 * 0x200, player.y + 8 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -243,11 +243,11 @@ impl Weapon {
|
|||
} else {
|
||||
match player.direction {
|
||||
Direction::Left => {
|
||||
bullet_manager.create_bullet(player.x - 6 * 0x200, player.y + 2 * 0x200, btype, Direction::Left, &state.constants);
|
||||
bullet_manager.create_bullet(player.x - 6 * 0x200, player.y + 2 * 0x200, btype, player_id, Direction::Left, &state.constants);
|
||||
state.create_caret(player.x - 12 * 0x200, player.y + 2 * 0x200, CaretType::Shoot, Direction::Left);
|
||||
}
|
||||
Direction::Right => {
|
||||
bullet_manager.create_bullet(player.x + 6 * 0x200, player.y + 2 * 0x200, btype, Direction::Right, &state.constants);
|
||||
bullet_manager.create_bullet(player.x + 6 * 0x200, player.y + 2 * 0x200, btype, player_id, Direction::Right, &state.constants);
|
||||
state.create_caret(player.x + 12 * 0x200, player.y + 2 * 0x200, CaretType::Shoot, Direction::Right);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -258,16 +258,16 @@ impl Weapon {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn shoot_bullet(&mut self, player: &Player, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||
if player.cond.hidden() {
|
||||
pub fn shoot_bullet(&mut self, player: &Player, player_id: TargetPlayer, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
|
||||
if !player.cond.alive() || player.cond.hidden() {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.wtype {
|
||||
WeaponType::None => {}
|
||||
WeaponType::Snake => self.shoot_bullet_snake(player, bullet_manager, state),
|
||||
WeaponType::PolarStar => self.shoot_bullet_polar_star(player, bullet_manager, state),
|
||||
WeaponType::Fireball => self.shoot_bullet_fireball(player, bullet_manager, state),
|
||||
WeaponType::Snake => self.shoot_bullet_snake(player, player_id, bullet_manager, state),
|
||||
WeaponType::PolarStar => self.shoot_bullet_polar_star(player, player_id, bullet_manager, state),
|
||||
WeaponType::Fireball => self.shoot_bullet_fireball(player, player_id, bullet_manager, state),
|
||||
WeaponType::MachineGun => {}
|
||||
WeaponType::MissileLauncher => {}
|
||||
WeaponType::Bubbler => {}
|
||||
|
|
Loading…
Reference in a new issue