1
0
Fork 0
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:
alula 2020-12-05 23:29:40 +01:00 committed by GitHub
commit 9e21838a9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1038 additions and 489 deletions

View file

@ -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;

View file

@ -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 => {

View file

@ -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];

View file

@ -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 {

View 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
View 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(())
}
}

View file

@ -1,2 +1,4 @@
pub mod boss_life_bar;
pub mod draw_common;
pub mod hud;
pub mod stage_select;

View file

@ -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;
}

View file

@ -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: [

View file

@ -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) => {

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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(())
}
}

View file

@ -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;

View file

@ -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
View 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]
}
}

View file

@ -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(())
}

View file

@ -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 {

View file

@ -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;

View file

@ -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 {

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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(())
}

View file

@ -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;

View file

@ -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;

View file

@ -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))?;
}
}

View file

@ -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 => {}