From d6d2df53f7d4d142b1bbef628f1562276ef6ff51 Mon Sep 17 00:00:00 2001 From: Alula Date: Sun, 29 Nov 2020 13:06:10 +0100 Subject: [PATCH 01/15] initial co-op support --- src/components/draw_common.rs | 27 +++++ src/components/hud.rs | 220 ++++++++++++++++++++++++++++++++++ src/components/mod.rs | 2 + src/menu/mod.rs | 2 - src/profile.rs | 3 + src/scene/game_scene.rs | 202 ++++++++----------------------- src/scene/title_scene.rs | 4 + src/text_script.rs | 72 +++++++++-- 8 files changed, 365 insertions(+), 167 deletions(-) create mode 100644 src/components/draw_common.rs create mode 100644 src/components/hud.rs diff --git a/src/components/draw_common.rs b/src/components/draw_common.rs new file mode 100644 index 0000000..37c292d --- /dev/null +++ b/src/components/draw_common.rs @@ -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(()) +} diff --git a/src/components/hud.rs b/src/components/hud.rs new file mode 100644 index 0000000..d1858ff --- /dev/null +++ b/src/components/hud.rs @@ -0,0 +1,220 @@ +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, + 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, + 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 { + // 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), + }; + + 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, + (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, + (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(()) + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index aa5e29e..a860e70 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,2 +1,4 @@ pub mod boss_life_bar; +pub mod draw_common; +pub mod hud; pub mod stage_select; diff --git a/src/menu/mod.rs b/src/menu/mod.rs index 914fcfe..13ea4be 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -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; diff --git a/src/profile.rs b/src/profile.rs index acb9fcc..8aa4120 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -105,6 +105,9 @@ 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(); } pub fn dump(state: &mut SharedGameState, game_scene: &mut GameScene) -> GameProfile { diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index d1086fa..00a9872 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -10,6 +10,8 @@ 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}; @@ -32,6 +34,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 +48,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,12 +59,6 @@ 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"]; @@ -85,6 +80,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 +99,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,127 +107,6 @@ 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(()) - } - - 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(()) - } - fn draw_background(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &self.tex_background_name)?; let scale = state.scale; @@ -1097,6 +970,7 @@ impl GameScene { } }; 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,6 +984,18 @@ impl GameScene { self.player1.damage = 0; } + 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); + } + _ => {} + } + + self.player2.damage = 0; + } + for npc_cell in self.npc_map.npcs.values() { let mut npc = npc_cell.borrow_mut(); @@ -1124,6 +1010,9 @@ impl GameScene { self.player1.tick_map_collisions(state, &mut self.stage); self.player1.tick_npc_collisions(state, &mut self.npc_map, &mut self.inventory_player1); + self.player2.tick_map_collisions(state, &mut self.stage); + self.player2.tick_npc_collisions(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(); @@ -1176,32 +1065,36 @@ impl GameScene { weapon.shoot_bullet(&self.player1, &mut self.bullet_manager, state); } + if let Some(weapon) = self.inventory_player2.get_current_weapon_mut() { + weapon.shoot_bullet(&self.player2, &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; + 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.weapon_x_pos = 0; + self.hud_player1.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.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.life_bar > self.player1.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.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)?; } @@ -1311,12 +1204,15 @@ impl Scene for GameScene { } } + self.hud_player1.visible = true; + self.hud_player2.visible = true; self.npc_map.boss_map.boss_type = self.stage.data.boss_no as u16; self.player1.target_x = self.player1.x; self.player1.target_y = self.player1.y; self.frame.target_x = self.player1.target_x; self.frame.target_y = self.player1.target_y; self.frame.immediate_update(state, &self.stage); + self.player2.appearance = PlayerAppearance::YellowQuote; Ok(()) } @@ -1324,6 +1220,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 +1237,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 +1263,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 +1325,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,7 +1346,8 @@ 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)?; } @@ -1479,7 +1375,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(()) } diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index 093a4b3..999c826 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -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; diff --git a/src/text_script.rs b/src/text_script.rs index a7e4248..6d5bbdf 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -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 ---- /// , pub flags: TextScriptFlags, pub mode: ScriptMode, + /// The player who triggered the event. + pub trigger_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, + trigger_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 } @@ -936,6 +958,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 +1033,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; @@ -1040,6 +1070,24 @@ impl TextScriptVM { exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } + OpCode::S2MV => { + let param = read_cur_varint(&mut cursor)? as usize; + + match param { + 0 | 1 => { + game_scene.player2.vel_x = 0; + game_scene.player2.vel_y = 0; + game_scene.player2.x = game_scene.player1.x + + if param == 0 { -16 * 0x200 } else { 16 * 0x200 }; + game_scene.player2.y = game_scene.player1.y; + } + _ => { + log::warn!("stub: <2MV unknown param"); + } + } + + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } OpCode::UNI => { let control_mode = read_cur_varint(&mut cursor)? as u8; @@ -1428,7 +1476,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); From 87b2df2ade91610a6171dbfe32d64eef03106458 Mon Sep 17 00:00:00 2001 From: Alula Date: Mon, 30 Nov 2020 14:31:40 +0100 Subject: [PATCH 02/15] make some of the NPCs aware of second player --- src/common.rs | 1 + src/components/hud.rs | 14 +++++- src/npc/balrog.rs | 24 ++++++++-- src/npc/characters.rs | 4 +- src/npc/egg_corridor.rs | 19 ++++++-- src/npc/grasstown.rs | 28 +++++++++--- src/npc/mimiga_village.rs | 10 +++-- src/npc/misc.rs | 40 +++++++++++------ src/npc/mod.rs | 95 ++++++++++++++++++++------------------- src/npc/npc_utils.rs | 28 ++++++++++++ src/npc/quote.rs | 18 ++++---- src/npc/santa.rs | 3 +- src/player/player_hit.rs | 12 ++--- src/scene/game_scene.rs | 35 +++++++++++---- src/text_script.rs | 46 ++++++++++++++----- 15 files changed, 258 insertions(+), 119 deletions(-) create mode 100644 src/npc/npc_utils.rs diff --git a/src/common.rs b/src/common.rs index 69e95c3..e595022 100644 --- a/src/common.rs +++ b/src/common.rs @@ -92,6 +92,7 @@ bitfield! { pub credits_running, set_credits_running: 3; // 0x08 // engine specific flags + pub friendly_fire, set_friendly_fire: 14; pub wind, set_wind: 15; } diff --git a/src/components/hud.rs b/src/components/hud.rs index d1858ff..609bce3 100644 --- a/src/components/hud.rs +++ b/src/components/hud.rs @@ -12,6 +12,7 @@ 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, @@ -35,6 +36,7 @@ impl HUD { alignment, weapon_x_pos: 16, visible: false, + has_player2: false, ammo: 0, max_ammo: 0, xp: 0, @@ -114,6 +116,14 @@ impl GameEntity<(&Player, &Inventory)> for HUD { 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, @@ -165,7 +175,7 @@ impl GameEntity<(&Player, &Inventory)> for HUD { Rect::new_size(112, 80, 32, 8) }; - batch.add_rect((state.canvas_size.0 / 2.0).floor() - 40.0, + batch.add_rect((state.canvas_size.0 / 2.0).floor() - 40.0 + air_offset, (state.canvas_size.1 / 2.0).floor(), &rect); } @@ -203,7 +213,7 @@ impl GameEntity<(&Player, &Inventory)> for HUD { batch.draw(ctx)?; if self.air_counter > 0 && self.air_counter % 6 < 4 { - draw_number((state.canvas_size.0 / 2.0).floor() + 8.0, + 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)?; } diff --git a/src/npc/balrog.rs b/src/npc/balrog.rs index dbda3d1..2e09df5 100644 --- a/src/npc/balrog.rs +++ b/src/npc/balrog.rs @@ -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>, stage: &mut Stage) -> GameResult { + pub(crate) fn tick_n012_balrog_cutscene(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], map: &BTreeMap>, 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,9 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n068_balrog_running(&mut self, state: &mut SharedGameState, player: &mut Player) -> GameResult { + pub(crate) fn tick_n068_balrog_running(&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 { diff --git a/src/npc/characters.rs b/src/npc/characters.rs index bb03f5c..d517057 100644 --- a/src/npc/characters.rs +++ b/src/npc/characters.rs @@ -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 { diff --git a/src/npc/egg_corridor.rs b/src/npc/egg_corridor.rs index 82cf541..3828b28 100644 --- a/src/npc/egg_corridor.rs +++ b/src/npc/egg_corridor.rs @@ -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 { diff --git a/src/npc/grasstown.rs b/src/npc/grasstown.rs index 1b13427..d545f58 100644 --- a/src/npc/grasstown.rs +++ b/src/npc/grasstown.rs @@ -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 { @@ -779,7 +787,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 +1078,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 +1098,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 +1146,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; diff --git a/src/npc/mimiga_village.rs b/src/npc/mimiga_village.rs index 90eb7ac..6040bae 100644 --- a/src/npc/mimiga_village.rs +++ b/src/npc/mimiga_village.rs @@ -209,7 +209,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 +224,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 +259,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 +271,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 +297,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 +359,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 +444,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; diff --git a/src/npc/misc.rs b/src/npc/misc.rs index 3cfbee7..f2ff665 100644 --- a/src/npc/misc.rs +++ b/src/npc/misc.rs @@ -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; @@ -643,10 +645,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; @@ -667,7 +671,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 +690,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::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(players[0].x - self.x) < 480 * 0x200 && abs(players[0].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); + } } } _ => {} diff --git a/src/npc/mod.rs b/src/npc/mod.rs index 6ac0991..76b99f8 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -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>, &mut Stage)> for NPC { - fn tick(&mut self, state: &mut SharedGameState, (player, map, stage): (&mut Player, &BTreeMap>, &mut Stage)) -> GameResult { +impl GameEntity<([&mut Player; 2], &BTreeMap>, &mut Stage)> for NPC { + fn tick(&mut self, state: &mut SharedGameState, (players, map, stage): ([&mut Player; 2], &BTreeMap>, &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>, &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[0], 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[0]), + 60 => self.tick_n060_toroko(state, players[0]), 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[0]), + 65 => self.tick_n065_first_cave_bat(state, players[0]), 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[0]), 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[0]), 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[0]), + 89 => self.tick_n089_igor_dead(state, players[0]), 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[0]), + 94 => self.tick_n094_kulala(state, players[0]), 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[0]), + 98 => self.tick_n098_fan_right(state, players[0]), + 99 => self.tick_n099_fan_down(state, players[0]), 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[0]), 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[0]), + 150 => self.tick_n150_quote(state, players[0]), 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[0]), 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>, &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[0]), _ => Ok(()), }?; diff --git a/src/npc/npc_utils.rs b/src/npc/npc_utils.rs new file mode 100644 index 0000000..9aea4a0 --- /dev/null +++ b/src/npc/npc_utils.rs @@ -0,0 +1,28 @@ +use num_traits::abs; + +use crate::npc::NPC; +use crate::player::Player; + +impl NPC { + 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] + } +} diff --git a/src/npc/quote.rs b/src/npc/quote.rs index b85ac45..2c46443 100644 --- a/src/npc/quote.rs +++ b/src/npc/quote.rs @@ -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; diff --git a/src/npc/santa.rs b/src/npc/santa.rs index 3968db8..4a795df 100644 --- a/src/npc/santa.rs +++ b/src/npc/santa.rs @@ -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 { diff --git a/src/player/player_hit.rs b/src/player/player_hit.rs index 01f95c2..6569eec 100644 --- a/src/player/player_hit.rs +++ b/src/player/player_hit.rs @@ -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); } } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 00a9872..09c614d 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -18,7 +18,7 @@ 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; @@ -962,6 +962,9 @@ impl GameScene { } fn tick_world(&mut self, state: &mut SharedGameState) -> GameResult { + self.hud_player1.has_player2 = !self.player2.cond.hidden(); + self.hud_player2.has_player2 = self.hud_player1.has_player2; + self.player1.current_weapon = { if let Some(weapon) = self.inventory_player1.get_current_weapon_mut() { weapon.wtype as u8 @@ -969,6 +972,13 @@ 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, ())?; @@ -996,11 +1006,13 @@ impl GameScene { self.player2.damage = 0; } - for npc_cell in self.npc_map.npcs.values() { - let mut npc = npc_cell.borrow_mut(); + { + 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, &self.npc_map.npcs, &mut self.stage))?; + 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))?; @@ -1008,10 +1020,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(state, &mut self.npc_map, &mut self.inventory_player2); + 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(); @@ -1036,8 +1048,13 @@ impl GameScene { 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.hidden() { + self.frame.target_x = (self.player1.target_x + self.player2.target_x) / 2; + self.frame.target_y = (self.player1.target_y + self.player2.target_y) / 2; + } 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) { diff --git a/src/text_script.rs b/src/text_script.rs index 6d5bbdf..347290d 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -366,7 +366,7 @@ pub struct TextScriptVM { pub flags: TextScriptFlags, pub mode: ScriptMode, /// The player who triggered the event. - pub trigger_player: TargetPlayer, + 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. @@ -451,7 +451,7 @@ impl TextScriptVM { stack: Vec::with_capacity(6), flags: TextScriptFlags(0), mode: ScriptMode::Map, - trigger_player: TargetPlayer::Player1, + executor_player: TargetPlayer::Player1, strict_mode: false, suspend: true, face: 0, @@ -805,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); } @@ -1063,23 +1065,29 @@ 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 => { - game_scene.player2.vel_x = 0; - game_scene.player2.vel_y = 0; - game_scene.player2.x = game_scene.player1.x - + if param == 0 { -16 * 0x200 } else { 16 * 0x200 }; - game_scene.player2.y = game_scene.player1.y; + 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"); @@ -1368,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); } @@ -1378,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 => { @@ -1388,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); } @@ -1404,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); @@ -1414,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); } @@ -1512,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))?; } } From 23c5196fde649d60284e7314f34653ac21d0ae93 Mon Sep 17 00:00:00 2001 From: Alula Date: Wed, 2 Dec 2020 02:19:42 +0100 Subject: [PATCH 03/15] Improved camera behavior --- src/scene/game_scene.rs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 09c614d..7859b05 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -59,8 +59,10 @@ pub enum TileLayer { Snack, } -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 { @@ -1051,6 +1053,12 @@ impl GameScene { if !self.player2.cond.hidden() { self.frame.target_x = (self.player1.target_x + self.player2.target_x) / 2; self.frame.target_y = (self.player1.target_y + self.player2.target_y) / 2; + + let up = if self.player1.up { 0x10000 } else { 0x8000 }; + let down = if self.player1.down { 0x10000 } else { 0x8000 }; + + 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 - down, self.player1.y + up); } else { self.frame.target_x = self.player1.target_x; self.frame.target_y = self.player1.target_y; @@ -1366,6 +1374,30 @@ impl Scene for GameScene { 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.hidden() { + let y = interpolate_fix9_scale(self.player2.prev_y - self.frame.prev_y, self.player2.y - self.frame.y, state.frame_time); + + 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, 100), &state.constants, &mut state.texture_set, ctx)?; + + state.font.draw_colored_text(P2_LEFT_TEXT.chars(), + 8.0, y, + (64, 64, 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, 100), &state.constants, &mut state.texture_set, ctx)?; + + state.font.draw_colored_text(P2_RIGHT_TEXT.chars(), + state.canvas_size.0 - width - 8.0, y, + (64, 64, 255), &state.constants, &mut state.texture_set, ctx)?; + } + } } if state.textscript_vm.mode == ScriptMode::StageSelect { From e540dfb29731556e670f09cc2cd35ac163e73549 Mon Sep 17 00:00:00 2001 From: Alula Date: Wed, 2 Dec 2020 12:47:40 +0100 Subject: [PATCH 04/15] camera improvements --- src/bmfont_renderer.rs | 4 ++++ src/player/mod.rs | 4 ++-- src/scene/game_scene.rs | 19 ++++++++++--------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/bmfont_renderer.rs b/src/bmfont_renderer.rs index 25ea65d..9fb9319 100644 --- a/src/bmfont_renderer.rs +++ b/src/bmfont_renderer.rs @@ -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>(&self, iter: I, constants: &EngineConstants) -> f32 { let mut offset_x = 0.0; diff --git a/src/player/mod.rs b/src/player/mod.rs index 28fdd5d..fd4e4b4 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -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; diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 7859b05..c5975c5 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -5,6 +5,7 @@ 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; @@ -1050,15 +1051,14 @@ impl GameScene { match self.frame.update_target { UpdateTarget::Player => { - if !self.player2.cond.hidden() { + if !self.player2.cond.hidden() + && abs(self.player1.target_x - self.player2.x) < 200 * 0x200 + && abs(self.player1.target_y - self.player2.y) < 160 * 0x200 { self.frame.target_x = (self.player1.target_x + self.player2.target_x) / 2; self.frame.target_y = (self.player1.target_y + self.player2.target_y) / 2; - let up = if self.player1.up { 0x10000 } else { 0x8000 }; - let down = if self.player1.down { 0x10000 } else { 0x8000 }; - 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 - down, self.player1.y + up); + 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; @@ -1377,25 +1377,26 @@ impl Scene for GameScene { if !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, 100), &state.constants, &mut state.texture_set, ctx)?; + (0, 0, 130), &state.constants, &mut state.texture_set, ctx)?; state.font.draw_colored_text(P2_LEFT_TEXT.chars(), 8.0, y, - (64, 64, 255), &state.constants, &mut state.texture_set, ctx)?; + (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, 100), &state.constants, &mut state.texture_set, ctx)?; + (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, - (64, 64, 255), &state.constants, &mut state.texture_set, ctx)?; + (96, 96, 255), &state.constants, &mut state.texture_set, ctx)?; } } } From f103e1aa2e22e0985779f92baf92c8363c87763a Mon Sep 17 00:00:00 2001 From: Alula Date: Thu, 3 Dec 2020 22:06:26 +0100 Subject: [PATCH 05/15] Every NPC is now aware of multiple players --- src/npc/chaco.rs | 3 +- src/npc/first_cave.rs | 12 ++-- src/npc/grasstown.rs | 4 +- src/npc/igor.rs | 7 ++- src/npc/maze.rs | 6 +- src/npc/mimiga_village.rs | 7 ++- src/npc/misc.rs | 127 ++++++++++++++++++++++++-------------- src/npc/mod.rs | 38 ++++++------ src/npc/quote.rs | 10 +-- src/npc/sue.rs | 3 +- src/npc/toroko.rs | 3 +- src/scene/game_scene.rs | 8 +-- 12 files changed, 136 insertions(+), 92 deletions(-) diff --git a/src/npc/chaco.rs b/src/npc/chaco.rs index c81f768..21505ec 100644 --- a/src/npc/chaco.rs +++ b/src/npc/chaco.rs @@ -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 { diff --git a/src/npc/first_cave.rs b/src/npc/first_cave.rs index c696add..9c19b5c 100644 --- a/src/npc/first_cave.rs +++ b/src/npc/first_cave.rs @@ -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 { diff --git a/src/npc/grasstown.rs b/src/npc/grasstown.rs index d545f58..478b727 100644 --- a/src/npc/grasstown.rs +++ b/src/npc/grasstown.rs @@ -504,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; @@ -587,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 diff --git a/src/npc/igor.rs b/src/npc/igor.rs index 7564dfd..5ca1780 100644 --- a/src/npc/igor.rs +++ b/src/npc/igor.rs @@ -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); diff --git a/src/npc/maze.rs b/src/npc/maze.rs index 1b40b17..85949b5 100644 --- a/src/npc/maze.rs +++ b/src/npc/maze.rs @@ -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 { diff --git a/src/npc/mimiga_village.rs b/src/npc/mimiga_village.rs index 6040bae..ba5d859 100644 --- a/src/npc/mimiga_village.rs +++ b/src/npc/mimiga_village.rs @@ -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; @@ -471,7 +472,7 @@ impl NPC { self.y += 16 * 0x200; self.anim_rect = state.constants.npc.n091_mimiga_cage; } - + Ok(()) } } diff --git a/src/npc/misc.rs b/src/npc/misc.rs index f2ff665..ba380d8 100644 --- a/src/npc/misc.rs +++ b/src/npc/misc.rs @@ -1,9 +1,9 @@ +use ggez::GameResult; use num_traits::{abs, clamp}; use num_traits::real::Real; use crate::caret::CaretType; use crate::common::Direction; -use ggez::GameResult; use crate::npc::{NPC, NPCMap}; use crate::player::Player; use crate::shared_game_state::SharedGameState; @@ -557,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); @@ -662,7 +663,7 @@ impl NPC { self.anim_num = 1; } } - _ =>{ } + _ => {} } let dir_offset = if self.direction == Direction::Left { 0 } else { 3 }; @@ -691,7 +692,8 @@ impl NPC { } { - if abs(players[0].x - self.x) < 480 * 0x200 && abs(players[0].y - self.y) < 240 * 0x200 + let player = self.get_closest_player_mut(players); + 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); @@ -723,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 { @@ -742,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 player = self.get_closest_player_mut(players); + 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); + } } - 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; + } } } _ => {} @@ -766,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 { @@ -785,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 player = self.get_closest_player_mut(players); + 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); + } } - 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); + } } } _ => {} @@ -810,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 { @@ -829,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 player = self.get_closest_player_mut(players); + 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); + } } - 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; + } } } _ => {} @@ -882,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 { @@ -898,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 { @@ -906,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() { @@ -953,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; @@ -966,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; @@ -998,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; @@ -1013,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; @@ -1045,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; @@ -1070,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; @@ -1083,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; @@ -1115,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; @@ -1130,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; @@ -1162,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; diff --git a/src/npc/mod.rs b/src/npc/mod.rs index 76b99f8..e1d5d24 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -209,29 +209,29 @@ impl GameEntity<([&mut Player; 2], &BTreeMap>, &mut Stage)> fo 39 => self.tick_n039_save_sign(state), 40 => self.tick_n040_santa(state, players), 41 => self.tick_n041_busted_door(state), - 42 => self.tick_n042_sue(state, players[0], map), + 42 => self.tick_n042_sue(state, players, map), 43 => self.tick_n043_chalkboard(state), 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, players), - 59 => self.tick_n059_eye_door(state, players[0]), - 60 => self.tick_n060_toroko(state, players[0]), + 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, players[0]), - 65 => self.tick_n065_first_cave_bat(state, players[0]), + 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, 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, players[0]), + 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, players[0]), + 75 => self.tick_n075_kanpachi(state, players), 76 => self.tick_n076_flowers(), 77 => self.tick_n077_yamashita(state), 78 => self.tick_n078_pot(state), @@ -244,17 +244,17 @@ impl GameEntity<([&mut Player; 2], &BTreeMap>, &mut Stage)> fo 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, players[0]), - 89 => self.tick_n089_igor_dead(state, players[0]), + 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, players[0]), - 94 => self.tick_n094_kulala(state, players[0]), + 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, players), - 97 => self.tick_n097_fan_up(state, players[0]), - 98 => self.tick_n098_fan_right(state, players[0]), - 99 => self.tick_n099_fan_down(state, players[0]), + 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), @@ -268,12 +268,12 @@ impl GameEntity<([&mut Player; 2], &BTreeMap>, &mut Stage)> fo 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[0]), + 114 => self.tick_n114_press(state, players), 129 => self.tick_n129_fireball_snake_trail(state), - 149 => self.tick_n149_horizontal_moving_block(state, players[0]), - 150 => self.tick_n150_quote(state, players[0]), + 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, players[0]), + 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), @@ -282,7 +282,7 @@ impl GameEntity<([&mut Player; 2], &BTreeMap>, &mut Stage)> fo 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, players[0]), + 361 => self.tick_n361_gaudi_dashing(state, players), _ => Ok(()), }?; diff --git a/src/npc/quote.rs b/src/npc/quote.rs index 2c46443..7df1ff9 100644 --- a/src/npc/quote.rs +++ b/src/npc/quote.rs @@ -130,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; @@ -276,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(()) } diff --git a/src/npc/sue.rs b/src/npc/sue.rs index 135676b..c053e1b 100644 --- a/src/npc/sue.rs +++ b/src/npc/sue.rs @@ -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>) -> GameResult { + pub fn tick_n042_sue(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], map: &BTreeMap>) -> 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; diff --git a/src/npc/toroko.rs b/src/npc/toroko.rs index ced7bb8..80c42bd 100644 --- a/src/npc/toroko.rs +++ b/src/npc/toroko.rs @@ -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 { diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index c5975c5..d3bdd3c 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -1052,10 +1052,10 @@ impl GameScene { match self.frame.update_target { UpdateTarget::Player => { if !self.player2.cond.hidden() - && abs(self.player1.target_x - self.player2.x) < 200 * 0x200 - && abs(self.player1.target_y - self.player2.y) < 160 * 0x200 { - self.frame.target_x = (self.player1.target_x + self.player2.target_x) / 2; - self.frame.target_y = (self.player1.target_y + self.player2.target_y) / 2; + && 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); From a9f626c3b5c3d33456e7169f9ec182f366ae3daf Mon Sep 17 00:00:00 2001 From: Alula Date: Thu, 3 Dec 2020 23:24:58 +0100 Subject: [PATCH 06/15] build fixes --- src/npc/misc.rs | 16 ++++++++-------- src/npc/npc_utils.rs | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/npc/misc.rs b/src/npc/misc.rs index ba380d8..6efd3fb 100644 --- a/src/npc/misc.rs +++ b/src/npc/misc.rs @@ -692,8 +692,8 @@ impl NPC { } { - let player = self.get_closest_player_mut(players); - if abs(player.x - self.x) < 480 * 0x200 && abs(player.y - self.y) < 240 * 0x200 + 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); @@ -745,8 +745,8 @@ impl NPC { } { - let player = self.get_closest_player_mut(players); - if abs(player.x - self.x) < 480 * 0x200 && abs(player.y - self.y) < 240 * 0x200 + 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); @@ -797,8 +797,8 @@ impl NPC { } { - let player = self.get_closest_player_mut(players); - if abs(player.x - self.x) < 480 * 0x200 && abs(player.y - self.y) < 240 * 0x200 + 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); @@ -846,8 +846,8 @@ impl NPC { } { - let player = self.get_closest_player_mut(players); - if abs(player.x - self.x) < 480 * 0x200 && abs(player.y - self.y) < 240 * 0x200 + 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); diff --git a/src/npc/npc_utils.rs b/src/npc/npc_utils.rs index 9aea4a0..83b003a 100644 --- a/src/npc/npc_utils.rs +++ b/src/npc/npc_utils.rs @@ -4,6 +4,28 @@ 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; From 3b1cd80d6a447d2fb492f31fab36b6487739f0f0 Mon Sep 17 00:00:00 2001 From: Alula Date: Fri, 4 Dec 2020 13:00:45 +0100 Subject: [PATCH 07/15] fix teleporter menu running always the first event --- src/components/stage_select.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/stage_select.rs b/src/components/stage_select.rs index 1d661cb..ed79b9c 100644 --- a/src/components/stage_select.rs +++ b/src/components/stage_select.rs @@ -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; } From a6967eb6c78cfa532296764cf257c34657909346 Mon Sep 17 00:00:00 2001 From: Alula Date: Fri, 4 Dec 2020 13:12:07 +0100 Subject: [PATCH 08/15] fix post-Toroko kidnap Balrog fight bug making second player disappear --- src/npc/balrog.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/npc/balrog.rs b/src/npc/balrog.rs index 2e09df5..675a654 100644 --- a/src/npc/balrog.rs +++ b/src/npc/balrog.rs @@ -813,9 +813,8 @@ impl NPC { Ok(()) } - pub(crate) fn tick_n068_balrog_running(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult { - let player = self.get_closest_player_mut(players); - + /// 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 { @@ -823,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 { @@ -861,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; @@ -888,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 => { @@ -904,6 +908,7 @@ impl NPC { } } 10 => { + let player = &mut players[self.vel_y2 as usize]; player.x = self.x; player.y = self.y; @@ -917,6 +922,7 @@ impl NPC { } } 11 => { + let player = &mut players[self.vel_y2 as usize]; player.x = self.x; player.y = self.y; @@ -937,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); From 36b2025fc452d4608a0a0c4e1dfe012ef1a2e9bb Mon Sep 17 00:00:00 2001 From: Alula Date: Fri, 4 Dec 2020 13:35:30 +0100 Subject: [PATCH 09/15] fix live debugger not preserving second player state --- src/live_debugger.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/live_debugger.rs b/src/live_debugger.rs index 6859f65..d44d82b 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -91,6 +91,7 @@ impl LiveDebugger { speed = 1.0 } + #[allow(clippy::float_cmp)] if state.settings.speed != speed { state.set_speed(speed); } @@ -140,6 +141,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 +151,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) => { From abc238dae4c7efbf2ae532cc7ca4d130b000153f Mon Sep 17 00:00:00 2001 From: Alula Date: Fri, 4 Dec 2020 15:43:06 +0100 Subject: [PATCH 10/15] fix stupid typo --- src/npc/grasstown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/npc/grasstown.rs b/src/npc/grasstown.rs index 478b727..dafd1f0 100644 --- a/src/npc/grasstown.rs +++ b/src/npc/grasstown.rs @@ -757,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; From 0cb6b911982c88cbe964a2609093ecaae339add6 Mon Sep 17 00:00:00 2001 From: Alula Date: Fri, 4 Dec 2020 18:37:47 +0100 Subject: [PATCH 11/15] hide player2 by default, add/drop button in debugger --- src/components/hud.rs | 4 +++ src/live_debugger.rs | 11 ++++++- src/player/mod.rs | 2 +- src/profile.rs | 3 +- src/scene/game_scene.rs | 65 +++++++++++++++++++++++++--------------- src/shared_game_state.rs | 1 + 6 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/components/hud.rs b/src/components/hud.rs index 609bce3..0ef68db 100644 --- a/src/components/hud.rs +++ b/src/components/hud.rs @@ -108,6 +108,10 @@ impl GameEntity<(&Player, &Inventory)> for HUD { } 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")?; diff --git a/src/live_debugger.rs b/src/live_debugger.rs index d44d82b..da36289 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -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})", @@ -114,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 { diff --git a/src/player/mod.rs b/src/player/mod.rs index fd4e4b4..8b3d674 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -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, diff --git a/src/profile.rs b/src/profile.rs index 8aa4120..2826adc 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -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; @@ -108,6 +107,8 @@ impl GameProfile { 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 { diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index d3bdd3c..9eb1f2a 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -110,6 +110,20 @@ impl GameScene { self.map_name_counter = ticks; } + 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; + } + + pub fn drop_player2(&mut self) { + self.player2.cond.set_alive(false); + } + fn draw_background(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &self.tex_background_name)?; let scale = state.scale; @@ -965,6 +979,8 @@ 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.hidden(); self.hud_player2.has_player2 = self.hud_player1.has_player2; @@ -1051,7 +1067,7 @@ impl GameScene { match self.frame.update_target { UpdateTarget::Player => { - if !self.player2.cond.hidden() + 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; @@ -1094,28 +1110,32 @@ impl GameScene { weapon.shoot_bullet(&self.player2, &mut self.bullet_manager, state); } - 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.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; + } } - 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; - } + 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_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; + 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))?; @@ -1229,15 +1249,12 @@ impl Scene for GameScene { } } - self.hud_player1.visible = true; - self.hud_player2.visible = true; self.npc_map.boss_map.boss_type = self.stage.data.boss_no as u16; self.player1.target_x = self.player1.x; self.player1.target_y = self.player1.y; self.frame.target_x = self.player1.target_x; self.frame.target_y = self.player1.target_y; self.frame.immediate_update(state, &self.stage); - self.player2.appearance = PlayerAppearance::YellowQuote; Ok(()) } @@ -1375,7 +1392,7 @@ impl Scene for GameScene { self.hud_player2.draw(state, ctx, &self.frame)?; self.boss_life_bar.draw(state, ctx, &self.frame)?; - if !self.player2.cond.hidden() { + 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)); diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 4d0e6de..c44501b 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -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; From 1180b8137329d37ae71735dce28ac5ae3b073e31 Mon Sep 17 00:00:00 2001 From: Alula Date: Fri, 4 Dec 2020 19:58:59 +0100 Subject: [PATCH 12/15] fix bullet hit glitches --- src/bullet.rs | 39 ++++++++------- src/common.rs | 14 ++++++ src/engine_constants/mod.rs | 96 ++++++++++++++++++------------------- src/scene/game_scene.rs | 4 +- 4 files changed, 85 insertions(+), 68 deletions(-) diff --git a/src/bullet.rs b/src/bullet.rs index af31179..8a018f7 100644 --- a/src/bullet.rs +++ b/src/bullet.rs @@ -1,7 +1,7 @@ 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}; @@ -61,7 +61,7 @@ pub struct Bullet { pub lifetime: u16, pub damage: u16, pub cond: Condition, - pub weapon_flags: Flag, + pub weapon_flags: BulletFlag, pub flags: Flag, pub direction: Direction, pub anim_rect: Rect, @@ -83,7 +83,7 @@ impl Bullet { damage: 0, life: 0, lifetime: 0, - flags: Flag(0), + flags: BulletFlag(0), enemy_hit_width: 0, enemy_hit_height: 0, block_hit_width: 0, @@ -161,7 +161,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]; @@ -397,7 +396,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 +467,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() { @@ -566,15 +565,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 +588,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 +617,8 @@ impl PhysicalEntity for Bullet { *tile = tile.wrapping_sub(1); } } + + self.flags.0 |= old_hit.0; } // Slopes 0x50 | 0x70 => { diff --git a/src/common.rs b/src/common.rs index e595022..09e1325 100644 --- a/src/common.rs +++ b/src/common.rs @@ -96,6 +96,20 @@ bitfield! { 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 { diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index 12eebc9..f7cbeeb 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -2,7 +2,7 @@ use case_insensitive_hashmap::CaseInsensitiveHashMap; use log::info; use crate::case_insensitive_hashmap; -use crate::common::{Flag, Rect}; +use crate::common::{Flag, Rect, BulletFlag}; use crate::player::ControlMode; use crate::str; use crate::text_script::TextScriptEncoding; @@ -96,7 +96,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, @@ -410,71 +410,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: [ diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 9eb1f2a..9a9175b 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -860,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); @@ -957,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; From c7622ea3b0c726df175df176c7adf36089414fc6 Mon Sep 17 00:00:00 2001 From: Alula Date: Sat, 5 Dec 2020 21:51:59 +0100 Subject: [PATCH 13/15] air hud fix --- src/scene/game_scene.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 9a9175b..1ff6ff1 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -981,8 +981,8 @@ 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.hidden(); - self.hud_player2.has_player2 = self.hud_player1.has_player2; + 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() { From d2c5f2161a5a811130f05dfbaba89e90807fb50f Mon Sep 17 00:00:00 2001 From: Alula Date: Sat, 5 Dec 2020 22:24:38 +0100 Subject: [PATCH 14/15] make bullets aware of player who shot them --- src/bullet.rs | 42 +++++++++++++++-------------- src/scene/game_scene.rs | 6 ++--- src/weapon.rs | 60 ++++++++++++++++++++--------------------- 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/src/bullet.rs b/src/bullet.rs index 8a018f7..d67df26 100644 --- a/src/bullet.rs +++ b/src/bullet.rs @@ -5,6 +5,7 @@ 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,6 +61,7 @@ pub struct Bullet { pub life: u16, pub lifetime: u16, pub damage: u16, + pub owner: TargetPlayer, pub cond: Condition, pub weapon_flags: BulletFlag, pub flags: Flag, @@ -76,7 +78,7 @@ 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 { @@ -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), @@ -246,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); @@ -284,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 @@ -292,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 @@ -301,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 @@ -365,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; @@ -374,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); @@ -554,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 diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 1ff6ff1..84ecfc6 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -1062,7 +1062,7 @@ 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 { @@ -1103,11 +1103,11 @@ 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 let Some(weapon) = self.inventory_player2.get_current_weapon_mut() { - weapon.shoot_bullet(&self.player2, &mut self.bullet_manager, state); + weapon.shoot_bullet(&self.player2, TargetPlayer::Player2, &mut self.bullet_manager, state); } if self.player1.cond.alive() { diff --git a/src/weapon.rs b/src/weapon.rs index de55347..86b7c0b 100644 --- a/src/weapon.rs +++ b/src/weapon.rs @@ -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 => {} From f00f0e39ccbcade644e72a621955f357e8d0dfe3 Mon Sep 17 00:00:00 2001 From: Alula Date: Sat, 5 Dec 2020 22:44:15 +0100 Subject: [PATCH 15/15] add missing caret --- src/caret.rs | 30 ++++++++++++++++++++++++++++-- src/engine_constants/mod.rs | 15 +++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/caret.rs b/src/caret.rs index ef12c9f..bf95e69 100644 --- a/src/caret.rs +++ b/src/caret.rs @@ -1,7 +1,7 @@ use std::fs::read_to_string; use crate::bitfield; -use crate::common::{Condition, Direction, Rect}; +use crate::common::{Condition, Direction, Rect, CDEG_RAD}; use crate::engine_constants::EngineConstants; use crate::rng::RNG; @@ -40,6 +40,7 @@ pub struct Caret { pub cond: Condition, pub direction: Direction, pub anim_rect: Rect, + 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]; diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index f7cbeeb..936e54b 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -2,11 +2,11 @@ use case_insensitive_hashmap::CaseInsensitiveHashMap; use log::info; use crate::case_insensitive_hashmap; -use crate::common::{Flag, Rect, BulletFlag}; +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, pub level_up_rects: Vec>, pub level_down_rects: Vec>, + pub hurt_particles_rects: Vec>, pub explosion_rects: Vec>, pub little_particles_rects: Vec>, pub exhaust_rects: Vec>, @@ -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(), @@ -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 },