mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-09-20 03:03:50 +00:00
Compare commits
6 commits
8603086694
...
81a422502c
Author | SHA1 | Date | |
---|---|---|---|
|
81a422502c | ||
|
b89fea9756 | ||
|
5949d51270 | ||
|
4ecb11e89d | ||
|
d1698d43b8 | ||
|
63bfce689d |
|
@ -26,7 +26,7 @@ impl BMFontRenderer {
|
|||
let mut pages = Vec::new();
|
||||
|
||||
println!("stem: {:?}", stem);
|
||||
let (zeros, ext, format) = FILE_TYPES
|
||||
let (zeros, _, _) = FILE_TYPES
|
||||
.iter()
|
||||
.map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext)))
|
||||
.find(|(_, _, path)| filesystem::exists(ctx, &path))
|
||||
|
|
Binary file not shown.
|
@ -31,7 +31,7 @@ impl io::Seek for BuiltinFile {
|
|||
}
|
||||
|
||||
impl io::Write for BuiltinFile {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
|
||||
Err(io::Error::new(ErrorKind::PermissionDenied, "Built-in file system is read-only."))
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,11 @@ use num_traits::clamp;
|
|||
use crate::caret::CaretType;
|
||||
use crate::common::{BulletFlag, Condition, Direction, Flag, Rect};
|
||||
use crate::engine_constants::{BulletData, EngineConstants};
|
||||
use crate::npc::NPCMap;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::physics::{OFF_X, OFF_Y, PhysicalEntity};
|
||||
use crate::player::TargetPlayer;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
|
||||
|
@ -25,15 +27,15 @@ impl BulletManager {
|
|||
self.bullets.push(Bullet::new(x, y, btype, owner, direction, constants));
|
||||
}
|
||||
|
||||
pub fn tick_bullets(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2], stage: &mut Stage) {
|
||||
pub fn tick_bullets(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2], npc_list: &NPCList, stage: &mut Stage) {
|
||||
for bullet in self.bullets.iter_mut() {
|
||||
if bullet.life < 1 {
|
||||
bullet.cond.set_alive(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
bullet.tick(state, players);
|
||||
bullet.tick_map_collisions(state, stage);
|
||||
bullet.tick(state, players, npc_list);
|
||||
bullet.tick_map_collisions(state, npc_list, stage);
|
||||
}
|
||||
|
||||
self.bullets.retain(|b| !b.is_dead());
|
||||
|
@ -253,7 +255,7 @@ impl Bullet {
|
|||
}
|
||||
}
|
||||
|
||||
fn tick_fireball(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2]) {
|
||||
fn tick_fireball(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2], npc_list: &NPCList) {
|
||||
self.action_counter += 1;
|
||||
if self.action_counter > self.lifetime {
|
||||
self.cond.set_alive(false);
|
||||
|
@ -361,18 +363,18 @@ impl Bullet {
|
|||
|
||||
self.anim_rect = state.constants.weapon.bullet_rects.b008_009_fireball_l2_3[self.anim_num as usize + dir_offset];
|
||||
|
||||
let mut npc = NPCMap::create_npc(129, &state.npc_table);
|
||||
let mut npc = NPC::create(129, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.vel_y = -0x200;
|
||||
npc.action_counter2 = if self.btype == 9 { self.anim_num + 3 } else { self.anim_num };
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2]) {
|
||||
pub fn tick(&mut self, state: &mut SharedGameState, players: [&dyn PhysicalEntity; 2], npc_list: &NPCList) {
|
||||
if self.lifetime == 0 {
|
||||
self.cond.set_alive(false);
|
||||
return;
|
||||
|
@ -381,7 +383,7 @@ 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, players),
|
||||
7 | 8 | 9 => self.tick_fireball(state, players, npc_list),
|
||||
_ => self.cond.set_alive(false),
|
||||
}
|
||||
}
|
||||
|
@ -570,7 +572,7 @@ impl PhysicalEntity for Bullet {
|
|||
}
|
||||
}
|
||||
|
||||
fn tick_map_collisions(&mut self, state: &mut SharedGameState, stage: &mut Stage) {
|
||||
fn tick_map_collisions(&mut self, state: &mut SharedGameState, npc_list: &NPCList, stage: &mut Stage) {
|
||||
self.flags().0 = 0;
|
||||
if self.weapon_flags.flag_x04() { // ???
|
||||
return;
|
||||
|
@ -606,7 +608,7 @@ impl PhysicalEntity for Bullet {
|
|||
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
|
||||
state.sound_manager.play_sfx(12);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = (x * 16 + 8) * 0x200;
|
||||
|
@ -616,7 +618,7 @@ impl PhysicalEntity for Bullet {
|
|||
npc.vel_x = state.game_rng.range(-0x200..0x200) as isize;
|
||||
npc.vel_y = state.game_rng.range(-0x200..0x200) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
if let Some(tile) = stage.map.tiles.get_mut(stage.map.width * (y + oy) as usize + (x + ox) as usize) {
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
use std::fs::read_to_string;
|
||||
|
||||
use crate::bitfield;
|
||||
use crate::common::{CDEG_RAD, Condition, Direction, Rect};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::rng::RNG;
|
||||
|
@ -68,7 +65,7 @@ impl Caret {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, rng: &RNG, constants: &EngineConstants) {
|
||||
pub fn tick(&mut self, rng: &dyn RNG, constants: &EngineConstants) {
|
||||
match self.ctype {
|
||||
CaretType::None => {}
|
||||
CaretType::Bubble => {}
|
||||
|
@ -177,7 +174,7 @@ impl Caret {
|
|||
Direction::Up => self.y -= 0x400,
|
||||
Direction::Right => self.x += 0x400,
|
||||
Direction::Bottom => self.y += 0x400,
|
||||
Direction::FacingPlayer => {},
|
||||
Direction::FacingPlayer => {}
|
||||
}
|
||||
}
|
||||
CaretType::DrownedQuote => {
|
||||
|
|
|
@ -95,9 +95,7 @@ bitfield! {
|
|||
pub alive, set_alive: 7; // 0x80
|
||||
|
||||
// engine specific flags
|
||||
pub drs_dont_remove, set_drs_dont_remove: 13;
|
||||
pub drs_boss, set_drs_boss: 14;
|
||||
pub drs_destroyed, set_drs_destroyed: 15;
|
||||
pub drs_boss, set_drs_boss: 15;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use ggez::{Context, GameResult};
|
||||
|
||||
use crate::common::Rect;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use ggez::{Context, GameResult};
|
||||
use crate::npc::NPCMap;
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::common::Rect;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
|
@ -32,10 +34,8 @@ impl BossLifeBar {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_npc_target(&mut self, npc_id: u16, npc_map: &NPCMap) {
|
||||
if let Some(npc_cell) = npc_map.npcs.get(&npc_id) {
|
||||
let npc = npc_cell.borrow();
|
||||
|
||||
pub fn set_npc_target(&mut self, npc_id: u16, npc_list: &NPCList) {
|
||||
if let Some(npc) = npc_list.get_npc(npc_id as usize) {
|
||||
self.target = BossLifeTarget::NPC(npc.id);
|
||||
self.life = npc.life;
|
||||
self.max_life = self.life;
|
||||
|
@ -43,26 +43,24 @@ impl BossLifeBar {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_boss_target(&mut self, npc_map: &NPCMap) {
|
||||
pub fn set_boss_target(&mut self, boss: &BossNPC) {
|
||||
self.target = BossLifeTarget::Boss;
|
||||
self.life = npc_map.boss_map.parts[0].life;
|
||||
self.life = boss.parts[0].life;
|
||||
self.max_life = self.life;
|
||||
self.prev_life = self.life;
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<&NPCMap> for BossLifeBar {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, npc_map: &NPCMap) -> GameResult<()> {
|
||||
impl GameEntity<(&NPCList, &BossNPC)> for BossLifeBar {
|
||||
fn tick(&mut self, _state: &mut SharedGameState, (npc_list, boss): (&NPCList, &BossNPC)) -> GameResult<> {
|
||||
match self.target {
|
||||
BossLifeTarget::NPC(npc_id) => {
|
||||
if let Some(npc_cell) = npc_map.npcs.get(&npc_id) {
|
||||
let npc = npc_cell.borrow();
|
||||
|
||||
if let Some(npc) = npc_list.get_npc(npc_id as usize) {
|
||||
self.life = npc.life;
|
||||
}
|
||||
}
|
||||
BossLifeTarget::Boss => {
|
||||
self.life = npc_map.boss_map.parts[0].life;
|
||||
self.life = boss.parts[0].life;
|
||||
}
|
||||
_ => {
|
||||
return Ok(());
|
||||
|
@ -103,7 +101,7 @@ impl GameEntity<&NPCMap> for BossLifeBar {
|
|||
rect_life_bar.right = ((self.life as u32 * bar_length) / self.max_life as u32).min(bar_length) as u16;
|
||||
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 20.0, &box_rect1);
|
||||
state.canvas_size.1 - 20.0, &box_rect1);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
state.canvas_size.1 - 12.0, &box_rect2);
|
||||
batch.add_rect(((state.canvas_size.0 - box_length as f32) / 2.0).floor(),
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::io::Cursor;
|
||||
|
||||
use ggez::{Context, GameResult};
|
||||
|
||||
use crate::common::Rect;
|
||||
|
|
|
@ -120,7 +120,7 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
|||
|
||||
// touch handler
|
||||
if state.settings.touch_controls && self.weapon_count != 0 {
|
||||
let mut rect = Rect::new(0, 0, 0, 16);
|
||||
let mut rect;
|
||||
let weapon_offset = match self.alignment {
|
||||
Alignment::Left => 0,
|
||||
Alignment::Right => (state.canvas_size.0 - 104.0) as isize,
|
||||
|
@ -157,7 +157,7 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult {
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
if !self.visible {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use case_insensitive_hashmap::CaseInsensitiveHashMap;
|
|||
use log::info;
|
||||
|
||||
use crate::case_insensitive_hashmap;
|
||||
use crate::common::{BulletFlag, Flag, Rect};
|
||||
use crate::common::{BulletFlag, Rect};
|
||||
use crate::engine_constants::npcs::NPCConsts;
|
||||
use crate::player::ControlMode;
|
||||
use crate::str;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::player::Player;
|
||||
use crate::common::{fix9_scale, interpolate_fix9_scale};
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
use crate::common::{interpolate_fix9_scale, fix9_scale};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -68,7 +68,6 @@ struct Game {
|
|||
state: SharedGameState,
|
||||
ui: UI,
|
||||
def_matrix: ColumnMatrix4<f32>,
|
||||
last_time: Instant,
|
||||
start_time: Instant,
|
||||
last_tick: u128,
|
||||
next_tick: u128,
|
||||
|
@ -82,7 +81,6 @@ impl Game {
|
|||
ui: UI::new(ctx)?,
|
||||
def_matrix: DrawParam::new().to_matrix(),
|
||||
state: SharedGameState::new(ctx)?,
|
||||
last_time: Instant::now(),
|
||||
start_time: Instant::now(),
|
||||
last_tick: 0,
|
||||
next_tick: 0,
|
||||
|
@ -183,7 +181,7 @@ impl Game {
|
|||
}
|
||||
}
|
||||
|
||||
fn key_up_event(&mut self, key_code: KeyCode, _key_mod: KeyMods) {
|
||||
fn key_up_event(&mut self, _key_code: KeyCode, _key_mod: KeyMods) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
@ -260,7 +258,7 @@ static BACKENDS: [Backend; 4] = [
|
|||
|
||||
fn init_ctx<P: Into<path::PathBuf> + Clone>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult<Context> {
|
||||
for backend in BACKENDS.iter() {
|
||||
let mut ctx = ContextBuilder::new("doukutsu-rs")
|
||||
let ctx = ContextBuilder::new("doukutsu-rs")
|
||||
.window_setup(WindowSetup::default().title("Cave Story ~ Doukutsu Monogatari (doukutsu-rs)"))
|
||||
.window_mode(WindowMode::default()
|
||||
.resizable(true)
|
||||
|
@ -304,7 +302,7 @@ pub fn init() -> GameResult {
|
|||
info!("Initializing engine...");
|
||||
|
||||
let event_loop = winit::event_loop::EventLoop::new();
|
||||
let mut context: Option<Context> = None;
|
||||
let mut context: Option<Context>;
|
||||
let mut game: Option<Game> = None;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
|
@ -341,6 +339,7 @@ pub fn init() -> GameResult {
|
|||
if context.is_none() {
|
||||
context = Some(init_ctx(target, resource_dir.clone()).unwrap());
|
||||
}
|
||||
let _ = target;
|
||||
|
||||
if let Some(game) = &mut game {
|
||||
game.loops = 0;
|
||||
|
@ -365,8 +364,6 @@ pub fn init() -> GameResult {
|
|||
}
|
||||
WindowEvent::Resized(_) => {
|
||||
if let (Some(ctx), Some(game)) = (&mut context, &mut game) {
|
||||
let (w, h) = graphics::drawable_size(ctx);
|
||||
|
||||
game.state.tmp_canvas = Canvas::with_window_size(ctx).unwrap();
|
||||
game.state.game_canvas = Canvas::with_window_size(ctx).unwrap();
|
||||
game.state.lightmap_canvas = Canvas::with_window_size(ctx).unwrap();
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use ggez::{Context, GameResult};
|
||||
use imgui::{CollapsingHeader, Condition, im_str, ImStr, ImString, Slider, Window, WindowFlags};
|
||||
use imgui::{CollapsingHeader, Condition, im_str, ImStr, ImString, Slider, Window};
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::scene::game_scene::GameScene;
|
||||
|
@ -71,9 +69,10 @@ impl LiveDebugger {
|
|||
));
|
||||
|
||||
ui.text(format!(
|
||||
"NPC Count: {}/{}",
|
||||
game_scene.npc_map.npcs.values().filter(|n| n.borrow().cond.alive()).count(),
|
||||
game_scene.npc_map.npcs.len(),
|
||||
"NPC Count: {}/{}/{}",
|
||||
game_scene.npc_list.iter_alive().count(),
|
||||
game_scene.npc_list.current_capacity(),
|
||||
game_scene.npc_list.max_capacity(),
|
||||
));
|
||||
|
||||
ui.text(format!(
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use std::io;
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
use byteorder::{LE, ReadBytesExt};
|
||||
|
||||
use ggez::GameError::ResourceLoadError;
|
||||
use ggez::GameResult;
|
||||
|
||||
use crate::str;
|
||||
|
||||
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
|
||||
|
|
|
@ -56,8 +56,8 @@ impl Menu {
|
|||
pub fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
let mut rect = Rect::new(0, 0, 0, 0);
|
||||
let mut rect2 = Rect::new(0, 0, 0, 0);
|
||||
let mut rect;
|
||||
let mut rect2;
|
||||
|
||||
rect = state.constants.title.menu_left_top;
|
||||
batch.add_rect(self.x as f32 - rect.width() as f32, self.y as f32 - rect.height() as f32, &rect);
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ggez::GameResult;
|
||||
use num_traits::clamp;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{CDEG_RAD, Direction};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n009_balrog_falling_in(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n009_balrog_falling_in(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -30,17 +29,17 @@ impl NPC {
|
|||
}
|
||||
|
||||
if self.flags.hit_bottom_wall() {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
|
||||
for _ in 0..3 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
self.action_num = 2;
|
||||
|
@ -74,7 +73,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n010_balrog_shooting(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n010_balrog_shooting(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
|
@ -97,7 +96,7 @@ impl NPC {
|
|||
self.action_counter2 -= 1;
|
||||
self.action_counter = 0;
|
||||
|
||||
let mut npc = NPCMap::create_npc(11, &state.npc_table);
|
||||
let mut npc = NPC::create(11, &state.npc_table);
|
||||
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
|
@ -108,7 +107,7 @@ impl NPC {
|
|||
npc.vel_x = (angle.cos() * 512.0) as isize; // 1.0fix9
|
||||
npc.vel_y = (angle.sin() * 512.0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
|
||||
state.sound_manager.play_sfx(39);
|
||||
|
||||
|
@ -203,7 +202,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n012_balrog_cutscene(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], map: &BTreeMap<u16, RefCell<NPC>>, stage: &mut Stage) -> GameResult {
|
||||
pub(crate) fn tick_n012_balrog_cutscene(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList, stage: &mut Stage) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -289,7 +288,7 @@ impl NPC {
|
|||
self.action_counter = 0;
|
||||
self.action_counter2 = 0;
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
|
||||
for _ in 0..3 {
|
||||
npc.cond.set_alive(true);
|
||||
|
@ -299,7 +298,7 @@ impl NPC {
|
|||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
state.sound_manager.play_sfx(72);
|
||||
|
@ -461,22 +460,18 @@ impl NPC {
|
|||
self.vel_y = -4 * 0x200;
|
||||
self.npc_flags.set_ignore_solidity(true);
|
||||
|
||||
for (&id, npc_cell) in map.iter() {
|
||||
if id == self.id { continue; } // prevent second mutable borrow of currently ticked npc
|
||||
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
if npc.npc_type == 150 || npc.npc_type == 117 {
|
||||
npc.cond.set_alive(false);
|
||||
}
|
||||
for npc in npc_list.iter_alive()
|
||||
.filter(|npc| npc.npc_type == 117 || npc.npc_type == 150) {
|
||||
npc.cond.set_alive(false)
|
||||
}
|
||||
|
||||
let mut npc = NPCMap::create_npc(355, &state.npc_table);
|
||||
let mut npc = NPC::create(355, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.parent_id = self.id;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
npc.direction = Direction::Up;
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
}
|
||||
102 => {
|
||||
|
@ -487,24 +482,24 @@ impl NPC {
|
|||
state.sound_manager.play_sfx(44);
|
||||
state.quake_counter = 10;
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = x as isize * 16 * 0x200;
|
||||
npc.y = y as isize * 16 * 0x200;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
|
||||
if x > 0 && stage.change_tile(x - 1, y, 0) {
|
||||
npc.x = (x - 1) as isize * 16 * 0x200;
|
||||
state.new_npcs.push(npc);
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
if x < stage.map.width && stage.change_tile(x + 1, y, 0) {
|
||||
npc.x = (x + 1) as isize * 16 * 0x200;
|
||||
state.new_npcs.push(npc);
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,7 +513,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
if self.target_x != 0 && self.rng.range(0..10) == 0 {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
|
@ -526,7 +521,7 @@ impl NPC {
|
|||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
|
||||
if self.vel_y > 0x5ff {
|
||||
|
@ -550,20 +545,21 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n019_balrog_bust_in(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n019_balrog_bust_in(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
|
||||
for _ in 0..16 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
state.sound_manager.play_sfx(12);
|
||||
|
@ -658,7 +654,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n036_balrog_hover(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n036_balrog_hover(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
|
@ -684,14 +680,14 @@ impl NPC {
|
|||
let angle = f64::atan2((self.y - player.y) as f64, (self.x - player.x) as f64)
|
||||
+ self.rng.range(-16..16) as f64 * CDEG_RAD;
|
||||
|
||||
let mut npc = NPCMap::create_npc(11, &state.npc_table);
|
||||
let mut npc = NPC::create(11, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.vel_x = (angle.cos() * -512.0) as isize;
|
||||
npc.vel_y = (angle.sin() * -512.0) as isize;
|
||||
npc.x = self.x;
|
||||
npc.y = self.y + 4 * 0x200;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
state.sound_manager.play_sfx(39);
|
||||
|
||||
if self.vel_x2 == 0 {
|
||||
|
@ -761,25 +757,27 @@ impl NPC {
|
|||
state.sound_manager.play_sfx(26);
|
||||
state.quake_counter = 30;
|
||||
|
||||
let mut npc_smoke = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc_proj = NPCMap::create_npc(33, &state.npc_table);
|
||||
let mut npc_smoke = NPC::create(4, &state.npc_table);
|
||||
npc_smoke.cond.set_alive(true);
|
||||
npc_smoke.direction = Direction::Left;
|
||||
|
||||
let mut npc_proj = NPC::create(33, &state.npc_table);
|
||||
npc_proj.cond.set_alive(true);
|
||||
npc_proj.direction = Direction::Left;
|
||||
|
||||
for _ in 0..8 {
|
||||
npc_smoke.cond.set_alive(true);
|
||||
npc_smoke.direction = Direction::Left;
|
||||
npc_smoke.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc_smoke.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc_smoke.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc_smoke.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
let _ = npc_list.spawn(0x100, npc_smoke.clone());
|
||||
|
||||
npc_proj.cond.set_alive(true);
|
||||
npc_proj.direction = Direction::Left;
|
||||
npc_proj.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc_proj.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc_proj.vel_x = self.rng.range(-0x400..0x400) as isize;
|
||||
npc_proj.vel_y = self.rng.range(-0x400..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc_smoke);
|
||||
state.new_npcs.push(npc_proj);
|
||||
let _ = npc_list.spawn(0x100, npc_proj.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -815,7 +813,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
pub(crate) fn tick_n068_balrog_running(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -869,7 +867,7 @@ impl NPC {
|
|||
self.anim_num = 5;
|
||||
self.vel_y2 = pi as isize;
|
||||
players[pi].cond.set_hidden(true);
|
||||
players[pi].damage(2, state);
|
||||
players[pi].damage(2, state, npc_list);
|
||||
} else {
|
||||
self.action_counter += 1;
|
||||
|
||||
|
@ -898,7 +896,7 @@ impl NPC {
|
|||
self.anim_num = 5;
|
||||
self.vel_y2 = pi as isize;
|
||||
players[pi].cond.set_hidden(true);
|
||||
players[pi].damage(2, state);
|
||||
players[pi].damage(2, state, npc_list);
|
||||
}
|
||||
}
|
||||
9 => {
|
|
@ -1,8 +1,9 @@
|
|||
use ggez::GameResult;
|
||||
|
||||
use crate::npc::NPC;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::common::Direction;
|
||||
use crate::npc::NPC;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n113_professor_booster(&mut self, state: &mut SharedGameState) -> GameResult {
|
|
@ -4,6 +4,7 @@ use crate::caret::CaretType;
|
|||
use crate::common::Direction;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -43,15 +44,7 @@ impl NPC {
|
|||
self.anim_counter = 0;
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 4 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num > 5 {
|
||||
self.anim_num = 2;
|
||||
}
|
||||
}
|
||||
self.animate(4, 2, 5);
|
||||
|
||||
self.x += self.direction.vector_x() * 0x200;
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
use num_traits::{clamp, abs};
|
||||
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::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -341,7 +342,7 @@ impl NPC {
|
|||
pub(crate) fn tick_n151_blue_robot_standing(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0{
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
self.anim_counter = 0;
|
||||
|
@ -360,7 +361,7 @@ impl NPC {
|
|||
self.anim_num = 0;
|
||||
}
|
||||
}
|
||||
_ =>{}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
|
|
@ -3,8 +3,10 @@ use num_traits::{abs, clamp};
|
|||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{CDEG_RAD, Direction, Rect};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -302,9 +304,7 @@ impl NPC {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
if self.anim_counter == 1 {
|
||||
self.anim_rect = state.constants.npc.n006_green_beetle[self.anim_num as usize + if self.direction == Direction::Right { 5 } else { 0 }];
|
||||
}
|
||||
self.anim_rect = state.constants.npc.n006_green_beetle[self.anim_num as usize + if self.direction == Direction::Right { 5 } else { 0 }];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -544,7 +544,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n058_basu(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n058_basu(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
||||
match self.action_num {
|
||||
|
@ -591,6 +591,9 @@ impl NPC {
|
|||
|
||||
self.vel_y += ((self.target_y - self.y).signum() | 1) * 0x08;
|
||||
|
||||
self.vel_x = clamp(self.vel_x, -0x2ff, 0x2ff);
|
||||
self.vel_y = clamp(self.vel_y, -0x100, 0x100);
|
||||
|
||||
if self.shock > 0 {
|
||||
self.x += self.vel_x / 2;
|
||||
self.y += self.vel_y / 2;
|
||||
|
@ -618,17 +621,17 @@ impl NPC {
|
|||
} else {
|
||||
self.action_counter2 += 1;
|
||||
if (self.action_counter2 % 8) == 0 && abs(self.x - player.x) < 160 * 0x200 {
|
||||
let angle = ((self.y - player.y) as f64 / (self.x - player.x) as f64).atan()
|
||||
+ (self.rng.range(-6..6) as u8) as f64 * CDEG_RAD;
|
||||
let angle = f64::atan2((self.y - player.y) as f64, (self.x - player.x) as f64)
|
||||
+ self.rng.range(-6..6) as f64 * CDEG_RAD;
|
||||
|
||||
let mut npc = NPCMap::create_npc(84, &state.npc_table);
|
||||
let mut npc = NPC::create(84, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.vel_x = (angle.cos() * 1024.0) as isize;
|
||||
npc.vel_y = (angle.sin() * 1024.0) as isize;
|
||||
npc.vel_x = (angle.cos() * -1024.0) as isize;
|
||||
npc.vel_y = (angle.sin() * -1024.0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
state.sound_manager.play_sfx(39);
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use ggez::GameResult;
|
||||
use num_traits::{abs, clamp};
|
||||
use num_traits::clamp;
|
||||
|
||||
use crate::common::Direction;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
|
@ -1,12 +1,12 @@
|
|||
use ggez::GameResult;
|
||||
use num_traits::abs;
|
||||
use num_traits::clamp;
|
||||
use winit::event::VirtualKeyCode::Caret;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Direction, Rect};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::{NPC, NPCList};
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -422,7 +422,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n035_mannan(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n035_mannan(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
if self.action_num <= 2 && self.life < 90 {
|
||||
self.action_num = 3;
|
||||
self.action_counter = 0;
|
||||
|
@ -430,9 +430,10 @@ impl NPC {
|
|||
self.damage = 0;
|
||||
self.npc_flags.set_shootable(false);
|
||||
|
||||
npc_list.create_death_smoke(self.x, self.y, self.display_bounds.right, 8, state, &self.rng);
|
||||
self.create_xp_drop(state, npc_list);
|
||||
|
||||
state.sound_manager.play_sfx(71);
|
||||
self.cond.set_drs_dont_remove(true);
|
||||
self.cond.set_drs_destroyed(true);
|
||||
}
|
||||
|
||||
if self.action_num == 2 {
|
||||
|
@ -460,12 +461,13 @@ impl NPC {
|
|||
self.action_counter = 0;
|
||||
self.anim_num = 1;
|
||||
|
||||
let mut npc = NPCMap::create_npc(103, &state.npc_table);
|
||||
let mut npc = NPC::create(103, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x + self.direction.vector_x() * 8 * 0x200;
|
||||
npc.y = self.y + 8 * 0x200;
|
||||
npc.direction = self.direction;
|
||||
state.new_npcs.push(npc);
|
||||
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
|
||||
|
@ -851,7 +853,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n107_malco_broken(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n107_malco_broken(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.action_num = 1;
|
||||
|
@ -866,15 +868,15 @@ impl NPC {
|
|||
self.action_counter = 0;
|
||||
self.anim_counter = 0;
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
for _ in 0..4 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -931,15 +933,15 @@ impl NPC {
|
|||
self.anim_num = 2;
|
||||
state.sound_manager.play_sfx(12);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
for _ in 0..8 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -971,15 +973,15 @@ impl NPC {
|
|||
self.action_num = 20;
|
||||
state.sound_manager.play_sfx(12);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
for _ in 0..4 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1002,7 +1004,7 @@ impl NPC {
|
|||
self.animate(4, 6, 9);
|
||||
}
|
||||
110 => {
|
||||
self.cond.set_drs_destroyed(true);
|
||||
npc_list.create_death_smoke(self.x, self.y, 16 * 0x200, 16, state, &self.rng);
|
||||
self.cond.set_alive(false);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -1013,7 +1015,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n109_malco_powered_on(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n109_malco_powered_on(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -1052,16 +1054,15 @@ impl NPC {
|
|||
self.action_num = 0;
|
||||
state.sound_manager.play_sfx(12);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
for _ in 0..8 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
|
@ -1,8 +1,10 @@
|
|||
use ggez::GameResult;
|
||||
|
||||
use crate::common::{CDEG_RAD, Direction};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -87,7 +89,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n088_igor_boss(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n088_igor_boss(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -198,17 +200,16 @@ impl NPC {
|
|||
state.sound_manager.play_sfx(26);
|
||||
state.quake_counter = 30;
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
|
||||
for _ in 0..4 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,7 +238,7 @@ impl NPC {
|
|||
let vel_x = (deg.cos() * 1536.0) as isize;
|
||||
let vel_y = (deg.sin() * 1536.0) as isize;
|
||||
|
||||
let mut npc = NPCMap::create_npc(11, &state.npc_table);
|
||||
let mut npc = NPC::create(11, &state.npc_table);
|
||||
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
|
@ -246,7 +247,7 @@ impl NPC {
|
|||
npc.vel_x = vel_x;
|
||||
npc.vel_y = vel_y;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
state.sound_manager.play_sfx(12);
|
||||
}
|
||||
|
||||
|
@ -275,7 +276,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n089_igor_dead(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n089_igor_dead(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -284,17 +285,16 @@ impl NPC {
|
|||
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);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
|
||||
for _ in 0..8 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,7 +305,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
if self.action_counter % 5 == 0 {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
|
@ -314,7 +314,7 @@ impl NPC {
|
|||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
|
||||
|
@ -345,7 +345,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
if self.action_counter % 9 == 0 {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
|
@ -354,7 +354,7 @@ impl NPC {
|
|||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
|
|
@ -1,7 +1,9 @@
|
|||
use ggez::GameResult;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::Direction;
|
||||
use ggez::GameResult;
|
||||
use crate::npc::NPC;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -44,14 +46,7 @@ impl NPC {
|
|||
self.anim_counter = 0;
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 10 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
if self.anim_num > 5 {
|
||||
self.anim_num = 2;
|
||||
}
|
||||
}
|
||||
self.animate(10, 2, 5);
|
||||
|
||||
self.x += 0x100;
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
use ggez::GameResult;
|
||||
use num_traits::{abs, clamp};
|
||||
use num_traits::clamp;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::Direction;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::caret::CaretType;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n154_gaudi_dead(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
|
@ -31,7 +32,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n361_gaudi_dashing(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n361_gaudi_dashing(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -101,7 +102,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
if self.life <= 985 {
|
||||
self.cond.set_drs_destroyed(true);
|
||||
npc_list.create_death_smoke(self.x, self.y, 0, 2, state, &self.rng);
|
||||
self.npc_type = 154;
|
||||
self.action_num = 0;
|
||||
}
|
|
@ -6,6 +6,7 @@ use num_traits::{abs, clamp};
|
|||
use crate::common::Direction;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
|
@ -1,11 +1,12 @@
|
|||
use ggez::GameResult;
|
||||
use num_traits::{abs, clamp};
|
||||
use num_traits::real::Real;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::Direction;
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
|
||||
|
@ -132,7 +133,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n015_chest_closed(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n015_chest_closed(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -142,17 +143,16 @@ impl NPC {
|
|||
if self.direction == Direction::Right {
|
||||
self.vel_y = -0x200;
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
|
||||
for _ in 0..4 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,7 +291,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n018_door(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n018_door(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
match self.direction {
|
||||
|
@ -301,17 +301,16 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
1 => {
|
||||
for _ in 0..4 {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
for _ in 0..4 {
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
self.action_num = 0;
|
||||
|
@ -557,7 +556,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
|
||||
pub(crate) fn tick_n072_sprinkler(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n072_sprinkler(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
if self.direction == Direction::Left {
|
||||
self.anim_counter = (self.anim_counter + 1) % 4;
|
||||
self.anim_num = self.anim_counter / 2;
|
||||
|
@ -567,19 +566,19 @@ impl NPC {
|
|||
if self.anim_num % 2 == 0 && (player.x - self.x).abs() < 480 * 0x200 {
|
||||
self.action_counter = self.action_counter.wrapping_add(1);
|
||||
|
||||
let mut droplet = NPCMap::create_npc(73, &state.npc_table);
|
||||
let mut droplet = NPC::create(73, &state.npc_table);
|
||||
droplet.cond.set_alive(true);
|
||||
droplet.direction = Direction::Left;
|
||||
droplet.x = self.x;
|
||||
droplet.y = self.y;
|
||||
droplet.vel_x = 2 * self.rng.range(-0x200..0x200) as isize;
|
||||
droplet.vel_y = 3 * self.rng.range(-0x200..0x80) as isize;
|
||||
state.new_npcs.push(droplet);
|
||||
let _ = npc_list.spawn(0x100, droplet.clone());
|
||||
|
||||
if self.action_counter % 2 == 0 {
|
||||
droplet.vel_x = 2 * self.rng.range(-0x200..0x200) as isize;
|
||||
droplet.vel_y = 3 * self.rng.range(-0x200..0x80) as isize;
|
||||
state.new_npcs.push(droplet);
|
||||
let _ = npc_list.spawn(0x100, droplet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -672,7 +671,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n096_fan_left(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n096_fan_left(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 && self.direction == Direction::Right {
|
||||
|
@ -695,12 +694,12 @@ impl NPC {
|
|||
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
|
||||
&& self.rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
let mut particle = NPC::create(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Left;
|
||||
particle.x = self.x;
|
||||
particle.y = self.y + (self.rng.range(-8..8) * 0x200) as isize;
|
||||
state.new_npcs.push(particle);
|
||||
let _ = npc_list.spawn(0x100, particle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -725,7 +724,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n097_fan_up(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n097_fan_up(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 && self.direction == Direction::Right {
|
||||
|
@ -748,12 +747,12 @@ impl NPC {
|
|||
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
|
||||
&& self.rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
let mut particle = NPC::create(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Up;
|
||||
particle.x = self.x + (self.rng.range(-8..8) * 0x200) as isize;
|
||||
particle.y = self.y;
|
||||
state.new_npcs.push(particle);
|
||||
let _ = npc_list.spawn(0x100, particle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -777,7 +776,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n098_fan_right(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n098_fan_right(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 && self.direction == Direction::Right {
|
||||
|
@ -800,12 +799,12 @@ impl NPC {
|
|||
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
|
||||
&& self.rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
let mut particle = NPC::create(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Right;
|
||||
particle.x = self.x;
|
||||
particle.y = self.y + (self.rng.range(-8..8) * 0x200) as isize;
|
||||
state.new_npcs.push(particle);
|
||||
let _ = npc_list.spawn(0x100, particle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -826,7 +825,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n099_fan_down(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n099_fan_down(&mut self, state: &mut SharedGameState, mut players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 && self.direction == Direction::Right {
|
||||
|
@ -849,12 +848,12 @@ impl NPC {
|
|||
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
|
||||
&& self.rng.range(0..5) == 1 {
|
||||
let mut particle = NPCMap::create_npc(199, &state.npc_table);
|
||||
let mut particle = NPC::create(199, &state.npc_table);
|
||||
particle.cond.set_alive(true);
|
||||
particle.direction = Direction::Bottom;
|
||||
particle.x = self.x + (self.rng.range(-8..8) * 0x200) as isize;
|
||||
particle.y = self.y;
|
||||
state.new_npcs.push(particle);
|
||||
let _ = npc_list.spawn(0x100, particle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -888,22 +887,22 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n106_hey_bubble_high(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n106_hey_bubble_high(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
|
||||
let mut npc = NPCMap::create_npc(105, &state.npc_table);
|
||||
let mut npc = NPC::create(105, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y - 8 * 0x200;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x180, npc);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n114_press(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n114_press(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -939,15 +938,16 @@ impl NPC {
|
|||
|
||||
if self.flags.hit_bottom_wall() {
|
||||
if self.anim_num > 1 {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
|
||||
for _ in 0..4 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
state.quake_counter = 10;
|
||||
|
@ -976,27 +976,30 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n125_hidden_item(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n125_hidden_item(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
if self.life < 990 {
|
||||
self.cond.set_drs_destroyed(true);
|
||||
npc_list.create_death_smoke(self.x, self.y, self.display_bounds.right, 8, state, &self.rng);
|
||||
self.cond.set_alive(false);
|
||||
state.sound_manager.play_sfx(70);
|
||||
|
||||
match self.direction {
|
||||
/// hidden heart
|
||||
// hidden heart
|
||||
Direction::Left => {
|
||||
let mut npc = NPCMap::create_npc(87, &state.npc_table);
|
||||
let mut npc = NPC::create(87, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.direction = Direction::Right;
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0, npc);
|
||||
}
|
||||
/// hidden missile
|
||||
// hidden missile
|
||||
Direction::Right => {
|
||||
let mut npc = NPCMap::create_npc(86, &state.npc_table);
|
||||
let mut npc = NPC::create(86, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.direction = Direction::Right;
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0, npc);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -1011,7 +1014,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n149_horizontal_moving_block(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n149_horizontal_moving_block(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.x += 8 * 0x200;
|
||||
|
@ -1045,16 +1048,15 @@ impl NPC {
|
|||
state.quake_counter = 10;
|
||||
state.sound_manager.play_sfx(26);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
for _ in 0..3 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
} else {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
@ -1094,16 +1096,15 @@ impl NPC {
|
|||
state.quake_counter = 10;
|
||||
state.sound_manager.play_sfx(26);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
for _ in 0..3 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
} else {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
@ -1132,7 +1133,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n157_vertical_moving_block(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n157_vertical_moving_block(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.x += 8 * 0x200;
|
||||
|
@ -1166,16 +1167,15 @@ impl NPC {
|
|||
state.quake_counter = 10;
|
||||
state.sound_manager.play_sfx(26);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
for _ in 0..3 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
} else {
|
||||
let player = self.get_closest_player_mut(players);
|
||||
|
@ -1215,16 +1215,15 @@ impl NPC {
|
|||
state.quake_counter = 10;
|
||||
state.sound_manager.play_sfx(26);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
for _ in 0..3 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.y + self.rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
} else {
|
||||
let player = self.get_closest_player_mut(players);
|
|
@ -1,36 +1,25 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ggez::GameResult;
|
||||
use num_traits::clamp;
|
||||
|
||||
use crate::common::Direction;
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n066_misery_bubble(&mut self, state: &mut SharedGameState, map: &BTreeMap<u16, RefCell<NPC>>) -> GameResult {
|
||||
pub(crate) fn tick_n066_misery_bubble(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
for (&id, npc_cell) in map.iter() {
|
||||
if self.id == id { continue; }
|
||||
if let Some(npc) = npc_list.iter().find(|npc| npc.event_num == 1000) {
|
||||
self.action_counter2 = npc.id;
|
||||
self.target_x = npc.x;
|
||||
self.target_y = npc.y;
|
||||
|
||||
let npc = npc_cell.borrow();
|
||||
|
||||
if npc.event_num == 1000 {
|
||||
self.action_counter2 = npc.id;
|
||||
self.target_x = npc.x;
|
||||
self.target_y = npc.y;
|
||||
|
||||
let angle = ((self.y - self.target_y) as f64 / (self.x - self.target_x) as f64).atan();
|
||||
self.vel_x = (angle.cos() * 1024.0) as isize; // 2.0fix9
|
||||
self.vel_y = (angle.sin() * 1024.0) as isize;
|
||||
|
||||
log::info!("bubble toss: {:#x} {:#x}", self.vel_x, self.vel_y);
|
||||
|
||||
break;
|
||||
}
|
||||
let angle = ((self.y - self.target_y) as f64 / (self.x - self.target_x) as f64).atan();
|
||||
self.vel_x = (angle.cos() * 1024.0) as isize; // 2.0fix9
|
||||
self.vel_y = (angle.sin() * 1024.0) as isize;
|
||||
}
|
||||
|
||||
if self.action_counter2 == 0 {
|
||||
|
@ -41,23 +30,15 @@ impl NPC {
|
|||
self.action_num = 1;
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 1 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
|
||||
if self.anim_num > 1 {
|
||||
self.anim_num = 0;
|
||||
}
|
||||
}
|
||||
self.animate(1, 0, 1);
|
||||
|
||||
if (self.x - self.target_x).abs() < 3 * 0x200 && (self.y - self.target_y).abs() < 3 * 0x200 {
|
||||
self.action_num = 2;
|
||||
self.anim_num = 2;
|
||||
state.sound_manager.play_sfx(21);
|
||||
|
||||
if let Some(npc) = map.get(&self.action_counter2) {
|
||||
npc.borrow_mut().cond.set_alive(false);
|
||||
if let Some(npc) = npc_list.get_npc(self.action_counter2 as usize) {
|
||||
npc.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,14 +53,7 @@ impl NPC {
|
|||
self.cond.set_alive(false);
|
||||
}
|
||||
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > 3 {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
if self.anim_num > 3 {
|
||||
self.anim_num = 2;
|
||||
}
|
||||
}
|
||||
self.animate(3, 2, 3);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -92,7 +66,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n067_misery_floating(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n067_misery_floating(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -156,12 +130,12 @@ impl NPC {
|
|||
self.action_counter += 1;
|
||||
if self.action_counter == 30 {
|
||||
state.sound_manager.play_sfx(21);
|
||||
let mut npc = NPCMap::create_npc(66, &state.npc_table);
|
||||
let mut npc = NPC::create(66, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y - 16 * 0x200;
|
||||
npc.cond.set_alive(true);
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0, npc);
|
||||
}
|
||||
|
||||
if self.action_counter == 50 {
|
||||
|
@ -215,10 +189,23 @@ impl NPC {
|
|||
self.x += self.vel_x;
|
||||
self.y += self.vel_y;
|
||||
|
||||
match self.action_num {
|
||||
11 => {}
|
||||
14 => {}
|
||||
_ => {}
|
||||
if self.action_num == 11 || self.action_num == 14 {
|
||||
let (frame1, frame2) = match self.action_num {
|
||||
11 => (0, 1),
|
||||
14 => (2, 3),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if self.anim_counter > 0 {
|
||||
self.anim_counter -= 1;
|
||||
self.anim_num = frame2;
|
||||
} else {
|
||||
if self.rng.range(0..100) == 1 {
|
||||
self.anim_counter = 30;
|
||||
}
|
||||
|
||||
self.anim_num = frame1;
|
||||
}
|
||||
}
|
||||
|
||||
let dir_offset = if self.direction == Direction::Left { 0 } else { 8 };
|
||||
|
@ -232,7 +219,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n082_misery_standing(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n082_misery_standing(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -264,12 +251,12 @@ impl NPC {
|
|||
if self.action_counter == 30 {
|
||||
state.sound_manager.play_sfx(21);
|
||||
|
||||
let mut npc = NPCMap::create_npc(66, &state.npc_table);
|
||||
let mut npc = NPC::create(66, &state.npc_table);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y - 16 * 0x200;
|
||||
npc.cond.set_alive(true);
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0, npc);
|
||||
}
|
||||
|
||||
if self.action_counter == 50 {
|
||||
|
@ -350,14 +337,14 @@ impl NPC {
|
|||
if self.action_counter == 30 || self.action_counter == 40 || self.action_counter == 50 {
|
||||
state.sound_manager.play_sfx(33);
|
||||
|
||||
let mut npc = NPCMap::create_npc(11, &state.npc_table);
|
||||
let mut npc = NPC::create(11, &state.npc_table);
|
||||
npc.x = self.x + 8 * 0x200;
|
||||
npc.y = self.y - 8 * 0x200;
|
||||
npc.vel_x = 0x600;
|
||||
npc.vel_y = self.rng.range(-0x200..0) as isize;
|
||||
npc.cond.set_alive(true);
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
}
|
||||
50 => {
|
20
src/npc/ai/mod.rs
Normal file
20
src/npc/ai/mod.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
pub mod balrog;
|
||||
pub mod booster;
|
||||
pub mod chaco;
|
||||
pub mod characters;
|
||||
pub mod egg_corridor;
|
||||
pub mod first_cave;
|
||||
pub mod grasstown;
|
||||
pub mod igor;
|
||||
pub mod intro;
|
||||
pub mod maze;
|
||||
pub mod mimiga_village;
|
||||
pub mod misc;
|
||||
pub mod misery;
|
||||
pub mod pickups;
|
||||
pub mod quote;
|
||||
pub mod sand_zone;
|
||||
pub mod santa;
|
||||
pub mod sue;
|
||||
pub mod toroko;
|
||||
pub mod weapon_trail;
|
|
@ -1,8 +1,9 @@
|
|||
use ggez::GameResult;
|
||||
use num_traits::{abs, clamp};
|
||||
use num_traits::clamp;
|
||||
|
||||
use crate::common::Direction;
|
||||
use crate::npc::NPC;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
|
@ -1,7 +1,10 @@
|
|||
use crate::common::Direction;
|
||||
use ggez::GameResult;
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
|
||||
use crate::common::Direction;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -130,7 +133,7 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n150_quote(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
pub(crate) fn tick_n150_quote(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 => {
|
||||
self.action_num = 1;
|
||||
|
@ -157,17 +160,17 @@ impl NPC {
|
|||
|
||||
state.sound_manager.play_sfx(71);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
|
||||
for _ in 0..4 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
npc.vel_x = self.rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
11 => {
|
|
@ -2,12 +2,14 @@ use ggez::GameResult;
|
|||
use num_traits::{abs, clamp};
|
||||
|
||||
use crate::common::Direction;
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n044_polish(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
pub(crate) fn tick_n044_polish(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
self.anim_num = 0;
|
||||
|
@ -109,15 +111,16 @@ impl NPC {
|
|||
}
|
||||
|
||||
if self.life <= 100 {
|
||||
self.cond.set_drs_destroyed(true);
|
||||
npc_list.create_death_smoke(self.x, self.y, self.display_bounds.right, 8, state, &self.rng);
|
||||
state.sound_manager.play_sfx(25);
|
||||
self.cond.set_alive(false);
|
||||
|
||||
let mut npc = NPCMap::create_npc(45, &state.npc_table);
|
||||
let mut npc = NPC::create(45, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
for _ in 0..9 {
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,10 +286,16 @@ impl NPC {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n049_skullhead(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
let parent = self.get_parent_ref_mut(npc_list);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn tick_n124_sunstone(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num ==0 {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.x += 8 * 0x200;
|
||||
self.y += 8 * 0x200;
|
||||
|
@ -312,12 +321,12 @@ impl NPC {
|
|||
Direction::FacingPlayer => {}
|
||||
}
|
||||
|
||||
state.quake_counter= 20;
|
||||
state.quake_counter = 20;
|
||||
if self.action_counter % 8 == 0 {
|
||||
state.sound_manager.play_sfx(26);
|
||||
}
|
||||
}
|
||||
_ =>{}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.anim_rect = state.constants.npc.n124_sunstone[self.anim_num as usize];
|
|
@ -1,15 +1,16 @@
|
|||
use ggez::GameResult;
|
||||
|
||||
use crate::npc::NPC;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::player::Player;
|
||||
use num_traits::abs;
|
||||
|
||||
use crate::common::Direction;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub(crate) fn tick_n040_santa(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 =>{
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
self.action_num = 1;
|
||||
self.anim_num = 0;
|
||||
|
@ -42,7 +43,7 @@ impl NPC {
|
|||
3 | 4 => {
|
||||
if self.action_num == 3 {
|
||||
self.action_num = 4;
|
||||
self.anim_num =2;
|
||||
self.anim_num = 2;
|
||||
self.anim_counter = 0;
|
||||
}
|
||||
|
|
@ -1,16 +1,15 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ggez::GameResult;
|
||||
use num_traits::clamp;
|
||||
|
||||
use crate::common::Direction;
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
pub fn tick_n042_sue(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], map: &BTreeMap<u16, RefCell<NPC>>) -> GameResult {
|
||||
pub fn tick_n042_sue(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
||||
match self.action_num {
|
||||
0 | 1 => {
|
||||
if self.action_num == 0 {
|
||||
|
@ -113,17 +112,12 @@ impl NPC {
|
|||
self.vel_y = 0;
|
||||
self.action_num = 14;
|
||||
|
||||
if let Some(parent_id) = map.iter()
|
||||
.filter(|(&id, _)| id != self.id)
|
||||
.find_map(|(id, npc_cell)|
|
||||
if npc_cell.borrow().event_num == 501 { Some(*id) } else { None }) {
|
||||
self.parent_id = parent_id;
|
||||
}
|
||||
self.parent_id = npc_list.iter_alive()
|
||||
.find_map(|npc| if npc.event_num == 501 { Some(npc.id) } else { None })
|
||||
.unwrap_or(0);
|
||||
}
|
||||
|
||||
if let Some(npc_cell) = map.get(&self.parent_id) {
|
||||
let npc = npc_cell.borrow();
|
||||
|
||||
if let Some(npc) = self.get_parent_ref_mut(npc_list) {
|
||||
self.direction = npc.direction.opposite();
|
||||
self.x = npc.x + npc.direction.vector_x() * 6 * 0x200;
|
||||
self.y = npc.y + 4 * 0x200;
|
||||
|
@ -139,15 +133,15 @@ impl NPC {
|
|||
self.vel_x = 0;
|
||||
self.anim_num = 0;
|
||||
|
||||
let mut npc = NPCMap::create_npc(257, &state.npc_table);
|
||||
let mut npc = NPC::create(257, &state.npc_table);
|
||||
npc.x = self.x + 128 * 0x200;
|
||||
npc.y = self.y;
|
||||
npc.direction = Direction::Left;
|
||||
npc.cond.set_alive(true);
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0, npc.clone());
|
||||
|
||||
npc.direction = Direction::Right;
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x80, npc);
|
||||
}
|
||||
|
||||
state.npc_super_pos = (
|
|
@ -1,9 +1,10 @@
|
|||
use ggez::GameResult;
|
||||
use num_traits::clamp;
|
||||
|
||||
use crate::common::Direction;
|
||||
use ggez::GameResult;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
|
@ -1,7 +1,5 @@
|
|||
use ggez::GameResult;
|
||||
use num_traits::{abs, clamp};
|
||||
|
||||
use crate::common::Direction;
|
||||
use crate::npc::NPC;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
use crate::caret::CaretType;
|
||||
use crate::common::{Direction, Rect, CDEG_RAD};
|
||||
use ggez::GameResult;
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{CDEG_RAD, Direction, Rect};
|
||||
use crate::npc::NPC;
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -33,7 +36,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
impl BossNPC {
|
||||
pub(crate) fn tick_b02_balfrog(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) {
|
||||
pub(crate) fn tick_b02_balfrog(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) {
|
||||
match self.parts[0].action_num {
|
||||
0 => {
|
||||
self.hurt_sound[0] = 52;
|
||||
|
@ -72,7 +75,7 @@ impl BossNPC {
|
|||
self.parts[2].cond.set_alive(true);
|
||||
self.parts[2].damage = 5;
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
|
||||
for _ in 0..8 {
|
||||
npc.cond.set_alive(true);
|
||||
|
@ -82,7 +85,7 @@ impl BossNPC {
|
|||
npc.vel_x = self.parts[0].rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.parts[0].rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
20 | 21 => {
|
||||
|
@ -163,15 +166,15 @@ impl BossNPC {
|
|||
self.parts[0].action_num = 110;
|
||||
}
|
||||
|
||||
let mut npc = NPCMap::create_npc(110, &state.npc_table);
|
||||
let mut npc = NPC::create(110, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].rng.range(4..16) as isize * 16 * 0x200;
|
||||
npc.y = self.parts[0].rng.range(0..4) as isize * 16 * 0x200;
|
||||
npc.direction = Direction::FacingPlayer;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x80, npc);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
|
||||
for _ in 0..4 {
|
||||
npc.cond.set_alive(true);
|
||||
|
@ -181,7 +184,7 @@ impl BossNPC {
|
|||
npc.vel_x = self.parts[0].rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.parts[0].rng.range(-0x600..0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
state.quake_counter = 30;
|
||||
|
@ -243,14 +246,14 @@ impl BossNPC {
|
|||
let deg = f64::atan2(py as f64, px as f64)
|
||||
+ self.parts[0].rng.range(-16..16) as f64 * CDEG_RAD;
|
||||
|
||||
let mut npc = NPCMap::create_npc(108, &state.npc_table);
|
||||
let mut npc = NPC::create(108, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].x + self.parts[0].direction.vector_x() * 2 * 16 * 0x200;
|
||||
npc.y = self.parts[0].y - 8 * 0x200;
|
||||
npc.vel_x = (deg.cos() * -512.0) as isize;
|
||||
npc.vel_y = (deg.sin() * -512.0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
|
||||
state.sound_manager.play_sfx(39);
|
||||
|
||||
|
@ -320,27 +323,27 @@ impl BossNPC {
|
|||
self.parts[0].display_bounds.top = 48 * 0x200;
|
||||
self.parts[0].display_bounds.bottom = 16 * 0x200;
|
||||
|
||||
let mut npc = NPCMap::create_npc(104, &state.npc_table);
|
||||
let mut npc = NPC::create(104, &state.npc_table);
|
||||
for _ in 0..2 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].rng.range(4..16) as isize * 16 * 0x200;
|
||||
npc.y = self.parts[0].rng.range(0..4) as isize * 16 * 0x200;
|
||||
npc.direction = Direction::FacingPlayer;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x80, npc.clone());
|
||||
}
|
||||
|
||||
let mut npc = NPCMap::create_npc(110, &state.npc_table);
|
||||
let mut npc = NPC::create(110, &state.npc_table);
|
||||
for _ in 0..6 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].rng.range(4..16) as isize * 16 * 0x200;
|
||||
npc.y = self.parts[0].rng.range(0..4) as isize * 16 * 0x200;
|
||||
npc.direction = Direction::FacingPlayer;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x80, npc.clone());
|
||||
}
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
for _ in 0..8 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].x + self.parts[0].rng.range(-12..12) as isize * 0x200;
|
||||
|
@ -349,7 +352,7 @@ impl BossNPC {
|
|||
npc.vel_y = self.parts[0].rng.range(-0x600..0) as isize;
|
||||
npc.direction = Direction::Left;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
let player = self.parts[0].get_closest_player_mut(players);
|
||||
|
@ -379,7 +382,7 @@ impl BossNPC {
|
|||
|
||||
state.sound_manager.play_sfx(72);
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
for _ in 0..8 {
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].x + self.parts[0].rng.range(-12..12) as isize * 0x200;
|
||||
|
@ -387,20 +390,20 @@ impl BossNPC {
|
|||
npc.vel_x = self.parts[0].rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.parts[0].rng.range(-0x600..0) as isize;
|
||||
npc.direction = Direction::Left;
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
|
||||
self.parts[0].action_counter += 1;
|
||||
if (self.parts[0].action_counter % 5) == 0 {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].x + self.parts[0].rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.parts[0].y + self.parts[0].rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.parts[0].rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.parts[0].rng.range(-0x600..0) as isize;
|
||||
npc.direction = Direction::Left;
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
|
||||
self.parts[0].x += if (self.parts[0].action_counter / 2 % 2) != 0 {
|
||||
|
@ -435,14 +438,14 @@ impl BossNPC {
|
|||
}
|
||||
|
||||
if (self.parts[0].action_counter % 9) == 0 {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].x + self.parts[0].rng.range(-12..12) as isize * 0x200;
|
||||
npc.y = self.parts[0].y + self.parts[0].rng.range(-12..12) as isize * 0x200;
|
||||
npc.vel_x = self.parts[0].rng.range(-0x155..0x155) as isize;
|
||||
npc.vel_y = self.parts[0].rng.range(-0x600..0) as isize;
|
||||
npc.direction = Direction::Left;
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
|
||||
if self.parts[0].action_counter > 150 {
|
||||
|
@ -543,7 +546,7 @@ impl BossNPC {
|
|||
self.parts[2].x = self.parts[0].x;
|
||||
self.parts[2].y = self.parts[0].y;
|
||||
}
|
||||
_ => { }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use ggez::{Context, GameResult};
|
||||
|
||||
|
@ -7,6 +6,7 @@ use crate::bullet::BulletManager;
|
|||
use crate::common::{Direction, interpolate_fix9_scale};
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
@ -31,11 +31,17 @@ pub struct BossNPC {
|
|||
|
||||
impl BossNPC {
|
||||
pub fn new() -> BossNPC {
|
||||
let mut parts = [{
|
||||
let mut part = NPC::empty();
|
||||
part.cond.set_drs_boss(true);
|
||||
part
|
||||
}; 20];
|
||||
let mut parts = unsafe {
|
||||
let mut parts_uninit: [NPC; 20] = MaybeUninit::uninit().assume_init();
|
||||
|
||||
for part in parts_uninit.iter_mut() {
|
||||
*part = NPC::empty();
|
||||
part.cond.set_drs_boss(true);
|
||||
}
|
||||
|
||||
parts_uninit
|
||||
};
|
||||
|
||||
parts[0].cond.set_alive(true);
|
||||
|
||||
for (i, part) in parts.iter_mut().enumerate() {
|
||||
|
@ -56,16 +62,16 @@ impl BossNPC {
|
|||
}
|
||||
}
|
||||
|
||||
impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &BulletManager)> for BossNPC {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (players, map, stage, bullet_manager): ([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &BulletManager)) -> GameResult {
|
||||
impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)> for BossNPC {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (players, npc_list, _stage, bullet_manager): ([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)) -> GameResult {
|
||||
if !self.parts[0].cond.alive() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match self.boss_type {
|
||||
1 => self.tick_b01_omega(state, players, map, bullet_manager),
|
||||
2 => self.tick_b02_balfrog(state, players),
|
||||
3 => self.tick_b03_monster_x(state, players, map),
|
||||
1 => self.tick_b01_omega(state, players, npc_list, bullet_manager),
|
||||
2 => self.tick_b02_balfrog(state, players, npc_list),
|
||||
3 => self.tick_b03_monster_x(state, players, npc_list),
|
||||
4 => self.tick_b04_core(),
|
||||
5 => self.tick_b05_ironhead(),
|
||||
6 => self.tick_b06_twins(),
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ggez::GameResult;
|
||||
use num_traits::{abs, clamp};
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{CDEG_RAD, Direction, Rect};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -72,7 +71,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
impl BossNPC {
|
||||
pub(crate) fn tick_b03_monster_x(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_map: &BTreeMap<u16, RefCell<NPC>>) {
|
||||
pub(crate) fn tick_b03_monster_x(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) {
|
||||
match self.parts[0].action_num {
|
||||
0 => {
|
||||
self.parts[0].life = 1;
|
||||
|
@ -109,7 +108,7 @@ impl BossNPC {
|
|||
};
|
||||
self.parts[1].npc_flags.set_ignore_solidity(true);
|
||||
|
||||
self.parts[2] = self.parts[1];
|
||||
self.parts[2] = self.parts[1].clone();
|
||||
self.parts[2].direction = Direction::Right;
|
||||
|
||||
self.parts[3].cond.set_alive(true);
|
||||
|
@ -132,11 +131,11 @@ impl BossNPC {
|
|||
self.hurt_sound[3] = 54;
|
||||
self.death_sound[3] = 71;
|
||||
|
||||
self.parts[4] = self.parts[3];
|
||||
self.parts[4] = self.parts[3].clone();
|
||||
self.parts[3].target_x = 1;
|
||||
|
||||
self.parts[5] = self.parts[3];
|
||||
self.parts[6] = self.parts[3];
|
||||
self.parts[5] = self.parts[3].clone();
|
||||
self.parts[6] = self.parts[3].clone();
|
||||
self.parts[5].target_x = 2;
|
||||
self.parts[6].target_x = 3;
|
||||
self.parts[5].life = 100;
|
||||
|
@ -185,10 +184,10 @@ impl BossNPC {
|
|||
self.parts[9].npc_flags.set_invulnerable(true);
|
||||
self.parts[9].npc_flags.set_solid_soft(true);
|
||||
|
||||
self.parts[10] = self.parts[9];
|
||||
self.parts[10] = self.parts[9].clone();
|
||||
self.parts[10].x = self.parts[0].x + 64 * 0x200;
|
||||
|
||||
self.parts[11] = self.parts[9];
|
||||
self.parts[11] = self.parts[9].clone();
|
||||
self.parts[11].x = self.parts[0].x - 64 * 0x200;
|
||||
self.parts[11].y = self.parts[0].y + 56 * 0x200;
|
||||
self.parts[11].direction = Direction::Bottom;
|
||||
|
@ -197,10 +196,10 @@ impl BossNPC {
|
|||
self.parts[11].hit_bounds.top = 16 * 0x200;
|
||||
self.parts[11].hit_bounds.bottom = 8 * 0x200;
|
||||
|
||||
self.parts[12] = self.parts[11];
|
||||
self.parts[12] = self.parts[11].clone();
|
||||
self.parts[12].x = self.parts[0].x + 64 * 0x200;
|
||||
|
||||
self.parts[13] = self.parts[9];
|
||||
self.parts[13] = self.parts[9].clone();
|
||||
self.parts[13].display_bounds = Rect {
|
||||
left: 30 * 0x200,
|
||||
top: 16 * 0x200,
|
||||
|
@ -212,23 +211,27 @@ impl BossNPC {
|
|||
self.parts[13].npc_flags.0 = 0;
|
||||
self.parts[13].npc_flags.set_ignore_solidity(true);
|
||||
|
||||
self.parts[14] = self.parts[13];
|
||||
self.parts[14] = self.parts[13].clone();
|
||||
self.parts[14].action_counter2 = 10;
|
||||
self.parts[14].anim_num = 1;
|
||||
self.parts[14].display_bounds.left = 42 * 0x200;
|
||||
self.parts[14].display_bounds.right = 30 * 0x200;
|
||||
|
||||
self.parts[15] = self.parts[13];
|
||||
self.parts[15] = self.parts[13].clone();
|
||||
self.parts[15].action_counter2 = 11;
|
||||
self.parts[15].anim_num = 2;
|
||||
self.parts[15].display_bounds.top = 16 * 0x200;
|
||||
self.parts[15].display_bounds.bottom = 16 * 0x200;
|
||||
|
||||
self.parts[16] = self.parts[15];
|
||||
self.parts[16] = self.parts[15].clone();
|
||||
self.parts[16].action_counter2 = 12;
|
||||
self.parts[16].anim_num = 3;
|
||||
self.parts[16].display_bounds.left = 42 * 0x200;
|
||||
self.parts[16].display_bounds.right = 30 * 0x200;
|
||||
|
||||
for npc in self.parts.iter_mut() {
|
||||
npc.init_rng();
|
||||
}
|
||||
}
|
||||
10 | 11 => {
|
||||
if self.parts[0].action_num == 10 {
|
||||
|
@ -424,11 +427,11 @@ impl BossNPC {
|
|||
state.sound_manager.play_sfx(52);
|
||||
}
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].x + self.parts[0].rng.range(-72..72) as isize * 0x200;
|
||||
npc.y = self.parts[0].y + self.parts[0].rng.range(-64..64) as isize * 0x200;
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
|
||||
if self.parts[0].action_counter > 100 {
|
||||
self.parts[0].action_num = 1001;
|
||||
|
@ -446,19 +449,18 @@ impl BossNPC {
|
|||
part.cond.set_alive(false);
|
||||
}
|
||||
|
||||
for npc_cell in npc_map.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
if npc.cond.alive() && npc.npc_type == 158 {
|
||||
for npc in npc_list.iter_alive() {
|
||||
if npc.npc_type == 158 {
|
||||
npc.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
|
||||
let mut npc = NPCMap::create_npc(159, &state.npc_table);
|
||||
let mut npc = NPC::create(159, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].x;
|
||||
npc.y = self.parts[1].y - 24 * 0x200;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0, npc);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -472,18 +474,18 @@ impl BossNPC {
|
|||
self.parts[0].x += (((self.parts[9].x + self.parts[10].x + self.parts[11].x + self.parts[12].x) / 4) - self.parts[0].x) / 16;
|
||||
self.tick_b03_monster_x_face(7, state);
|
||||
|
||||
self.tick_b03_monster_x_frame(13, state);
|
||||
self.tick_b03_monster_x_frame(14, state);
|
||||
self.tick_b03_monster_x_frame(15, state);
|
||||
self.tick_b03_monster_x_frame(16, state);
|
||||
self.tick_b03_monster_x_frame(13, state, npc_list);
|
||||
self.tick_b03_monster_x_frame(14, state, npc_list);
|
||||
self.tick_b03_monster_x_frame(15, state, npc_list);
|
||||
self.tick_b03_monster_x_frame(16, state, npc_list);
|
||||
|
||||
self.tick_b03_monster_x_shield(1, state);
|
||||
self.tick_b03_monster_x_shield(2, state);
|
||||
|
||||
if self.parts[3].cond.alive() { self.tick_b03_monster_x_eye(3, state, &players); }
|
||||
if self.parts[4].cond.alive() { self.tick_b03_monster_x_eye(4, state, &players); }
|
||||
if self.parts[5].cond.alive() { self.tick_b03_monster_x_eye(5, state, &players); }
|
||||
if self.parts[6].cond.alive() { self.tick_b03_monster_x_eye(6, state, &players); }
|
||||
if self.parts[3].cond.alive() { self.tick_b03_monster_x_eye(3, state, &players, npc_list); }
|
||||
if self.parts[4].cond.alive() { self.tick_b03_monster_x_eye(4, state, &players, npc_list); }
|
||||
if self.parts[5].cond.alive() { self.tick_b03_monster_x_eye(5, state, &players, npc_list); }
|
||||
if self.parts[6].cond.alive() { self.tick_b03_monster_x_eye(6, state, &players, npc_list); }
|
||||
|
||||
if self.parts[0].life == 0 && self.parts[0].action_num < 1000 {
|
||||
self.parts[0].action_num = 1000;
|
||||
|
@ -657,7 +659,7 @@ impl BossNPC {
|
|||
self.parts[i].anim_rect = state.constants.npc.b03_monster_x[self.parts[i].anim_num as usize + dir_offset];
|
||||
}
|
||||
|
||||
fn tick_b03_monster_x_frame(&mut self, i: usize, state: &mut SharedGameState) {
|
||||
fn tick_b03_monster_x_frame(&mut self, i: usize, state: &mut SharedGameState, npc_list: &NPCList) {
|
||||
match self.parts[i].action_num {
|
||||
10 | 11 => {
|
||||
if self.parts[i].action_num == 10 {
|
||||
|
@ -671,7 +673,7 @@ impl BossNPC {
|
|||
self.parts[i].action_counter = 120;
|
||||
state.sound_manager.play_sfx(39);
|
||||
|
||||
let mut npc = NPCMap::create_npc(158, &state.npc_table);
|
||||
let mut npc = NPC::create(158, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
|
||||
match self.parts[i].anim_num {
|
||||
|
@ -698,7 +700,7 @@ impl BossNPC {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -773,7 +775,7 @@ impl BossNPC {
|
|||
self.parts[i].anim_rect = state.constants.npc.b03_monster_x[dir_offset];
|
||||
}
|
||||
|
||||
fn tick_b03_monster_x_eye(&mut self, i: usize, state: &mut SharedGameState, players: &[&mut Player; 2]) {
|
||||
fn tick_b03_monster_x_eye(&mut self, i: usize, state: &mut SharedGameState, players: &[&mut Player; 2], npc_list: &NPCList) {
|
||||
match self.parts[i].action_num {
|
||||
0 => {
|
||||
self.parts[i].npc_flags.set_shootable(false);
|
||||
|
@ -799,14 +801,14 @@ impl BossNPC {
|
|||
let deg = f64::atan2(py as f64, px as f64)
|
||||
+ self.parts[i].rng.range(-2..2) as f64 * CDEG_RAD;
|
||||
|
||||
let mut npc = NPCMap::create_npc(156, &state.npc_table);
|
||||
let mut npc = NPC::create(156, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[i].x;
|
||||
npc.y = self.parts[i].y;
|
||||
npc.vel_x = (deg.cos() * -1536.0) as isize;
|
||||
npc.vel_y = (deg.sin() * -1536.0) as isize;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
|
||||
state.sound_manager.play_sfx(39);
|
||||
self.parts[i].action_counter = 40;
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ggez::GameResult;
|
||||
|
||||
use crate::bullet::BulletManager;
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Direction, Rect};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
|
@ -21,7 +20,7 @@ impl NPC {
|
|||
if self.action_counter2 <= 2 && self.direction != Direction::Right {
|
||||
self.vel_y = -0x100;
|
||||
} else {
|
||||
self.cond.set_drs_destroyed(true);
|
||||
self.vanish(state);
|
||||
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +51,7 @@ impl NPC {
|
|||
}
|
||||
|
||||
impl BossNPC {
|
||||
pub(crate) fn tick_b01_omega(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_map: &BTreeMap<u16, RefCell<NPC>>, bullet_manager: &BulletManager) {
|
||||
pub(crate) fn tick_b01_omega(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList, bullet_manager: &BulletManager) {
|
||||
match self.parts[0].action_num {
|
||||
0 => {
|
||||
self.parts[0].cond.set_alive(true);
|
||||
|
@ -114,7 +113,6 @@ impl BossNPC {
|
|||
};
|
||||
self.hurt_sound[3] = 52;
|
||||
|
||||
|
||||
self.parts[4].cond.set_alive(true);
|
||||
self.parts[4].display_bounds = self.parts[3].display_bounds;
|
||||
self.parts[4].hit_bounds = self.parts[3].hit_bounds;
|
||||
|
@ -185,7 +183,7 @@ impl BossNPC {
|
|||
60 => {
|
||||
self.parts[0].action_counter += 1;
|
||||
if self.parts[0].action_counter % 3 == 0 && (20..80).contains(&self.parts[0].action_counter) {
|
||||
let mut npc = NPCMap::create_npc(48, &state.npc_table);
|
||||
let mut npc = NPC::create(48, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].x;
|
||||
npc.y = self.parts[0].y - 16 * 0x200;
|
||||
|
@ -197,7 +195,7 @@ impl BossNPC {
|
|||
Direction::Right
|
||||
};
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
state.sound_manager.play_sfx(39);
|
||||
}
|
||||
|
||||
|
@ -283,7 +281,7 @@ impl BossNPC {
|
|||
}
|
||||
|
||||
if self.parts[0].action_counter <= 29 && self.parts[0].action_counter % 5 == 0 {
|
||||
let mut npc = NPCMap::create_npc(48, &state.npc_table);
|
||||
let mut npc = NPC::create(48, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.parts[0].x;
|
||||
npc.y = self.parts[0].y - 16 * 0x200;
|
||||
|
@ -291,7 +289,7 @@ impl BossNPC {
|
|||
npc.vel_y = -0x333;
|
||||
npc.direction = Direction::Left;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
state.sound_manager.play_sfx(39);
|
||||
}
|
||||
}
|
||||
|
@ -355,11 +353,11 @@ impl BossNPC {
|
|||
let dest_x = self.parts[0].x + self.parts[0].rng.range(-0x30..0x30) as isize * 0x200;
|
||||
let dest_y = self.parts[0].y + self.parts[0].rng.range(-0x30..0x18) as isize * 0x200;
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = dest_x;
|
||||
npc.y = dest_y;
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc);
|
||||
state.create_caret(dest_x, dest_y, CaretType::Explosion, Direction::Left);
|
||||
|
||||
if self.parts[0].action_counter > 100 {
|
||||
|
@ -457,9 +455,8 @@ impl BossNPC {
|
|||
self.parts[0].damage = 0;
|
||||
self.parts[5].damage = 5;
|
||||
|
||||
for npc_cell in npc_map.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
if npc.cond.alive() && npc.npc_type == 48 {
|
||||
for npc in npc_list.iter_alive() {
|
||||
if npc.npc_type == 48 {
|
||||
npc.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
|
|
278
src/npc/list.rs
Normal file
278
src/npc/list.rs
Normal file
|
@ -0,0 +1,278 @@
|
|||
use std::cell::{Cell, UnsafeCell};
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use ggez::{GameError, GameResult};
|
||||
|
||||
use crate::npc::NPC;
|
||||
|
||||
/// Maximum capacity of NPCList
|
||||
const NPC_LIST_MAX_CAP: usize = 512;
|
||||
|
||||
/// A data structure for storing an NPC list for current stage.
|
||||
/// Provides multiple mutable references to NPC objects with internal sanity checks and lifetime bounds.
|
||||
pub struct NPCList {
|
||||
// UnsafeCell is required because we do mutable aliasing (ik, discouraged), prevents Rust/LLVM
|
||||
// from theoretically performing some optimizations that might break the code.
|
||||
npcs: Box<UnsafeCell<[NPC; NPC_LIST_MAX_CAP]>>,
|
||||
max_npc: Cell<u16>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NPCList {
|
||||
pub fn new() -> NPCList {
|
||||
let map = NPCList {
|
||||
npcs: Box::new(UnsafeCell::new(unsafe {
|
||||
let mut parts_uninit: [NPC; NPC_LIST_MAX_CAP] = MaybeUninit::uninit().assume_init();
|
||||
|
||||
for part in parts_uninit.iter_mut() {
|
||||
*part = NPC::empty();
|
||||
}
|
||||
|
||||
parts_uninit
|
||||
})),
|
||||
max_npc: Cell::new(0),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
for (idx, npc_ref) in map.npcs_mut().iter_mut().enumerate() {
|
||||
npc_ref.id = idx as u16;
|
||||
}
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/// Inserts NPC into list in first available slot after given ID.
|
||||
pub fn spawn(&self, min_id: u16, mut npc: NPC) -> GameResult {
|
||||
let npc_len = unsafe { self.npcs().len() };
|
||||
|
||||
if min_id as usize >= npc_len {
|
||||
return Err(GameError::InvalidValue("NPC ID is out of bounds".to_string()));
|
||||
}
|
||||
|
||||
for id in min_id..(npc_len as u16) {
|
||||
let npc_ref = unsafe { self.npcs_mut().get_unchecked_mut(id as usize) };
|
||||
|
||||
if !npc_ref.cond.alive() {
|
||||
npc.id = id;
|
||||
|
||||
if npc.tsc_direction == 0 {
|
||||
npc.tsc_direction = npc.direction as u16;
|
||||
}
|
||||
|
||||
npc.init_rng();
|
||||
|
||||
*npc_ref = npc;
|
||||
|
||||
if self.max_npc.get() <= id {
|
||||
self.max_npc.replace(id + 1);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(GameError::InvalidValue("No free NPC slot found!".to_string()))
|
||||
}
|
||||
|
||||
/// Inserts the NPC at specified slot.
|
||||
pub fn spawn_at_slot(&self, id: u16, mut npc: NPC) -> GameResult {
|
||||
let npc_len = unsafe { self.npcs().len() };
|
||||
|
||||
if id as usize >= npc_len {
|
||||
return Err(GameError::InvalidValue("NPC ID is out of bounds".to_string()));
|
||||
}
|
||||
|
||||
npc.id = id;
|
||||
|
||||
if npc.tsc_direction == 0 {
|
||||
npc.tsc_direction = npc.direction as u16;
|
||||
}
|
||||
|
||||
npc.init_rng();
|
||||
|
||||
unsafe {
|
||||
let npc_ref = self.npcs_mut().get_unchecked_mut(id as usize);
|
||||
*npc_ref = npc;
|
||||
}
|
||||
|
||||
if self.max_npc.get() <= id {
|
||||
self.max_npc.replace(id + 1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to NPC from this list.
|
||||
pub fn get_npc<'a: 'b, 'b>(&'a self, id: usize) -> Option<&'b mut NPC> {
|
||||
unsafe {
|
||||
self.npcs_mut().get_mut(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator that iterates over allocated (not up to it's capacity) NPC slots.
|
||||
pub fn iter(&self) -> NPCListMutableIterator {
|
||||
NPCListMutableIterator::new(self)
|
||||
}
|
||||
|
||||
/// Returns an iterator over alive NPC slots.
|
||||
pub fn iter_alive(&self) -> NPCListMutableAliveIterator {
|
||||
NPCListMutableAliveIterator::new(self)
|
||||
}
|
||||
|
||||
/// Removes all NPCs from this list and resets it's capacity.
|
||||
pub fn clear(&self) {
|
||||
for (idx, npc) in self.iter_alive().enumerate() {
|
||||
*npc = NPC::empty();
|
||||
npc.id = idx as u16;
|
||||
}
|
||||
|
||||
self.max_npc.replace(0);
|
||||
}
|
||||
|
||||
/// Returns current capacity of this NPC list.
|
||||
pub fn current_capacity(&self) -> u16 {
|
||||
self.max_npc.get()
|
||||
}
|
||||
|
||||
/// Returns maximum capacity of this NPC list.
|
||||
pub fn max_capacity(&self) -> u16 {
|
||||
NPC_LIST_MAX_CAP as u16
|
||||
}
|
||||
|
||||
unsafe fn npcs<'a: 'b, 'b>(&'a self) -> &'b [NPC; NPC_LIST_MAX_CAP] {
|
||||
&*self.npcs.get()
|
||||
}
|
||||
|
||||
unsafe fn npcs_mut<'a: 'b, 'b>(&'a self) -> &'b mut [NPC; NPC_LIST_MAX_CAP] {
|
||||
&mut *self.npcs.get()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NPCListMutableIterator<'a> {
|
||||
index: u16,
|
||||
map: &'a NPCList,
|
||||
}
|
||||
|
||||
impl<'a> NPCListMutableIterator<'a> {
|
||||
pub fn new(map: &'a NPCList) -> NPCListMutableIterator<'a> {
|
||||
NPCListMutableIterator {
|
||||
index: 0,
|
||||
map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for NPCListMutableIterator<'a> {
|
||||
type Item = &'a mut NPC;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.index >= self.map.max_npc.get() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let item = unsafe { self.map.npcs_mut().get_mut(self.index as usize) };
|
||||
self.index += 1;
|
||||
|
||||
item
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NPCListMutableAliveIterator<'a> {
|
||||
index: u16,
|
||||
map: &'a NPCList,
|
||||
}
|
||||
|
||||
impl<'a> NPCListMutableAliveIterator<'a> {
|
||||
pub fn new(map: &'a NPCList) -> NPCListMutableAliveIterator<'a> {
|
||||
NPCListMutableAliveIterator {
|
||||
index: 0,
|
||||
map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for NPCListMutableAliveIterator<'a> {
|
||||
type Item = &'a mut NPC;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if self.index >= self.map.max_npc.get() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let item = unsafe { self.map.npcs_mut().get_mut(self.index as usize) };
|
||||
self.index += 1;
|
||||
|
||||
match item {
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
Some(ref npc) if npc.cond.alive() => {
|
||||
return item;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_npc_list() -> GameResult {
|
||||
impl NPC {
|
||||
fn test_tick(&mut self, _map: &NPCList) -> GameResult {
|
||||
self.action_counter += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let mut npc = NPC::empty();
|
||||
npc.cond.set_alive(true);
|
||||
|
||||
{
|
||||
let map = Box::new(NPCList::new());
|
||||
let mut ctr = 20;
|
||||
|
||||
map.spawn(0, npc.clone())?;
|
||||
map.spawn(2, npc.clone())?;
|
||||
map.spawn(256, npc.clone())?;
|
||||
|
||||
assert_eq!(map.iter_alive().count(), 3);
|
||||
|
||||
for npc_ref in map.iter() {
|
||||
if ctr > 0 {
|
||||
ctr -= 1;
|
||||
map.spawn(100, npc.clone())?;
|
||||
map.spawn(400, npc.clone())?;
|
||||
}
|
||||
|
||||
if npc_ref.cond.alive() {
|
||||
npc_ref.test_tick(&map)?;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(map.iter_alive().count(), 43);
|
||||
|
||||
for npc_ref in map.iter().skip(256) {
|
||||
if npc_ref.cond.alive() {
|
||||
npc_ref.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(map.iter_alive().count(), 22);
|
||||
|
||||
assert!(map.spawn((NPC_LIST_MAX_CAP + 1) as u16, npc.clone()).is_err());
|
||||
|
||||
map.clear();
|
||||
assert_eq!(map.iter_alive().count(), 0);
|
||||
|
||||
for i in 0..map.max_capacity() {
|
||||
map.spawn(i, npc.clone())?;
|
||||
}
|
||||
|
||||
assert!(map.spawn(0, npc.clone()).is_err());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
441
src/npc/mod.rs
441
src/npc/mod.rs
|
@ -1,52 +1,29 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::io;
|
||||
use std::io::Cursor;
|
||||
|
||||
use bitvec::vec::BitVec;
|
||||
use byteorder::{LE, ReadBytesExt};
|
||||
use ggez::{Context, GameResult};
|
||||
use num_traits::abs;
|
||||
|
||||
use crate::bitfield;
|
||||
use crate::caret::CaretType;
|
||||
use crate::bullet::BulletManager;
|
||||
use crate::common::{Condition, interpolate_fix9_scale, Rect};
|
||||
use crate::common::Direction;
|
||||
use crate::common::Flag;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::map::NPCData;
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::Player;
|
||||
use crate::rng::Xoroshiro32PlusPlus;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
use crate::str;
|
||||
use crate::bullet::BulletManager;
|
||||
|
||||
pub mod balrog;
|
||||
pub mod booster;
|
||||
pub mod ai;
|
||||
pub mod boss;
|
||||
pub mod chaco;
|
||||
pub mod characters;
|
||||
pub mod egg_corridor;
|
||||
pub mod first_cave;
|
||||
pub mod grasstown;
|
||||
pub mod igor;
|
||||
pub mod intro;
|
||||
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;
|
||||
pub mod santa;
|
||||
pub mod sue;
|
||||
pub mod toroko;
|
||||
pub mod weapon_trail;
|
||||
pub mod list;
|
||||
pub mod utils;
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -71,7 +48,8 @@ bitfield! {
|
|||
pub show_damage, set_show_damage: 15; // 0x8000
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
/// Represents an NPC object.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NPC {
|
||||
pub id: u16,
|
||||
pub npc_type: u16,
|
||||
|
@ -168,8 +146,8 @@ impl NPC {
|
|||
}
|
||||
}
|
||||
|
||||
impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &BulletManager)> for NPC {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (players, map, stage, bullet_manager): ([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &BulletManager)) -> GameResult {
|
||||
impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)> for NPC {
|
||||
fn tick(&mut self, state: &mut SharedGameState, (players, npc_list, stage, _bullet_manager): ([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)) -> GameResult {
|
||||
match self.npc_type {
|
||||
0 => self.tick_n000_null(),
|
||||
1 => self.tick_n001_experience(state),
|
||||
|
@ -180,17 +158,17 @@ impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &Bu
|
|||
6 => self.tick_n006_green_beetle(state),
|
||||
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, players),
|
||||
9 => self.tick_n009_balrog_falling_in(state, npc_list),
|
||||
10 => self.tick_n010_balrog_shooting(state, players, npc_list),
|
||||
11 => self.tick_n011_balrogs_projectile(state),
|
||||
12 => self.tick_n012_balrog_cutscene(state, players, map, stage),
|
||||
12 => self.tick_n012_balrog_cutscene(state, players, npc_list, stage),
|
||||
13 => self.tick_n013_forcefield(state),
|
||||
14 => self.tick_n014_key(state),
|
||||
15 => self.tick_n015_chest_closed(state),
|
||||
15 => self.tick_n015_chest_closed(state, npc_list),
|
||||
16 => self.tick_n016_save_point(state),
|
||||
17 => self.tick_n017_health_refill(state),
|
||||
18 => self.tick_n018_door(state),
|
||||
19 => self.tick_n019_balrog_bust_in(state),
|
||||
18 => self.tick_n018_door(state, npc_list),
|
||||
19 => self.tick_n019_balrog_bust_in(state, npc_list),
|
||||
20 => self.tick_n020_computer(state),
|
||||
21 => self.tick_n021_chest_open(state),
|
||||
22 => self.tick_n022_teleporter(state),
|
||||
|
@ -206,23 +184,24 @@ impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &Bu
|
|||
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, players),
|
||||
35 => self.tick_n035_mannan(state, npc_list),
|
||||
36 => self.tick_n036_balrog_hover(state, players, npc_list),
|
||||
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, players),
|
||||
41 => self.tick_n041_busted_door(state),
|
||||
42 => self.tick_n042_sue(state, players, map),
|
||||
42 => self.tick_n042_sue(state, players, npc_list),
|
||||
43 => self.tick_n043_chalkboard(state),
|
||||
44 => self.tick_n044_polish(state),
|
||||
44 => self.tick_n044_polish(state, npc_list),
|
||||
45 => self.tick_n045_baby(state),
|
||||
46 => self.tick_n046_hv_trigger(players),
|
||||
47 => self.tick_n047_sandcroc(state, players),
|
||||
48 => self.tick_n048_omega_projectiles(state),
|
||||
49 => self.tick_n049_skullhead(state, npc_list),
|
||||
52 => self.tick_n052_sitting_blue_robot(state),
|
||||
55 => self.tick_n055_kazuma(state),
|
||||
58 => self.tick_n058_basu(state, players),
|
||||
58 => self.tick_n058_basu(state, players, npc_list),
|
||||
59 => self.tick_n059_eye_door(state, players),
|
||||
60 => self.tick_n060_toroko(state, players),
|
||||
61 => self.tick_n061_king(state),
|
||||
|
@ -230,13 +209,13 @@ impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &Bu
|
|||
63 => self.tick_n063_toroko_stick(state),
|
||||
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),
|
||||
66 => self.tick_n066_misery_bubble(state, npc_list),
|
||||
67 => self.tick_n067_misery_floating(state, npc_list),
|
||||
68 => self.tick_n068_balrog_running(state, players, npc_list),
|
||||
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),
|
||||
72 => self.tick_n072_sprinkler(state, players, npc_list),
|
||||
73 => self.tick_n073_water_droplet(state, stage),
|
||||
74 => self.tick_n074_jack(state),
|
||||
75 => self.tick_n075_kanpachi(state, players),
|
||||
|
@ -246,47 +225,47 @@ impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &Bu
|
|||
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),
|
||||
82 => self.tick_n082_misery_standing(state, npc_list),
|
||||
83 => self.tick_n083_igor_cutscene(state),
|
||||
84 => self.tick_n084_basu_projectile(state),
|
||||
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),
|
||||
89 => self.tick_n089_igor_dead(state, players),
|
||||
88 => self.tick_n088_igor_boss(state, players, npc_list),
|
||||
89 => self.tick_n089_igor_dead(state, players, npc_list),
|
||||
91 => self.tick_n091_mimiga_cage(state),
|
||||
92 => self.tick_n092_sue_at_pc(state),
|
||||
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),
|
||||
98 => self.tick_n098_fan_right(state, players),
|
||||
99 => self.tick_n099_fan_down(state, players),
|
||||
96 => self.tick_n096_fan_left(state, players, npc_list),
|
||||
97 => self.tick_n097_fan_up(state, players, npc_list),
|
||||
98 => self.tick_n098_fan_right(state, players, npc_list),
|
||||
99 => self.tick_n099_fan_down(state, players, npc_list),
|
||||
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, players),
|
||||
105 => self.tick_n105_hey_bubble_low(state),
|
||||
106 => self.tick_n106_hey_bubble_high(state),
|
||||
107 => self.tick_n107_malco_broken(state),
|
||||
106 => self.tick_n106_hey_bubble_high(state, npc_list),
|
||||
107 => self.tick_n107_malco_broken(state, npc_list),
|
||||
108 => self.tick_n108_balfrog_projectile(state),
|
||||
109 => self.tick_n109_malco_powered_on(state, players),
|
||||
109 => self.tick_n109_malco_powered_on(state, players, npc_list),
|
||||
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),
|
||||
113 => self.tick_n113_professor_booster(state),
|
||||
114 => self.tick_n114_press(state, players),
|
||||
114 => self.tick_n114_press(state, players, npc_list),
|
||||
124 => self.tick_n124_sunstone(state),
|
||||
125 => self.tick_n125_hidden_item(state),
|
||||
125 => self.tick_n125_hidden_item(state, npc_list),
|
||||
129 => self.tick_n129_fireball_snake_trail(state),
|
||||
149 => self.tick_n149_horizontal_moving_block(state, players),
|
||||
150 => self.tick_n150_quote(state, players),
|
||||
149 => self.tick_n149_horizontal_moving_block(state, players, npc_list),
|
||||
150 => self.tick_n150_quote(state, players, npc_list),
|
||||
151 => self.tick_n151_blue_robot_standing(state),
|
||||
154 => self.tick_n154_gaudi_dead(state),
|
||||
156 => self.tick_n156_gaudi_projectile(state),
|
||||
157 => self.tick_n157_vertical_moving_block(state, players),
|
||||
157 => self.tick_n157_vertical_moving_block(state, players, npc_list),
|
||||
158 => self.tick_n158_fish_missile(state, players),
|
||||
192 => self.tick_n192_scooter(state),
|
||||
193 => self.tick_n193_broken_scooter(state),
|
||||
|
@ -297,7 +276,7 @@ impl GameEntity<([&mut Player; 2], &BTreeMap<u16, RefCell<NPC>>, &mut Stage, &Bu
|
|||
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),
|
||||
361 => self.tick_n361_gaudi_dashing(state, players, npc_list),
|
||||
_ => Ok(()),
|
||||
}?;
|
||||
|
||||
|
@ -422,344 +401,6 @@ impl PhysicalEntity for NPC {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct NPCMap {
|
||||
ids: HashSet<u16>,
|
||||
pub npcs: BTreeMap<u16, RefCell<NPC>>,
|
||||
/// NPCMap but for bosses and of static size.
|
||||
pub boss_map: BossNPC,
|
||||
}
|
||||
|
||||
impl NPCMap {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> NPCMap {
|
||||
NPCMap {
|
||||
ids: HashSet::new(),
|
||||
npcs: BTreeMap::new(),
|
||||
boss_map: BossNPC::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.ids.clear();
|
||||
self.npcs.clear();
|
||||
}
|
||||
|
||||
pub fn create_npc_from_data(&mut self, table: &NPCTable, data: &NPCData) -> &mut NPC {
|
||||
let display_bounds = table.get_display_bounds(data.npc_type);
|
||||
let hit_bounds = table.get_hit_bounds(data.npc_type);
|
||||
let (size, life, damage, flags, exp) = match table.get_entry(data.npc_type) {
|
||||
Some(entry) => { (entry.size, entry.life, entry.damage as u16, entry.npc_flags, entry.experience as u16) }
|
||||
None => { (1, 0, 0, NPCFlag(0), 0) }
|
||||
};
|
||||
let npc_flags = NPCFlag(data.flags | flags.0);
|
||||
|
||||
let npc = NPC {
|
||||
id: data.id,
|
||||
npc_type: data.npc_type,
|
||||
x: data.x as isize * 16 * 0x200,
|
||||
y: data.y as isize * 16 * 0x200,
|
||||
vel_x: 0,
|
||||
vel_y: 0,
|
||||
vel_x2: 0,
|
||||
vel_y2: 0,
|
||||
target_x: 0,
|
||||
target_y: 0,
|
||||
prev_x: 0,
|
||||
prev_y: 0,
|
||||
action_num: 0,
|
||||
anim_num: 0,
|
||||
flag_num: data.flag_num,
|
||||
event_num: data.event_num,
|
||||
shock: 0,
|
||||
exp,
|
||||
size,
|
||||
life,
|
||||
damage,
|
||||
cond: Condition(0x00),
|
||||
flags: Flag(0),
|
||||
direction: if npc_flags.spawn_facing_right() { Direction::Right } else { Direction::Left },
|
||||
tsc_direction: 0,
|
||||
npc_flags,
|
||||
display_bounds,
|
||||
hit_bounds,
|
||||
parent_id: 0,
|
||||
action_counter: 0,
|
||||
action_counter2: 0,
|
||||
anim_counter: 0,
|
||||
anim_rect: Rect::new(0, 0, 0, 0),
|
||||
rng: Xoroshiro32PlusPlus::new((data.id as u32)
|
||||
.wrapping_sub(data.npc_type as u32)
|
||||
.wrapping_add(data.flag_num as u32)
|
||||
.wrapping_mul(214013)
|
||||
.wrapping_add(2531011) >> 5),
|
||||
};
|
||||
|
||||
let cell = RefCell::new(npc);
|
||||
self.npcs.insert(data.id, cell);
|
||||
self.ids.insert(data.id);
|
||||
|
||||
self.npcs.get_mut(&data.id).unwrap().get_mut()
|
||||
}
|
||||
|
||||
pub fn create_npc(npc_type: u16, table: &NPCTable) -> NPC {
|
||||
let display_bounds = table.get_display_bounds(npc_type);
|
||||
let hit_bounds = table.get_hit_bounds(npc_type);
|
||||
let (size, life, damage, flags, exp) = match table.get_entry(npc_type) {
|
||||
Some(entry) => { (entry.size, entry.life, entry.damage as u16, entry.npc_flags, entry.experience as u16) }
|
||||
None => { (2, 0, 0, NPCFlag(0), 0) }
|
||||
};
|
||||
let npc_flags = NPCFlag(flags.0);
|
||||
|
||||
NPC {
|
||||
id: 0,
|
||||
npc_type,
|
||||
x: 0,
|
||||
y: 0,
|
||||
vel_x: 0,
|
||||
vel_y: 0,
|
||||
vel_x2: 0,
|
||||
vel_y2: 0,
|
||||
target_x: 0,
|
||||
target_y: 0,
|
||||
prev_x: 0,
|
||||
prev_y: 0,
|
||||
action_num: 0,
|
||||
anim_num: 0,
|
||||
flag_num: 0,
|
||||
event_num: 0,
|
||||
shock: 0,
|
||||
exp,
|
||||
size,
|
||||
life,
|
||||
damage,
|
||||
cond: Condition(0x00),
|
||||
flags: Flag(0),
|
||||
direction: if npc_flags.spawn_facing_right() { Direction::Right } else { Direction::Left },
|
||||
tsc_direction: 0,
|
||||
npc_flags,
|
||||
display_bounds,
|
||||
hit_bounds,
|
||||
parent_id: 0,
|
||||
action_counter: 0,
|
||||
action_counter2: 0,
|
||||
anim_counter: 0,
|
||||
anim_rect: Rect::new(0, 0, 0, 0),
|
||||
rng: Xoroshiro32PlusPlus::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn garbage_collect(&mut self) {
|
||||
for npc_cell in self.npcs.values_mut() {
|
||||
let mut npc = npc_cell.borrow();
|
||||
|
||||
if !npc.cond.alive() {
|
||||
self.ids.remove(&npc.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_by_event(&mut self, event_num: u16, game_flags: &mut BitVec) {
|
||||
for npc_cell in self.npcs.values_mut() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
if npc.event_num == event_num {
|
||||
npc.cond.set_alive(false);
|
||||
game_flags.set(npc.flag_num as usize, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_by_type(&mut self, npc_type: u16, state: &mut SharedGameState) {
|
||||
for npc_cell in self.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
if npc.npc_type == npc_type {
|
||||
npc.cond.set_alive(false);
|
||||
state.game_flags.set(npc.flag_num as usize, true);
|
||||
|
||||
match npc.size {
|
||||
1 => self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 3, state),
|
||||
2 => self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 7, state),
|
||||
3 => self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 12, state),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate_id(&mut self, start: u16) -> u16 {
|
||||
for i in start..(u16::MAX) {
|
||||
if !self.ids.contains(&i) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
0xffff
|
||||
}
|
||||
|
||||
pub fn create_death_effect(&self, x: isize, y: isize, radius: usize, count: usize, state: &mut SharedGameState) {
|
||||
let radius = radius as i32 / 0x200;
|
||||
|
||||
for _ in 0..count {
|
||||
let off_x = state.game_rng.range(-radius..radius) as isize * 0x200;
|
||||
let off_y = state.game_rng.range(-radius..radius) as isize * 0x200;
|
||||
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = Direction::Left;
|
||||
npc.x = x + off_x;
|
||||
npc.y = y + off_y;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
}
|
||||
|
||||
state.create_caret(x, y, CaretType::Explosion, Direction::Left);
|
||||
}
|
||||
|
||||
pub fn process_dead_npcs(&mut self, list: &[u16], has_missile: bool, player: &Player, state: &mut SharedGameState) {
|
||||
for id in list {
|
||||
if let Some(npc_cell) = self.npcs.get(id) {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
if let Some(table_entry) = state.npc_table.get_entry(npc.npc_type) {
|
||||
state.sound_manager.play_sfx(table_entry.death_sound);
|
||||
}
|
||||
|
||||
match npc.size {
|
||||
1 => { self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 3, state); }
|
||||
2 => { self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 7, state); }
|
||||
3 => { self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 12, state); }
|
||||
_ => {}
|
||||
};
|
||||
|
||||
if npc.exp != 0 {
|
||||
let rng = state.game_rng.range(0..4);
|
||||
match rng {
|
||||
0 => {
|
||||
let mut heart_pick = NPCMap::create_npc(87, &state.npc_table);
|
||||
heart_pick.cond.set_alive(true);
|
||||
heart_pick.direction = Direction::Left;
|
||||
heart_pick.x = npc.x;
|
||||
heart_pick.y = npc.y;
|
||||
heart_pick.exp = if npc.exp > 6 { 6 } else { 2 };
|
||||
|
||||
state.new_npcs.push(heart_pick);
|
||||
}
|
||||
1 if has_missile => {
|
||||
let mut missile_pick = NPCMap::create_npc(86, &state.npc_table);
|
||||
missile_pick.cond.set_alive(true);
|
||||
missile_pick.direction = Direction::Left;
|
||||
missile_pick.x = npc.x;
|
||||
missile_pick.y = npc.y;
|
||||
missile_pick.exp = if npc.exp > 6 { 3 } else { 1 };
|
||||
|
||||
state.new_npcs.push(missile_pick);
|
||||
}
|
||||
_ => {
|
||||
let mut exp = npc.exp;
|
||||
|
||||
while exp > 0 {
|
||||
let exp_piece = if exp >= 20 {
|
||||
exp -= 20;
|
||||
20
|
||||
} else if exp >= 5 {
|
||||
exp -= 5;
|
||||
5
|
||||
} else {
|
||||
exp -= 1;
|
||||
1
|
||||
};
|
||||
|
||||
let mut xp_npc = NPCMap::create_npc(1, &state.npc_table);
|
||||
xp_npc.cond.set_alive(true);
|
||||
xp_npc.direction = Direction::Left;
|
||||
xp_npc.x = npc.x;
|
||||
xp_npc.y = npc.y;
|
||||
xp_npc.exp = exp_piece;
|
||||
|
||||
state.new_npcs.push(xp_npc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.game_flags.set(npc.flag_num as usize, true);
|
||||
|
||||
// todo vanish / show damage
|
||||
|
||||
if npc.cond.drs_dont_remove() {
|
||||
npc.cond.set_drs_dont_remove(false);
|
||||
} else {
|
||||
npc.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.process_npc_changes(player, state);
|
||||
}
|
||||
|
||||
pub fn process_npc_changes(&mut self, player: &Player, state: &mut SharedGameState) {
|
||||
if !state.new_npcs.is_empty() {
|
||||
for mut npc in state.new_npcs.iter_mut() {
|
||||
let id = if npc.id == 0 {
|
||||
self.allocate_id(npc.get_start_index())
|
||||
} else {
|
||||
npc.id
|
||||
};
|
||||
|
||||
npc.id = id;
|
||||
if npc.tsc_direction == 0 {
|
||||
npc.tsc_direction = npc.direction as u16;
|
||||
}
|
||||
|
||||
if npc.direction == Direction::FacingPlayer {
|
||||
npc.direction = if npc.x < player.x {
|
||||
Direction::Right
|
||||
} else {
|
||||
Direction::Left
|
||||
};
|
||||
}
|
||||
|
||||
npc.rng = Xoroshiro32PlusPlus::new((npc.id as u32)
|
||||
.wrapping_sub(npc.npc_type as u32)
|
||||
.wrapping_add(npc.flag_num as u32)
|
||||
.wrapping_mul(214013)
|
||||
.wrapping_add(2531011) >> 5);
|
||||
|
||||
self.ids.insert(id);
|
||||
self.npcs.insert(id, RefCell::new(*npc));
|
||||
}
|
||||
|
||||
state.new_npcs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if at least one NPC with specified type is alive.
|
||||
pub fn is_alive_by_type(&self, npc_type: u16) -> bool {
|
||||
for npc_cell in self.npcs.values() {
|
||||
let npc = npc_cell.borrow();
|
||||
if npc.cond.alive() && npc.npc_type == npc_type {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if at least one NPC with specified event is alive.
|
||||
pub fn is_alive_by_event(&self, event_num: u16) -> bool {
|
||||
for npc_cell in self.npcs.values() {
|
||||
let npc = npc_cell.borrow();
|
||||
if npc.cond.alive() && npc.event_num == event_num {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NPCTableEntry {
|
||||
pub npc_flags: NPCFlag,
|
||||
pub life: u16,
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
use num_traits::abs;
|
||||
|
||||
use crate::npc::NPC;
|
||||
use crate::player::Player;
|
||||
|
||||
impl NPC {
|
||||
pub fn animate(&mut self, ticks_between_frames: u16, start_frame: u16, end_frame: u16) {
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > ticks_between_frames {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
if self.anim_num > end_frame {
|
||||
self.anim_num = start_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns index of player that's closest to the current 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
|
||||
}
|
||||
|
||||
/// Returns a reference to closest player.
|
||||
pub fn get_closest_player_mut<'a>(&self, players: [&'a mut Player; 2]) -> &'a mut Player {
|
||||
let idx = self.get_closest_player_idx_mut(&players);
|
||||
|
||||
players[idx]
|
||||
}
|
||||
}
|
356
src/npc/utils.rs
Normal file
356
src/npc/utils.rs
Normal file
|
@ -0,0 +1,356 @@
|
|||
///! Various utility functions for NPC-related objects
|
||||
|
||||
use num_traits::abs;
|
||||
|
||||
use crate::bullet::Bullet;
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Condition, Direction, Flag, Rect};
|
||||
use crate::map::NPCData;
|
||||
use crate::npc::{NPC, NPCFlag, NPCTable};
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::player::Player;
|
||||
use crate::rng::{RNG, Xoroshiro32PlusPlus};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
impl NPC {
|
||||
/// Initializes the RNG. Called when the [NPC] is being added to an [NPCList].
|
||||
pub(crate) fn init_rng(&mut self) {
|
||||
self.rng = Xoroshiro32PlusPlus::new((self.id as u32)
|
||||
.wrapping_sub(self.npc_type as u32)
|
||||
.wrapping_add(self.flag_num as u32)
|
||||
.wrapping_mul(214013)
|
||||
.wrapping_add(2531011) >> 5);
|
||||
}
|
||||
|
||||
/// Creates a new NPC object with properties that have been populated with data from given NPC data table.
|
||||
pub fn create(npc_type: u16, table: &NPCTable) -> NPC {
|
||||
let display_bounds = table.get_display_bounds(npc_type);
|
||||
let hit_bounds = table.get_hit_bounds(npc_type);
|
||||
let (size, life, damage, flags, exp) = match table.get_entry(npc_type) {
|
||||
Some(entry) => { (entry.size, entry.life, entry.damage as u16, entry.npc_flags, entry.experience as u16) }
|
||||
None => { (2, 0, 0, NPCFlag(0), 0) }
|
||||
};
|
||||
let npc_flags = NPCFlag(flags.0);
|
||||
|
||||
NPC {
|
||||
id: 0,
|
||||
npc_type,
|
||||
x: 0,
|
||||
y: 0,
|
||||
vel_x: 0,
|
||||
vel_y: 0,
|
||||
vel_x2: 0,
|
||||
vel_y2: 0,
|
||||
target_x: 0,
|
||||
target_y: 0,
|
||||
prev_x: 0,
|
||||
prev_y: 0,
|
||||
action_num: 0,
|
||||
anim_num: 0,
|
||||
flag_num: 0,
|
||||
event_num: 0,
|
||||
shock: 0,
|
||||
exp,
|
||||
size,
|
||||
life,
|
||||
damage,
|
||||
cond: Condition(0x00),
|
||||
flags: Flag(0),
|
||||
direction: if npc_flags.spawn_facing_right() { Direction::Right } else { Direction::Left },
|
||||
tsc_direction: 0,
|
||||
npc_flags,
|
||||
display_bounds,
|
||||
hit_bounds,
|
||||
parent_id: 0,
|
||||
action_counter: 0,
|
||||
action_counter2: 0,
|
||||
anim_counter: 0,
|
||||
anim_rect: Rect::new(0, 0, 0, 0),
|
||||
rng: Xoroshiro32PlusPlus::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_from_data(data: &NPCData, table: &NPCTable) -> NPC {
|
||||
let display_bounds = table.get_display_bounds(data.npc_type);
|
||||
let hit_bounds = table.get_hit_bounds(data.npc_type);
|
||||
let (size, life, damage, flags, exp) = match table.get_entry(data.npc_type) {
|
||||
Some(entry) => { (entry.size, entry.life, entry.damage as u16, entry.npc_flags, entry.experience as u16) }
|
||||
None => { (1, 0, 0, NPCFlag(0), 0) }
|
||||
};
|
||||
let npc_flags = NPCFlag(data.flags | flags.0);
|
||||
|
||||
NPC {
|
||||
id: data.id,
|
||||
npc_type: data.npc_type,
|
||||
x: data.x as isize * 16 * 0x200,
|
||||
y: data.y as isize * 16 * 0x200,
|
||||
vel_x: 0,
|
||||
vel_y: 0,
|
||||
vel_x2: 0,
|
||||
vel_y2: 0,
|
||||
target_x: 0,
|
||||
target_y: 0,
|
||||
prev_x: 0,
|
||||
prev_y: 0,
|
||||
action_num: 0,
|
||||
anim_num: 0,
|
||||
flag_num: data.flag_num,
|
||||
event_num: data.event_num,
|
||||
shock: 0,
|
||||
exp,
|
||||
size,
|
||||
life,
|
||||
damage,
|
||||
cond: Condition(0x00),
|
||||
flags: Flag(0),
|
||||
direction: if npc_flags.spawn_facing_right() { Direction::Right } else { Direction::Left },
|
||||
tsc_direction: 0,
|
||||
npc_flags,
|
||||
display_bounds,
|
||||
hit_bounds,
|
||||
parent_id: 0,
|
||||
action_counter: 0,
|
||||
action_counter2: 0,
|
||||
anim_counter: 0,
|
||||
anim_rect: Rect::new(0, 0, 0, 0),
|
||||
rng: Xoroshiro32PlusPlus::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to parent NPC (if present).
|
||||
pub fn get_parent_ref_mut<'a: 'b, 'b>(&self, npc_list: &'a NPCList) -> Option<&'b mut NPC> {
|
||||
match self.parent_id {
|
||||
0 => None,
|
||||
id if id == self.id => None,
|
||||
id => npc_list.get_npc(id as usize),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cycles animation frames in given range and speed.
|
||||
pub fn animate(&mut self, ticks_between_frames: u16, start_frame: u16, end_frame: u16) {
|
||||
self.anim_counter += 1;
|
||||
if self.anim_counter > ticks_between_frames {
|
||||
self.anim_counter = 0;
|
||||
self.anim_num += 1;
|
||||
if self.anim_num > end_frame {
|
||||
self.anim_num = start_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns index of player that's closest to the current 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
|
||||
}
|
||||
|
||||
/// Returns a reference to closest player.
|
||||
pub fn get_closest_player_mut<'a>(&self, players: [&'a mut Player; 2]) -> &'a mut Player {
|
||||
let idx = self.get_closest_player_idx_mut(&players);
|
||||
|
||||
players[idx]
|
||||
}
|
||||
|
||||
/// Returns true if the [NPC] collides with a [Bullet].
|
||||
pub fn collides_with_bullet(&self, bullet: &Bullet) -> bool {
|
||||
(
|
||||
self.npc_flags.shootable()
|
||||
&& (self.x - self.hit_bounds.right as isize) < (bullet.x + bullet.enemy_hit_width as isize)
|
||||
&& (self.x + self.hit_bounds.right as isize) > (bullet.x - bullet.enemy_hit_width as isize)
|
||||
&& (self.y - self.hit_bounds.top as isize) < (bullet.y + bullet.enemy_hit_height as isize)
|
||||
&& (self.y + self.hit_bounds.bottom as isize) > (bullet.y - bullet.enemy_hit_height as isize)
|
||||
) || (
|
||||
self.npc_flags.invulnerable()
|
||||
&& (self.x - self.hit_bounds.right as isize) < (bullet.x + bullet.hit_bounds.right as isize)
|
||||
&& (self.x + self.hit_bounds.right as isize) > (bullet.x - bullet.hit_bounds.left as isize)
|
||||
&& (self.y - self.hit_bounds.top as isize) < (bullet.y + bullet.hit_bounds.bottom as isize)
|
||||
&& (self.y + self.hit_bounds.bottom as isize) > (bullet.y - bullet.hit_bounds.top as isize)
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates experience drop for this NPC.
|
||||
pub fn create_xp_drop(&self, state: &SharedGameState, npc_list: &NPCList) {
|
||||
let mut exp = self.exp;
|
||||
|
||||
let mut xp_npc = NPC::create(1, &state.npc_table);
|
||||
xp_npc.cond.set_alive(true);
|
||||
xp_npc.direction = Direction::Left;
|
||||
xp_npc.x = self.x;
|
||||
xp_npc.y = self.y;
|
||||
|
||||
while exp > 0 {
|
||||
let exp_piece = if exp >= 20 {
|
||||
exp -= 20;
|
||||
20
|
||||
} else if exp >= 5 {
|
||||
exp -= 5;
|
||||
5
|
||||
} else {
|
||||
exp -= 1;
|
||||
1
|
||||
};
|
||||
|
||||
xp_npc.exp = exp_piece;
|
||||
|
||||
let _ = npc_list.spawn(0x100, xp_npc.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes the NPC disappear and turns it into damage value holder.
|
||||
pub fn vanish(&mut self, state: &SharedGameState) {
|
||||
let mut npc = NPC::create(3, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = self.x;
|
||||
npc.y = self.y;
|
||||
|
||||
*self = npc;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl NPCList {
|
||||
/// Returns true if at least one NPC with specified type is alive.
|
||||
#[inline]
|
||||
pub fn is_alive_by_type(&self, npc_type: u16) -> bool {
|
||||
self.iter_alive().any(|npc| npc.npc_type == npc_type)
|
||||
}
|
||||
|
||||
/// Returns true if at least one NPC with specified event is alive.
|
||||
#[inline]
|
||||
pub fn is_alive_by_event(&self, event_num: u16) -> bool {
|
||||
self.iter_alive().any(|npc| npc.event_num == event_num)
|
||||
}
|
||||
|
||||
/// Called once NPC is killed, creates smoke and drops.
|
||||
pub fn kill_npc(&self, id: usize, vanish: bool, can_drop_missile: bool, state: &mut SharedGameState) {
|
||||
if let Some(npc) = self.get_npc(id) {
|
||||
if let Some(table_entry) = state.npc_table.get_entry(npc.npc_type) {
|
||||
state.sound_manager.play_sfx(table_entry.death_sound);
|
||||
}
|
||||
|
||||
match npc.size {
|
||||
1 => { self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right, 3, state, &npc.rng); }
|
||||
2 => { self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right, 7, state, &npc.rng); }
|
||||
3 => { self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right, 12, state, &npc.rng); }
|
||||
_ => {}
|
||||
};
|
||||
|
||||
if npc.exp != 0 {
|
||||
let rng = npc.rng.range(0..4);
|
||||
match rng {
|
||||
0 => {
|
||||
let mut heart_pick = NPC::create(87, &state.npc_table);
|
||||
heart_pick.cond.set_alive(true);
|
||||
heart_pick.direction = Direction::Left;
|
||||
heart_pick.x = npc.x;
|
||||
heart_pick.y = npc.y;
|
||||
heart_pick.exp = if npc.exp > 6 { 6 } else { 2 };
|
||||
|
||||
let _ = self.spawn(0x100, heart_pick);
|
||||
}
|
||||
1 if can_drop_missile => {
|
||||
let mut missile_pick = NPC::create(86, &state.npc_table);
|
||||
missile_pick.cond.set_alive(true);
|
||||
missile_pick.direction = Direction::Left;
|
||||
missile_pick.x = npc.x;
|
||||
missile_pick.y = npc.y;
|
||||
missile_pick.exp = if npc.exp > 6 { 3 } else { 1 };
|
||||
|
||||
let _ = self.spawn(0x100, missile_pick);
|
||||
}
|
||||
_ => {
|
||||
npc.create_xp_drop(state, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.game_flags.set(npc.flag_num as usize, true);
|
||||
|
||||
if npc.npc_flags.show_damage() {
|
||||
// todo show damage
|
||||
if vanish {
|
||||
npc.vanish(state);
|
||||
}
|
||||
} else {
|
||||
npc.cond.set_alive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes NPCs whose event number matches the provided one.
|
||||
pub fn remove_by_event(&mut self, event_num: u16, state: &mut SharedGameState) {
|
||||
for npc in self.iter_alive() {
|
||||
if npc.event_num == event_num {
|
||||
npc.cond.set_alive(false);
|
||||
state.game_flags.set(npc.flag_num as usize, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes NPCs (and creates a smoke effect) whose type IDs match the provided one.
|
||||
pub fn remove_by_type(&mut self, npc_type: u16, state: &mut SharedGameState) {
|
||||
for npc in self.iter_alive() {
|
||||
if npc.npc_type == npc_type {
|
||||
npc.cond.set_alive(false);
|
||||
state.game_flags.set(npc.flag_num as usize, true);
|
||||
|
||||
match npc.size {
|
||||
1 => self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right, 3, state, &npc.rng),
|
||||
2 => self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right, 7, state, &npc.rng),
|
||||
3 => self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right, 12, state, &npc.rng),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates NPC death smoke diffusing in random directions.
|
||||
#[inline]
|
||||
pub fn create_death_smoke(&self, x: isize, y: isize, radius: usize, amount: usize, state: &mut SharedGameState, rng: &dyn RNG) {
|
||||
self.create_death_smoke_common(x, y, radius, amount, Direction::Left, state, rng)
|
||||
}
|
||||
|
||||
/// Creates NPC death smoke diffusing upwards.
|
||||
#[inline]
|
||||
pub fn create_death_smoke_up(&self, x: isize, y: isize, radius: usize, amount: usize, state: &mut SharedGameState, rng: &dyn RNG) {
|
||||
self.create_death_smoke_common(x, y, radius, amount, Direction::Up, state, rng)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn create_death_smoke_common(&self, x: isize, y: isize, radius: usize, amount: usize, direction: Direction, state: &mut SharedGameState, rng: &dyn RNG) {
|
||||
let radius = (radius / 0x200) as i32;
|
||||
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.direction = direction;
|
||||
|
||||
for _ in 0..amount {
|
||||
let off_x = rng.range(-radius..radius) as isize * 0x200;
|
||||
let off_y = rng.range(-radius..radius) as isize * 0x200;
|
||||
|
||||
npc.x = x + off_x;
|
||||
npc.y = y + off_y;
|
||||
|
||||
let _ = self.spawn(0x100, npc.clone());
|
||||
}
|
||||
|
||||
state.create_caret(x, y, CaretType::Explosion, Direction::Left);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ use crate::caret::CaretType;
|
|||
use crate::common::{Condition, Direction, Flag, Rect};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
use crate::npc::list::NPCList;
|
||||
|
||||
// -1 0 1 2
|
||||
// +------------
|
||||
|
@ -353,7 +354,7 @@ pub trait PhysicalEntity {
|
|||
}
|
||||
}
|
||||
|
||||
fn tick_map_collisions(&mut self, state: &mut SharedGameState, stage: &mut Stage) {
|
||||
fn tick_map_collisions(&mut self, state: &mut SharedGameState, _npc_list: &NPCList, stage: &mut Stage) {
|
||||
let hit_rect_size = clamp(self.hit_rect_size(), 1, 4);
|
||||
let hit_rect_size = hit_rect_size * hit_rect_size;
|
||||
|
||||
|
|
|
@ -10,8 +10,10 @@ use crate::entity::GameEntity;
|
|||
use crate::frame::Frame;
|
||||
use crate::input::dummy_player_controller::DummyPlayerController;
|
||||
use crate::input::player_controller::PlayerController;
|
||||
use crate::npc::NPCMap;
|
||||
use crate::npc::NPC;
|
||||
use crate::rng::RNG;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::npc::list::NPCList;
|
||||
|
||||
mod player_hit;
|
||||
|
||||
|
@ -145,7 +147,7 @@ impl Player {
|
|||
(self.appearance as u16 % 6) * 64 + if self.equip.has_mimiga_mask() { 32 } else { 0 }
|
||||
}
|
||||
|
||||
fn tick_normal(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
fn tick_normal(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
if !state.control_flags.interactions_disabled() && state.control_flags.control_enabled() {
|
||||
if self.equip.has_air_tank() {
|
||||
self.air = 1000;
|
||||
|
@ -440,13 +442,13 @@ impl Player {
|
|||
let horizontal_splash = self.vel_x > 0x200 || self.vel_x < -0x200;
|
||||
|
||||
if vertical_splash || horizontal_splash {
|
||||
let mut droplet = NPCMap::create_npc(73, &state.npc_table);
|
||||
let mut droplet = NPC::create(73, &state.npc_table);
|
||||
droplet.cond.set_alive(true);
|
||||
droplet.y = self.y;
|
||||
droplet.direction = if self.flags.water_splash_facing_right() { Direction::Right } else { Direction::Left };
|
||||
|
||||
for _ in 0..7 {
|
||||
droplet.cond.set_alive(true);
|
||||
droplet.direction = if self.flags.water_splash_facing_right() { Direction::Right } else { Direction::Left };
|
||||
droplet.x = self.x + (state.game_rng.range(-8..8) * 0x200) as isize;
|
||||
droplet.y = self.y;
|
||||
droplet.vel_x = if vertical_splash {
|
||||
(self.vel_x + state.game_rng.range(-0x200..0x200) as isize) - (self.vel_x / 2)
|
||||
} else if horizontal_splash {
|
||||
|
@ -456,7 +458,7 @@ impl Player {
|
|||
};
|
||||
droplet.vel_y = state.game_rng.range(-0x200..0x80) as isize;
|
||||
|
||||
state.new_npcs.push(droplet);
|
||||
let _ = npc_list.spawn(0x100, droplet.clone());
|
||||
}
|
||||
|
||||
state.sound_manager.play_sfx(56);
|
||||
|
@ -471,7 +473,7 @@ impl Player {
|
|||
|
||||
// spike damage
|
||||
if self.flags.hit_by_spike() {
|
||||
self.damage(10, state);
|
||||
self.damage(10, state, npc_list);
|
||||
}
|
||||
|
||||
// camera
|
||||
|
@ -509,7 +511,7 @@ impl Player {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn tick_ironhead(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||
fn tick_ironhead(&mut self, _state: &mut SharedGameState) -> GameResult {
|
||||
// todo ironhead boss controls
|
||||
Ok(())
|
||||
}
|
||||
|
@ -614,7 +616,7 @@ impl Player {
|
|||
self.anim_rect.bottom += offset;
|
||||
}
|
||||
|
||||
pub fn damage(&mut self, hp: isize, state: &mut SharedGameState) {
|
||||
pub fn damage(&mut self, hp: isize, state: &mut SharedGameState, npc_list: &NPCList) {
|
||||
if state.settings.god_mode || self.shock_counter > 0 {
|
||||
return;
|
||||
}
|
||||
|
@ -643,20 +645,20 @@ impl Player {
|
|||
state.textscript_vm.start_script(40);
|
||||
|
||||
state.create_caret(self.x, self.y, CaretType::Explosion, Direction::Left);
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
for _ in 0..0x40 {
|
||||
npc.x = self.x + state.game_rng.range(-10..10) as isize * 0x200;
|
||||
npc.y = self.y + state.game_rng.range(-10..10) as isize * 0x200;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
let _ = npc_list.spawn(0x100, npc.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for Player {
|
||||
fn tick(&mut self, state: &mut SharedGameState, _cust: ()) -> GameResult {
|
||||
impl GameEntity<&NPCList> for Player {
|
||||
fn tick(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
|
||||
if !self.cond.alive() {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -668,13 +670,13 @@ impl GameEntity<()> for Player {
|
|||
if self.shock_counter != 0 {
|
||||
self.shock_counter -= 1;
|
||||
} else if self.damage_taken != 0 {
|
||||
// SetValueView(&self.x, &self.y, self.exp_count); // todo: damage popup
|
||||
// todo: damage popup
|
||||
self.damage_taken = 0;
|
||||
}
|
||||
|
||||
// todo: add additional control modes like NXEngine has such as noclip?
|
||||
match self.control_mode {
|
||||
ControlMode::Normal => self.tick_normal(state)?,
|
||||
ControlMode::Normal => self.tick_normal(state, npc_list)?,
|
||||
ControlMode::IronHead => self.tick_ironhead(state)?,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use num_traits::abs;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{Condition, Direction, Flag, Rect};
|
||||
use crate::inventory::{AddExperienceResult, Inventory};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::{ControlMode, Player, TargetPlayer};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
@ -236,7 +238,7 @@ impl Player {
|
|||
flags
|
||||
}
|
||||
|
||||
fn tick_npc_collision(&mut self, id: TargetPlayer, state: &mut SharedGameState, npc: &mut NPC, inventory: &mut Inventory) {
|
||||
fn tick_npc_collision(&mut self, id: TargetPlayer, state: &mut SharedGameState, npc: &mut NPC, npc_list: &NPCList, inventory: &mut Inventory) {
|
||||
let flags: Flag;
|
||||
|
||||
if npc.npc_flags.solid_soft() {
|
||||
|
@ -314,29 +316,26 @@ impl Player {
|
|||
|| flags.hit_right_wall() && npc.vel_x < 0
|
||||
|| flags.hit_top_wall() && npc.vel_y > 0
|
||||
|| flags.hit_bottom_wall() && npc.vel_y < 0 {
|
||||
self.damage(npc.damage as isize, state);
|
||||
self.damage(npc.damage as isize, state, npc_list);
|
||||
}
|
||||
} else if flags.0 != 0 && npc.damage != 0 && !state.control_flags.interactions_disabled() {
|
||||
self.damage(npc.damage as isize, state);
|
||||
self.damage(npc.damage as isize, state, npc_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick_npc_collisions(&mut self, id: TargetPlayer, state: &mut SharedGameState, npc_map: &mut NPCMap, inventory: &mut Inventory) {
|
||||
pub fn tick_npc_collisions(&mut self, id: TargetPlayer, state: &mut SharedGameState, npc_list: &NPCList, boss: &mut BossNPC, inventory: &mut Inventory) {
|
||||
if !self.cond.alive() {
|
||||
return;
|
||||
}
|
||||
|
||||
for npc_cell in npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
if !npc.cond.alive() { continue; }
|
||||
|
||||
self.tick_npc_collision(id, state, npc.borrow_mut(), inventory);
|
||||
for npc in npc_list.iter_alive() {
|
||||
self.tick_npc_collision(id, state, npc, npc_list, inventory);
|
||||
}
|
||||
|
||||
for boss_npc in npc_map.boss_map.parts.iter_mut() {
|
||||
for boss_npc in boss.parts.iter_mut() {
|
||||
if boss_npc.cond.alive() {
|
||||
self.tick_npc_collision(id, state, boss_npc, inventory);
|
||||
self.tick_npc_collision(id, state, boss_npc, npc_list, inventory);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ impl GameProfile {
|
|||
state.control_flags.set_tick_world(true);
|
||||
state.control_flags.set_control_enabled(true);
|
||||
|
||||
state.sound_manager.play_song(self.current_song as usize, &state.constants, ctx);
|
||||
let _ = state.sound_manager.play_song(self.current_song as usize, &state.constants, ctx);
|
||||
|
||||
game_scene.inventory_player1.current_weapon = self.current_weapon as u16;
|
||||
game_scene.inventory_player1.current_item = self.current_item as u16;
|
||||
|
@ -238,8 +238,8 @@ impl GameProfile {
|
|||
data.write_u32::<LE>(slot.event_num)?;
|
||||
}
|
||||
|
||||
let mut something = [0u8; 0x80];
|
||||
data.write(&something);
|
||||
let something = [0u8; 0x80];
|
||||
data.write(&something)?;
|
||||
|
||||
data.write_u32::<BE>(0x464c4147)?;
|
||||
data.write(&self.flags)?;
|
||||
|
|
76
src/rng.rs
76
src/rng.rs
|
@ -1,14 +1,24 @@
|
|||
use std::cell::Cell;
|
||||
use std::ops::Range;
|
||||
|
||||
pub trait RNG {
|
||||
fn next(&self) -> i32;
|
||||
|
||||
fn range(&self, range: Range<i32>) -> i32 {
|
||||
range.start.wrapping_add((self.next() >> 2) % (range.end.wrapping_sub(range.start).wrapping_add(1)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Deterministic XorShift-based random number generator
|
||||
pub struct RNG(Cell<(u64, u64, u64, u64)>);
|
||||
pub struct XorShift(Cell<(u64, u64, u64, u64)>);
|
||||
|
||||
impl RNG {
|
||||
impl XorShift {
|
||||
pub fn new(seed: i32) -> Self {
|
||||
Self(Cell::new((seed as u64,
|
||||
(seed as u64).wrapping_add(0x9e3779b97f4a7c15),
|
||||
(seed as u64).wrapping_add(0xbdd3944475a73cf0),
|
||||
0
|
||||
Self(Cell::new((
|
||||
seed as u64,
|
||||
(seed as u64).wrapping_add(0x9e3779b97f4a7c15),
|
||||
(seed as u64).wrapping_add(0xbdd3944475a73cf0),
|
||||
0
|
||||
)))
|
||||
}
|
||||
|
||||
|
@ -29,11 +39,6 @@ impl RNG {
|
|||
result as i32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next(&self) -> i32 {
|
||||
self.next_u64() as i32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next_u32(&self) -> u32 {
|
||||
self.next_u64() as u32
|
||||
|
@ -46,44 +51,55 @@ impl RNG {
|
|||
pub fn load_state(&mut self, saved_state: (u64, u64, u64, u64)) {
|
||||
self.0.replace(saved_state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range(&self, range: std::ops::Range<i32>) -> i32 {
|
||||
range.start.wrapping_add((self.next_u32() >> 2) as i32 % (range.end.wrapping_sub(range.start).wrapping_add(1)))
|
||||
impl RNG for XorShift {
|
||||
#[inline]
|
||||
fn next(&self) -> i32 {
|
||||
self.next_u64() as i32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Xoroshiro32PlusPlus(u16, u16);
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Xoroshiro32PlusPlus(Cell<(u16, u16)>);
|
||||
|
||||
impl Xoroshiro32PlusPlus {
|
||||
pub fn new(seed: u32) -> Xoroshiro32PlusPlus {
|
||||
Xoroshiro32PlusPlus(
|
||||
Xoroshiro32PlusPlus(Cell::new((
|
||||
(seed & 0xffff) as u16,
|
||||
(seed >> 16 & 0xffff) as u16
|
||||
)
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn next_u16(&mut self) -> u16 {
|
||||
let mut result = (self.0.wrapping_add(self.1)).rotate_left(9).wrapping_add(self.0);
|
||||
pub fn next_u16(&self) -> u16 {
|
||||
let mut state = self.0.get();
|
||||
let mut result = (state.0.wrapping_add(state.1)).rotate_left(9).wrapping_add(state.0);
|
||||
|
||||
self.1 ^= self.0;
|
||||
self.0 = self.0.rotate_left(13) ^ self.1 ^ (self.1 << 5);
|
||||
self.1 = self.1.rotate_left(10);
|
||||
state.1 ^= state.0;
|
||||
state.0 = state.0.rotate_left(13) ^ state.1 ^ (state.1 << 5);
|
||||
state.1 = state.1.rotate_left(10);
|
||||
|
||||
self.0.replace(state);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn dump_state(&self) -> u32 {
|
||||
(self.0 as u32) | (self.1 as u32) << 16
|
||||
let state = self.0.get();
|
||||
|
||||
(state.0 as u32) | (state.1 as u32) << 16
|
||||
}
|
||||
|
||||
pub fn load_state(&mut self, state: u32) {
|
||||
self.0 = (state & 0xffff) as u16;
|
||||
self.1 = ((state >> 16) & 0xffff) as u16;
|
||||
}
|
||||
|
||||
pub fn range(&mut self, range: std::ops::Range<i32>) -> i32 {
|
||||
let num = ((self.next_u16() as u32) << 16 | self.next_u16() as u32) >> 2;
|
||||
range.start.wrapping_add(num as i32 % (range.end.wrapping_sub(range.start).wrapping_add(1)))
|
||||
self.0.replace((
|
||||
(state & 0xffff) as u16,
|
||||
((state >> 16) & 0xffff) as u16
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
impl RNG for Xoroshiro32PlusPlus {
|
||||
fn next(&self) -> i32 {
|
||||
(((self.next_u16() as u32) << 16 | self.next_u16() as u32) >> 2) as i32
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use ggez::{Context, GameResult, graphics, timer};
|
||||
use ggez::graphics::{BlendMode, Color, Drawable, DrawParam, FilterMode, mint};
|
||||
use ggez::graphics::spritebatch::SpriteBatch;
|
||||
|
@ -16,11 +14,14 @@ use crate::components::hud::HUD;
|
|||
use crate::components::stage_select::StageSelect;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::{Frame, UpdateTarget};
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::inventory::{Inventory, TakeExperienceResult};
|
||||
use crate::npc::{NPC, NPCMap};
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::NPC;
|
||||
use crate::physics::PhysicalEntity;
|
||||
use crate::player::{Player, PlayerAppearance, TargetPlayer};
|
||||
use crate::rng::RNG;
|
||||
use crate::rng::XorShift;
|
||||
use crate::scene::Scene;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::shared_game_state::{Season, SharedGameState};
|
||||
|
@ -29,7 +30,6 @@ use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState,
|
|||
use crate::texture_set::SizedBatch;
|
||||
use crate::ui::Components;
|
||||
use crate::weapon::WeaponType;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
|
||||
pub struct GameScene {
|
||||
pub tick: usize,
|
||||
|
@ -44,7 +44,8 @@ pub struct GameScene {
|
|||
pub inventory_player1: Inventory,
|
||||
pub inventory_player2: Inventory,
|
||||
pub stage_id: usize,
|
||||
pub npc_map: NPCMap,
|
||||
pub npc_list: NPCList,
|
||||
pub boss: BossNPC,
|
||||
pub bullet_manager: BulletManager,
|
||||
pub intro_mode: bool,
|
||||
water_visible: bool,
|
||||
|
@ -97,7 +98,8 @@ impl GameScene {
|
|||
wait: 16,
|
||||
},
|
||||
stage_id: id,
|
||||
npc_map: NPCMap::new(),
|
||||
npc_list: NPCList::new(),
|
||||
boss: BossNPC::new(),
|
||||
bullet_manager: BulletManager::new(),
|
||||
intro_mode: false,
|
||||
water_visible: true,
|
||||
|
@ -366,7 +368,7 @@ impl GameScene {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_black_bars(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
fn draw_black_bars(&self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -531,10 +533,8 @@ impl GameScene {
|
|||
}
|
||||
}
|
||||
|
||||
for npc_cell in self.npc_map.npcs.values() {
|
||||
let npc = npc_cell.borrow();
|
||||
|
||||
if !npc.cond.alive() || npc.cond.hidden() || (npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as isize * 0x200)
|
||||
for npc in self.npc_list.iter_alive() {
|
||||
if npc.cond.hidden() || (npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as isize * 0x200)
|
||||
|| npc.x > (self.frame.x + 128 * 0x200 + (state.canvas_size.0 as isize + npc.display_bounds.width() as isize) * 0x200)
|
||||
&& npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as isize * 0x200)
|
||||
|| npc.y > (self.frame.y + 128 * 0x200 + (state.canvas_size.1 as isize + npc.display_bounds.height() as isize) * 0x200)) {
|
||||
|
@ -790,19 +790,7 @@ impl GameScene {
|
|||
}
|
||||
|
||||
fn tick_npc_bullet_collissions(&mut self, state: &mut SharedGameState) {
|
||||
let mut dead_npcs = Vec::new();
|
||||
|
||||
for npc_cell in self.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
if npc.cond.drs_destroyed() {
|
||||
dead_npcs.push(npc.id);
|
||||
}
|
||||
|
||||
if !npc.cond.alive() {
|
||||
continue;
|
||||
}
|
||||
|
||||
for npc in self.npc_list.iter_alive() {
|
||||
if npc.npc_flags.shootable() && npc.npc_flags.interactable() {
|
||||
continue;
|
||||
}
|
||||
|
@ -812,21 +800,7 @@ impl GameScene {
|
|||
continue;
|
||||
}
|
||||
|
||||
let hit = (
|
||||
npc.npc_flags.shootable()
|
||||
&& (npc.x - npc.hit_bounds.right as isize) < (bullet.x + bullet.enemy_hit_width as isize)
|
||||
&& (npc.x + npc.hit_bounds.right as isize) > (bullet.x - bullet.enemy_hit_width as isize)
|
||||
&& (npc.y - npc.hit_bounds.top as isize) < (bullet.y + bullet.enemy_hit_height as isize)
|
||||
&& (npc.y + npc.hit_bounds.bottom as isize) > (bullet.y - bullet.enemy_hit_height as isize)
|
||||
) || (
|
||||
npc.npc_flags.invulnerable()
|
||||
&& (npc.x - npc.hit_bounds.right as isize) < (bullet.x + bullet.hit_bounds.right as isize)
|
||||
&& (npc.x + npc.hit_bounds.right as isize) > (bullet.x - bullet.hit_bounds.left as isize)
|
||||
&& (npc.y - npc.hit_bounds.top as isize) < (bullet.y + bullet.hit_bounds.bottom as isize)
|
||||
&& (npc.y + npc.hit_bounds.bottom as isize) > (bullet.y - bullet.hit_bounds.top as isize)
|
||||
);
|
||||
|
||||
if !hit {
|
||||
if !npc.collides_with_bullet(bullet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -876,17 +850,19 @@ impl GameScene {
|
|||
}
|
||||
}
|
||||
|
||||
if npc.cond.explode_die() && !npc.cond.drs_destroyed() {
|
||||
dead_npcs.push(npc.id);
|
||||
}
|
||||
if npc.cond.explode_die() {
|
||||
let can_drop_missile = [&self.inventory_player1, &self.inventory_player2]
|
||||
.iter()
|
||||
.any(|inv| inv.has_weapon(WeaponType::MissileLauncher) ||
|
||||
inv.has_weapon(WeaponType::SuperMissileLauncher));
|
||||
|
||||
npc.cond.set_drs_destroyed(false);
|
||||
self.npc_list.kill_npc(npc.id as usize, true, can_drop_missile, state);
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..self.npc_map.boss_map.parts.len() {
|
||||
for i in 0..self.boss.parts.len() {
|
||||
let mut idx = i;
|
||||
let (mut destroy_x, mut destroy_y, mut destroy_radius, mut destroy_count) = (0, 0, 0, 0);
|
||||
let mut npc = unsafe { self.npc_map.boss_map.parts.get_unchecked_mut(i) };
|
||||
let mut npc = unsafe { self.boss.parts.get_unchecked_mut(i) };
|
||||
if !npc.cond.alive() {
|
||||
continue;
|
||||
}
|
||||
|
@ -917,7 +893,7 @@ impl GameScene {
|
|||
if npc.npc_flags.shootable() {
|
||||
if npc.cond.damage_boss() {
|
||||
idx = 0;
|
||||
npc = unsafe { self.npc_map.boss_map.parts.get_unchecked_mut(0) };
|
||||
npc = unsafe { self.boss.parts.get_unchecked_mut(0) };
|
||||
}
|
||||
|
||||
npc.life = npc.life.saturating_sub(bullet.damage);
|
||||
|
@ -930,13 +906,11 @@ impl GameScene {
|
|||
state.control_flags.set_interactions_disabled(true);
|
||||
state.textscript_vm.start_script(npc.event_num);
|
||||
} else {
|
||||
state.sound_manager.play_sfx(self.npc_map.boss_map.death_sound[idx]);
|
||||
state.sound_manager.play_sfx(self.boss.death_sound[idx]);
|
||||
|
||||
destroy_x = npc.x;
|
||||
destroy_y = npc.y;
|
||||
destroy_radius = npc.display_bounds.right;
|
||||
destroy_count = 4usize * (2usize).pow((npc.size as u32).saturating_sub(1));
|
||||
let destroy_count = 4usize * (2usize).pow((npc.size as u32).saturating_sub(1));
|
||||
|
||||
self.npc_list.create_death_smoke(npc.x, npc.y, npc.display_bounds.right, destroy_count, state, &npc.rng);
|
||||
npc.cond.set_alive(false);
|
||||
}
|
||||
} else {
|
||||
|
@ -944,12 +918,12 @@ impl GameScene {
|
|||
for _ in 0..3 {
|
||||
state.create_caret(bullet.x, bullet.y, CaretType::HurtParticles, Direction::Left);
|
||||
}
|
||||
state.sound_manager.play_sfx(self.npc_map.boss_map.hurt_sound[idx]);
|
||||
state.sound_manager.play_sfx(self.boss.hurt_sound[idx]);
|
||||
}
|
||||
|
||||
npc.shock = 8;
|
||||
|
||||
npc = unsafe { self.npc_map.boss_map.parts.get_unchecked_mut(0) };
|
||||
npc = unsafe { self.boss.parts.get_unchecked_mut(0) };
|
||||
npc.shock = 8;
|
||||
}
|
||||
|
||||
|
@ -966,17 +940,6 @@ impl GameScene {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if destroy_count != 0 {
|
||||
self.npc_map.create_death_effect(destroy_x, destroy_y, destroy_radius, destroy_count, state);
|
||||
}
|
||||
}
|
||||
|
||||
if !dead_npcs.is_empty() {
|
||||
let missile = self.inventory_player1.has_weapon(WeaponType::MissileLauncher)
|
||||
|| self.inventory_player1.has_weapon(WeaponType::SuperMissileLauncher);
|
||||
self.npc_map.process_dead_npcs(&dead_npcs, missile, &self.player1, state);
|
||||
self.npc_map.garbage_collect();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1000,8 +963,8 @@ impl GameScene {
|
|||
0
|
||||
}
|
||||
};
|
||||
self.player1.tick(state, ())?;
|
||||
self.player2.tick(state, ())?;
|
||||
self.player1.tick(state, &self.npc_list)?;
|
||||
self.player2.tick(state, &self.npc_list)?;
|
||||
|
||||
if self.player1.damage > 0 {
|
||||
let xp_loss = self.player1.damage * if self.player1.equip.has_arms_barrier() { 1 } else { 2 };
|
||||
|
@ -1027,44 +990,32 @@ impl GameScene {
|
|||
self.player2.damage = 0;
|
||||
}
|
||||
|
||||
{
|
||||
for npc_cell in self.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
if npc.cond.alive() {
|
||||
npc.tick(state, ([&mut self.player1, &mut self.player2], &self.npc_map.npcs, &mut self.stage, &self.bullet_manager))?;
|
||||
}
|
||||
for npc in self.npc_list.iter_alive() {
|
||||
npc.tick(state, ([&mut self.player1, &mut self.player2], &self.npc_list, &mut self.stage, &self.bullet_manager))?;
|
||||
}
|
||||
self.boss.tick(state, ([&mut self.player1, &mut self.player2], &self.npc_list, &mut self.stage, &self.bullet_manager))?;
|
||||
|
||||
self.player1.tick_map_collisions(state, &self.npc_list, &mut self.stage);
|
||||
self.player1.tick_npc_collisions(TargetPlayer::Player1, state, &self.npc_list, &mut self.boss, &mut self.inventory_player1);
|
||||
|
||||
self.player2.tick_map_collisions(state, &self.npc_list, &mut self.stage);
|
||||
self.player2.tick_npc_collisions(TargetPlayer::Player2, state, &self.npc_list, &mut self.boss, &mut self.inventory_player2);
|
||||
|
||||
for npc in self.npc_list.iter_alive() {
|
||||
if !npc.npc_flags.ignore_solidity() {
|
||||
npc.tick_map_collisions(state, &self.npc_list, &mut self.stage);
|
||||
}
|
||||
}
|
||||
self.npc_map.boss_map.tick(state, ([&mut self.player1, &mut self.player2], &self.npc_map.npcs, &mut self.stage, &self.bullet_manager))?;
|
||||
self.npc_map.process_npc_changes(&self.player1, state);
|
||||
self.npc_map.garbage_collect();
|
||||
|
||||
self.player1.tick_map_collisions(state, &mut self.stage);
|
||||
self.player1.tick_npc_collisions(TargetPlayer::Player1, state, &mut self.npc_map, &mut self.inventory_player1);
|
||||
|
||||
self.player2.tick_map_collisions(state, &mut self.stage);
|
||||
self.player2.tick_npc_collisions(TargetPlayer::Player2, state, &mut self.npc_map, &mut self.inventory_player2);
|
||||
|
||||
for npc_cell in self.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
for npc in self.boss.parts.iter_mut() {
|
||||
if npc.cond.alive() && !npc.npc_flags.ignore_solidity() {
|
||||
npc.tick_map_collisions(state, &mut self.stage);
|
||||
npc.tick_map_collisions(state, &self.npc_list, &mut self.stage);
|
||||
}
|
||||
}
|
||||
for npc in self.npc_map.boss_map.parts.iter_mut() {
|
||||
if npc.cond.alive() && !npc.npc_flags.ignore_solidity() {
|
||||
npc.tick_map_collisions(state, &mut self.stage);
|
||||
}
|
||||
}
|
||||
self.npc_map.process_npc_changes(&self.player1, state);
|
||||
self.npc_map.garbage_collect();
|
||||
|
||||
self.tick_npc_bullet_collissions(state);
|
||||
self.npc_map.process_npc_changes(&self.player1, state);
|
||||
|
||||
self.bullet_manager.tick_bullets(state, [&self.player1, &self.player2], &mut self.stage);
|
||||
self.bullet_manager.tick_bullets(state, [&self.player1, &self.player2], &self.npc_list, &mut self.stage);
|
||||
state.tick_carets();
|
||||
|
||||
match self.frame.update_target {
|
||||
|
@ -1083,9 +1034,7 @@ impl GameScene {
|
|||
}
|
||||
}
|
||||
UpdateTarget::NPC(npc_id) => {
|
||||
if let Some(npc_cell) = self.npc_map.npcs.get(&npc_id) {
|
||||
let npc = npc_cell.borrow();
|
||||
|
||||
if let Some(npc) = self.npc_list.get_npc(npc_id as usize) {
|
||||
if npc.cond.alive() {
|
||||
self.frame.target_x = npc.x;
|
||||
self.frame.target_y = npc.y;
|
||||
|
@ -1093,7 +1042,7 @@ impl GameScene {
|
|||
}
|
||||
}
|
||||
UpdateTarget::Boss(boss_id) => {
|
||||
if let Some(boss) = self.npc_map.boss_map.parts.get(boss_id as usize) {
|
||||
if let Some(boss) = self.boss.parts.get(boss_id as usize) {
|
||||
if boss.cond.alive() {
|
||||
self.frame.target_x = boss.x;
|
||||
self.frame.target_y = boss.y;
|
||||
|
@ -1114,7 +1063,7 @@ impl GameScene {
|
|||
|
||||
self.hud_player1.tick(state, (&self.player1, &mut self.inventory_player1))?;
|
||||
self.hud_player2.tick(state, (&self.player2, &mut self.inventory_player2))?;
|
||||
self.boss_life_bar.tick(state, &self.npc_map)?;
|
||||
self.boss_life_bar.tick(state, (&self.npc_list, &self.boss))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1167,11 +1116,11 @@ impl GameScene {
|
|||
}
|
||||
|
||||
fn draw_debug_outlines(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
for npc in self.npc_map.npcs.values().map(|n| n.borrow()).filter(|n| n.cond.alive()) {
|
||||
self.draw_debug_npc(npc.deref(), state, ctx)?;
|
||||
for npc in self.npc_list.iter_alive() {
|
||||
self.draw_debug_npc(npc, state, ctx)?;
|
||||
}
|
||||
|
||||
for boss in self.npc_map.boss_map.parts.iter().filter(|n| n.cond.alive()) {
|
||||
for boss in self.boss.parts.iter().filter(|n| n.cond.alive()) {
|
||||
self.draw_debug_npc(boss, state, ctx)?;
|
||||
}
|
||||
|
||||
|
@ -1185,8 +1134,8 @@ impl Scene for GameScene {
|
|||
.wrapping_add(self.player1.x as i32)
|
||||
.wrapping_add(self.player1.y as i32)
|
||||
.wrapping_add(self.stage_id as i32)
|
||||
.wrapping_mul(7);
|
||||
state.game_rng = RNG::new(seed);
|
||||
.rotate_right(7);
|
||||
state.game_rng = XorShift::new(seed);
|
||||
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?);
|
||||
state.textscript_vm.suspend = false;
|
||||
|
||||
|
@ -1197,7 +1146,7 @@ impl Scene for GameScene {
|
|||
for npc_data in npcs.iter() {
|
||||
log::info!("creating npc: {:?}", npc_data);
|
||||
|
||||
let npc = self.npc_map.create_npc_from_data(&state.npc_table, npc_data);
|
||||
let mut npc = NPC::create_from_data(npc_data, &state.npc_table);
|
||||
if npc.npc_flags.appear_when_flag_set() {
|
||||
if let Some(true) = state.game_flags.get(npc_data.flag_num as usize) {
|
||||
npc.cond.set_alive(true);
|
||||
|
@ -1209,6 +1158,8 @@ impl Scene for GameScene {
|
|||
} else {
|
||||
npc.cond.set_alive(true);
|
||||
}
|
||||
|
||||
self.npc_list.spawn_at_slot(npc_data.id, npc)?;
|
||||
}
|
||||
|
||||
state.npc_table.tileset_name = self.tex_tileset_name.to_owned();
|
||||
|
@ -1223,7 +1174,7 @@ impl Scene for GameScene {
|
|||
}
|
||||
}
|
||||
|
||||
self.npc_map.boss_map.boss_type = self.stage.data.boss_no as u16;
|
||||
self.boss.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;
|
||||
|
@ -1244,7 +1195,7 @@ impl Scene for GameScene {
|
|||
} else {
|
||||
TouchControlType::Dialog
|
||||
};
|
||||
|
||||
|
||||
if state.settings.touch_controls {
|
||||
state.touch_controls.interact_icon = false;
|
||||
}
|
||||
|
@ -1292,16 +1243,12 @@ impl Scene for GameScene {
|
|||
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();
|
||||
|
||||
if npc.cond.alive() {
|
||||
npc.prev_x = npc.x;
|
||||
npc.prev_y = npc.y;
|
||||
}
|
||||
for npc in self.npc_list.iter_alive() {
|
||||
npc.prev_x = npc.x;
|
||||
npc.prev_y = npc.y;
|
||||
}
|
||||
|
||||
for npc in self.npc_map.boss_map.parts.iter_mut() {
|
||||
for npc in self.boss.parts.iter_mut() {
|
||||
if npc.cond.alive() {
|
||||
npc.prev_x = npc.x;
|
||||
npc.prev_y = npc.y;
|
||||
|
@ -1337,10 +1284,8 @@ impl Scene for GameScene {
|
|||
self.draw_light_map(state, ctx)?;
|
||||
}
|
||||
|
||||
self.npc_map.boss_map.draw(state, ctx, &self.frame)?;
|
||||
for npc_cell in self.npc_map.npcs.values() {
|
||||
let npc = npc_cell.borrow();
|
||||
|
||||
self.boss.draw(state, ctx, &self.frame)?;
|
||||
for npc in self.npc_list.iter_alive() {
|
||||
if npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as isize * 0x200)
|
||||
|| npc.x > (self.frame.x + 128 * 0x200 + (state.canvas_size.0 as isize + npc.display_bounds.width() as isize) * 0x200)
|
||||
&& npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as isize * 0x200)
|
||||
|
|
|
@ -13,9 +13,9 @@ use crate::caret::{Caret, CaretType};
|
|||
use crate::common::{ControlFlags, Direction, FadeState};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::input::touch_controls::TouchControls;
|
||||
use crate::npc::{NPC, NPCTable};
|
||||
use crate::npc::NPCTable;
|
||||
use crate::profile::GameProfile;
|
||||
use crate::rng::RNG;
|
||||
use crate::rng::XorShift;
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::settings::Settings;
|
||||
|
@ -24,7 +24,7 @@ use crate::sound::SoundManager;
|
|||
use crate::stage::StageData;
|
||||
use crate::str;
|
||||
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::texture_set::{g_mag, TextureSet};
|
||||
use crate::texture_set::{G_MAG, TextureSet};
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TimingMode {
|
||||
|
@ -87,9 +87,9 @@ pub struct SharedGameState {
|
|||
pub game_flags: BitVec,
|
||||
pub fade_state: FadeState,
|
||||
/// RNG used by game state, using it for anything else might cause unintended side effects and break replays.
|
||||
pub game_rng: RNG,
|
||||
pub game_rng: XorShift,
|
||||
/// RNG used by graphics effects that aren't dependent on game's state.
|
||||
pub effect_rng: RNG,
|
||||
pub effect_rng: XorShift,
|
||||
pub quake_counter: u16,
|
||||
pub teleporter_slots: Vec<(u16, u16)>,
|
||||
pub carets: Vec<Caret>,
|
||||
|
@ -98,7 +98,6 @@ pub struct SharedGameState {
|
|||
pub npc_table: NPCTable,
|
||||
pub npc_super_pos: (isize, isize),
|
||||
pub stages: Vec<StageData>,
|
||||
pub new_npcs: Vec<NPC>,
|
||||
pub frame_time: f64,
|
||||
pub scale: f32,
|
||||
pub shaders: Shaders,
|
||||
|
@ -122,7 +121,7 @@ impl SharedGameState {
|
|||
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
|
||||
let screen_size = graphics::drawable_size(ctx);
|
||||
let scale = screen_size.1.div(235.0).floor().max(1.0);
|
||||
unsafe { g_mag = scale };
|
||||
unsafe { G_MAG = scale };
|
||||
|
||||
let canvas_size = (screen_size.0 / scale, screen_size.1 / scale);
|
||||
|
||||
|
@ -161,8 +160,8 @@ impl SharedGameState {
|
|||
control_flags: ControlFlags(0),
|
||||
game_flags: bitvec::bitvec![0; 8000],
|
||||
fade_state: FadeState::Hidden,
|
||||
game_rng: RNG::new(0),
|
||||
effect_rng: RNG::new(Instant::now().elapsed().as_nanos() as i32),
|
||||
game_rng: XorShift::new(0),
|
||||
effect_rng: XorShift::new(Instant::now().elapsed().as_nanos() as i32),
|
||||
quake_counter: 0,
|
||||
teleporter_slots: Vec::with_capacity(8),
|
||||
carets: Vec::with_capacity(32),
|
||||
|
@ -171,7 +170,6 @@ impl SharedGameState {
|
|||
npc_table: NPCTable::new(),
|
||||
npc_super_pos: (0, 0),
|
||||
stages: Vec::with_capacity(96),
|
||||
new_npcs: Vec::with_capacity(8),
|
||||
frame_time: 0.0,
|
||||
scale,
|
||||
shaders: Shaders::new(ctx)?,
|
||||
|
@ -267,11 +265,10 @@ impl SharedGameState {
|
|||
self.control_flags.0 = 0;
|
||||
self.game_flags = bitvec::bitvec![0; 8000];
|
||||
self.fade_state = FadeState::Hidden;
|
||||
self.game_rng = RNG::new(0);
|
||||
self.game_rng = XorShift::new(0);
|
||||
self.teleporter_slots.clear();
|
||||
self.quake_counter = 0;
|
||||
self.carets.clear();
|
||||
self.new_npcs.clear();
|
||||
self.textscript_vm.set_mode(ScriptMode::Map);
|
||||
self.textscript_vm.suspend = true;
|
||||
}
|
||||
|
@ -280,7 +277,7 @@ impl SharedGameState {
|
|||
self.screen_size = graphics::drawable_size(ctx);
|
||||
self.scale = self.screen_size.1.div(240.0).floor().max(1.0);
|
||||
self.canvas_size = (self.screen_size.0 / self.scale, self.screen_size.1 / self.scale);
|
||||
unsafe { g_mag = self.scale };
|
||||
unsafe { G_MAG = self.scale };
|
||||
|
||||
graphics::set_screen_coordinates(ctx, graphics::Rect::new(0.0, 0.0, self.screen_size.0, self.screen_size.1))?;
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ impl SoundManager {
|
|||
}
|
||||
|
||||
pub fn play_sfx(&mut self, id: u8) {
|
||||
self.tx.send(PlaybackMessage::PlaySample(id));
|
||||
let _ = self.tx.send(PlaybackMessage::PlaySample(id));
|
||||
}
|
||||
|
||||
pub fn play_song(&mut self, song_id: usize, constants: &EngineConstants, ctx: &mut Context) -> GameResult {
|
||||
|
@ -197,7 +197,7 @@ fn run<T>(rx: Receiver<PlaybackMessage>, bank: SoundBank,
|
|||
org_engine.set_sample_rate(sample_rate as usize);
|
||||
org_engine.loops = usize::MAX;
|
||||
|
||||
let buf_size = sample_rate as usize * 15 / 1000;
|
||||
let buf_size = sample_rate as usize * 10 / 1000;
|
||||
let mut bgm_buf = vec![0x8080; buf_size];
|
||||
let mut pxt_buf = vec![0x8000; buf_size];
|
||||
let mut bgm_index = 0;
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use num_traits::clamp;
|
||||
use vec_mut_scan::VecMutScan;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::sound::pixtone_sfx::PIXTONE_TABLE;
|
||||
use crate::sound::stuff::cubic_interp;
|
||||
|
||||
|
@ -81,29 +80,34 @@ pub struct Envelope {
|
|||
|
||||
impl Envelope {
|
||||
pub fn evaluate(&self, i: i32) -> i32 {
|
||||
let (next_time, next_val) = {
|
||||
if i < self.time_c {
|
||||
(self.time_c, self.value_c)
|
||||
} else if i < self.time_b {
|
||||
(self.time_b, self.value_b)
|
||||
} else if i < self.time_a {
|
||||
(self.time_a, self.value_a)
|
||||
} else {
|
||||
(256, 0)
|
||||
}
|
||||
};
|
||||
let (mut next_time, mut next_val) = (256, 0);
|
||||
let (mut prev_time, mut prev_val) = (0, self.initial);
|
||||
|
||||
let (prev_time, prev_val) = {
|
||||
if i >= self.time_a {
|
||||
(self.time_a, self.value_a)
|
||||
} else if i >= self.time_b {
|
||||
(self.time_b, self.value_b)
|
||||
} else if i >= self.time_c {
|
||||
(self.time_c, self.value_c)
|
||||
} else {
|
||||
(0, self.initial)
|
||||
}
|
||||
};
|
||||
if i < self.time_c {
|
||||
next_time = self.time_c;
|
||||
next_val = self.value_c;
|
||||
}
|
||||
if i < self.time_b {
|
||||
next_time = self.time_b;
|
||||
next_val = self.value_b;
|
||||
}
|
||||
if i < self.time_a {
|
||||
next_time = self.time_a;
|
||||
next_val = self.value_a;
|
||||
}
|
||||
|
||||
if i >= self.time_a {
|
||||
prev_time = self.time_a;
|
||||
prev_val = self.value_a;
|
||||
}
|
||||
if i >= self.time_b {
|
||||
prev_time = self.time_b;
|
||||
prev_val = self.value_b;
|
||||
}
|
||||
if i >= self.time_c {
|
||||
prev_time = self.time_c;
|
||||
prev_val = self.value_c;
|
||||
}
|
||||
|
||||
if next_time <= prev_time {
|
||||
return prev_val;
|
||||
|
@ -180,39 +184,26 @@ impl PixToneParameters {
|
|||
for channel in self.channels.iter() {
|
||||
if !channel.enabled { continue; }
|
||||
|
||||
fn s(p: f32, i: usize, length: u32) -> f32 {
|
||||
256.0 * p * i as f32 / length as f32
|
||||
}
|
||||
|
||||
let mut phase = channel.carrier.offset as f32;
|
||||
let delta = 256.0 * channel.carrier.pitch as f32 / channel.length as f32;
|
||||
let carrier_wave = channel.carrier.get_waveform();
|
||||
let frequency_wave = channel.frequency.get_waveform();
|
||||
let amplitude_wave = channel.amplitude.get_waveform();
|
||||
let mut last_wave = 0;
|
||||
|
||||
for (i, result) in samples.iter_mut().enumerate() {
|
||||
if i >= channel.length as usize {
|
||||
if i == channel.length as usize {
|
||||
last_wave = *result;
|
||||
} else if i == (channel.length as usize + 100) {
|
||||
break;
|
||||
}
|
||||
|
||||
let fac = (i - channel.length as usize) as i16 / 2 + 1;
|
||||
|
||||
last_wave /= fac;
|
||||
*result = last_wave;
|
||||
continue;
|
||||
} else {
|
||||
let carrier = carrier_wave[0xff & phase as usize] as i32 * channel.carrier.level;
|
||||
let freq = frequency_wave[0xff & (channel.frequency.offset as f32 + s(channel.frequency.pitch, i, channel.length)) as usize] as i32 * channel.frequency.level;
|
||||
let amp = amplitude_wave[0xff & (channel.amplitude.offset as f32 + s(channel.amplitude.pitch, i, channel.length)) as usize] as i32 * channel.amplitude.level;
|
||||
|
||||
*result = clamp((*result as i32) + (carrier * (amp + 4096) / 4096 * channel.envelope.evaluate(s(1.0, i, channel.length) as i32) / 4096) * 192, -32767, 32767) as i16;
|
||||
|
||||
phase += delta * (1.0 + (freq as f32 / (if freq < 0 { 8192.0 } else { 2048.0 })));
|
||||
if i == channel.length as usize {
|
||||
break;
|
||||
}
|
||||
|
||||
let s = |p: f32| -> f32 { 256.0 * p * i as f32 / channel.length as f32 };
|
||||
|
||||
let carrier = carrier_wave[0xff & phase as usize] as i32 * channel.carrier.level;
|
||||
let freq = frequency_wave[0xff & (channel.frequency.offset as f32 + s(channel.frequency.pitch)) as usize] as i32 * channel.frequency.level;
|
||||
let amp = amplitude_wave[0xff & (channel.amplitude.offset as f32 + s(channel.amplitude.pitch)) as usize] as i32 * channel.amplitude.level;
|
||||
|
||||
*result = clamp((*result as i32) + (carrier * (amp + 4096) / 4096 * channel.envelope.evaluate(s(1.0) as i32) / 4096) * 256, -32767, 32767) as i16;
|
||||
|
||||
phase += delta * (1.0 + (freq as f32 / (if freq < 0 { 8192.0 } else { 2048.0 })));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ use crate::sound::organya::Song as Organya;
|
|||
use crate::sound::stuff::*;
|
||||
use crate::sound::wav::*;
|
||||
use crate::sound::wave_bank::SoundBank;
|
||||
use num_traits::real::Real;
|
||||
|
||||
pub struct PlaybackEngine {
|
||||
song: Organya,
|
||||
|
@ -73,7 +72,7 @@ impl PlaybackEngine {
|
|||
}
|
||||
}
|
||||
|
||||
for (idx, (track, buf)) in song.tracks[8..].iter().zip(buffers[128..].iter_mut()).enumerate() {
|
||||
for (idx, (_track, buf)) in song.tracks[8..].iter().zip(buffers[128..].iter_mut()).enumerate() {
|
||||
*buf =
|
||||
MaybeUninit::new(
|
||||
RenderBuffer::new(
|
||||
|
@ -145,8 +144,8 @@ impl PlaybackEngine {
|
|||
}
|
||||
}
|
||||
|
||||
for (inst, buf) in song.tracks[8..].iter().zip(self.track_buffers[128..].iter_mut()) {
|
||||
*buf = RenderBuffer::new(samples.samples[inst.inst.inst as usize].clone());
|
||||
for (idx, (_track, buf)) in song.tracks[8..].iter().zip(self.track_buffers[128..].iter_mut()).enumerate() {
|
||||
*buf = RenderBuffer::new(samples.samples[idx].clone());
|
||||
}
|
||||
|
||||
self.song = song;
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
pub const FRQ_TBL: [i16; 12] = [
|
||||
261,278,294,311,329,349,371,391,414,440,466,494
|
||||
262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494
|
||||
];
|
||||
|
||||
pub const PAN_TBL: [i16; 13] = [
|
||||
0,43,86,129,172,215,256,297,340,383,426,469,512
|
||||
0, 43, 86, 129, 172, 215, 256, 297, 340, 383, 426, 469, 512
|
||||
];
|
||||
|
||||
pub const OCT_TBL: [i16; 8] = [
|
||||
32,64,64,128,128,128,128,128
|
||||
32, 64, 64, 128, 128, 128, 128, 128
|
||||
];
|
||||
|
||||
pub fn org_key_to_freq(key: u8, a: i16) -> i32 {
|
||||
let (oct, pitch) = org_key_to_oct_pitch(key);
|
||||
|
||||
let freq = FRQ_TBL[pitch as usize] as f32;
|
||||
let oct = OCT_TBL[oct as usize] as f32;
|
||||
let oct = OCT_TBL[oct as usize] as f32;
|
||||
|
||||
(freq * oct) as i32 + (a as i32 - 1000)
|
||||
}
|
||||
|
||||
pub fn org_key_to_drum_freq(key: u8) -> i32 {
|
||||
pub fn org_key_to_drum_freq(key: u8) -> i32 {
|
||||
key as i32 * 800 + 100
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ pub fn org_vol_to_vol(vol: u8) -> i32 {
|
|||
}
|
||||
|
||||
pub fn org_key_to_oct_pitch(key: u8) -> (u8, u8) {
|
||||
(key/12, key%12)
|
||||
(key / 12, key % 12)
|
||||
}
|
||||
|
||||
// s1: sample 1
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::ops::Not;
|
|||
use std::str::FromStr;
|
||||
|
||||
use byteorder::ReadBytesExt;
|
||||
use ggez::{Context, GameError, GameResult};
|
||||
use ggez::{Context, GameResult};
|
||||
use ggez::GameError::{InvalidValue, ParseError};
|
||||
use itertools::Itertools;
|
||||
use num_derive::FromPrimitive;
|
||||
|
@ -21,9 +21,8 @@ use crate::encoding::{read_cur_shift_jis, read_cur_wtf8};
|
|||
use crate::engine_constants::EngineConstants;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::UpdateTarget;
|
||||
use crate::npc::NPCMap;
|
||||
use crate::npc::NPC;
|
||||
use crate::player::{ControlMode, TargetPlayer};
|
||||
use crate::profile::GameProfile;
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
@ -668,10 +667,11 @@ impl TextScriptVM {
|
|||
|
||||
pub fn execute(event: u16, ip: u32, state: &mut SharedGameState, game_scene: &mut GameScene, ctx: &mut Context) -> GameResult<TextScriptExecutionState> {
|
||||
let mut exec_state = state.textscript_vm.state;
|
||||
let mut tick_npc = 0u16;
|
||||
let mut npc_remove_type = 0u16;
|
||||
|
||||
if let Some(bytecode) = state.textscript_vm.scripts.find_script(state.textscript_vm.mode, event) {
|
||||
let state_ref = state as *mut SharedGameState;
|
||||
let scripts = unsafe { &(*state_ref).textscript_vm.scripts };
|
||||
|
||||
if let Some(bytecode) = scripts.find_script(state.textscript_vm.mode, event) {
|
||||
let mut cursor = Cursor::new(bytecode);
|
||||
cursor.seek(SeekFrom::Start(ip as u64))?;
|
||||
|
||||
|
@ -890,7 +890,7 @@ impl TextScriptVM {
|
|||
let npc_type = read_cur_varint(&mut cursor)? as u16;
|
||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
if game_scene.npc_map.is_alive_by_type(npc_type) {
|
||||
if game_scene.npc_list.is_alive_by_type(npc_type) {
|
||||
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
||||
} else {
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
@ -900,7 +900,7 @@ impl TextScriptVM {
|
|||
let npc_event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
if game_scene.npc_map.is_alive_by_event(npc_event_num) {
|
||||
if game_scene.npc_list.is_alive_by_event(npc_event_num) {
|
||||
exec_state = TextScriptExecutionState::Running(event_num, 0);
|
||||
} else {
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
@ -947,13 +947,13 @@ impl TextScriptVM {
|
|||
let tile_type = read_cur_varint(&mut cursor)? as u8;
|
||||
|
||||
if game_scene.stage.change_tile(pos_x, pos_y, tile_type) {
|
||||
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
||||
let mut npc = NPC::create(4, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = pos_x as isize * 16 * 0x200;
|
||||
npc.y = pos_y as isize * 16 * 0x200;
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
state.new_npcs.push(npc);
|
||||
game_scene.npc_list.spawn(0x100, npc.clone())?;
|
||||
game_scene.npc_list.spawn(0x100, npc)?;
|
||||
}
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
|
@ -1164,12 +1164,14 @@ impl TextScriptVM {
|
|||
OpCode::DNP => {
|
||||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
game_scene.npc_map.remove_by_event(event_num, &mut state.game_flags);
|
||||
game_scene.npc_list.remove_by_event(event_num, state);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
OpCode::DNA => {
|
||||
npc_remove_type = read_cur_varint(&mut cursor)? as u16;
|
||||
let npc_remove_type = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
game_scene.npc_list.remove_by_type(npc_remove_type, state);
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -1194,9 +1196,7 @@ impl TextScriptVM {
|
|||
let ticks = read_cur_varint(&mut cursor)? as isize;
|
||||
game_scene.frame.wait = ticks;
|
||||
|
||||
for npc_cell in game_scene.npc_map.npcs.values() {
|
||||
let npc = npc_cell.borrow();
|
||||
|
||||
for npc in game_scene.npc_list.iter() {
|
||||
if event_num == npc.event_num {
|
||||
game_scene.frame.update_target = UpdateTarget::NPC(npc.id);
|
||||
break;
|
||||
|
@ -1209,13 +1209,11 @@ impl TextScriptVM {
|
|||
let event_num = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
if event_num == 0 {
|
||||
game_scene.boss_life_bar.set_boss_target(&game_scene.npc_map);
|
||||
game_scene.boss_life_bar.set_boss_target(&game_scene.boss);
|
||||
} else {
|
||||
for npc_cell in game_scene.npc_map.npcs.values() {
|
||||
let npc = npc_cell.borrow();
|
||||
|
||||
if npc.cond.alive() && event_num == npc.event_num {
|
||||
game_scene.boss_life_bar.set_npc_target(npc.id, &game_scene.npc_map);
|
||||
for npc in game_scene.npc_list.iter_alive() {
|
||||
if event_num == npc.event_num {
|
||||
game_scene.boss_life_bar.set_npc_target(npc.id, &game_scene.npc_list);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1226,7 +1224,7 @@ impl TextScriptVM {
|
|||
OpCode::BOA => {
|
||||
let action_num = read_cur_varint(&mut cursor)? as u16;
|
||||
|
||||
game_scene.npc_map.boss_map.parts[0].action_num = action_num;
|
||||
game_scene.boss.parts[0].action_num = action_num;
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -1236,10 +1234,8 @@ impl TextScriptVM {
|
|||
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
||||
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
||||
|
||||
for npc_cell in game_scene.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
if npc.cond.alive() && npc.event_num == event_num {
|
||||
for npc in game_scene.npc_list.iter_alive() {
|
||||
if npc.event_num == event_num {
|
||||
npc.action_num = action_num;
|
||||
npc.tsc_direction = tsc_direction as u16;
|
||||
|
||||
|
@ -1263,10 +1259,8 @@ impl TextScriptVM {
|
|||
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
||||
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
||||
|
||||
for npc_cell in game_scene.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
if npc.cond.alive() && npc.event_num == event_num {
|
||||
for npc in game_scene.npc_list.iter_alive() {
|
||||
if npc.event_num == event_num {
|
||||
npc.npc_flags.set_solid_soft(false);
|
||||
npc.npc_flags.set_ignore_tile_44(false);
|
||||
npc.npc_flags.set_invulnerable(false);
|
||||
|
@ -1310,7 +1304,7 @@ impl TextScriptVM {
|
|||
npc.direction = direction;
|
||||
}
|
||||
|
||||
tick_npc = npc.id;
|
||||
npc.tick(state, ([&mut game_scene.player1, &mut game_scene.player2], &game_scene.npc_list, &mut game_scene.stage, &game_scene.bullet_manager))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1323,10 +1317,8 @@ impl TextScriptVM {
|
|||
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
||||
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
||||
|
||||
for npc_cell in game_scene.npc_map.npcs.values() {
|
||||
let mut npc = npc_cell.borrow_mut();
|
||||
|
||||
if npc.cond.alive() && npc.event_num == event_num {
|
||||
for npc in game_scene.npc_list.iter_alive() {
|
||||
if npc.event_num == event_num {
|
||||
npc.x = x * 16 * 0x200;
|
||||
npc.y = y * 16 * 0x200;
|
||||
npc.tsc_direction = tsc_direction as u16;
|
||||
|
@ -1354,7 +1346,7 @@ impl TextScriptVM {
|
|||
let tsc_direction = read_cur_varint(&mut cursor)? as usize;
|
||||
let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left);
|
||||
|
||||
let mut npc = NPCMap::create_npc(npc_type, &state.npc_table);
|
||||
let mut npc = NPC::create(npc_type, &state.npc_table);
|
||||
npc.cond.set_alive(true);
|
||||
npc.x = x * 16 * 0x200;
|
||||
npc.y = y * 16 * 0x200;
|
||||
|
@ -1370,7 +1362,7 @@ impl TextScriptVM {
|
|||
npc.direction = direction;
|
||||
}
|
||||
|
||||
state.new_npcs.push(npc);
|
||||
game_scene.npc_list.spawn(0x100, npc)?;
|
||||
|
||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||
}
|
||||
|
@ -1534,16 +1526,6 @@ impl TextScriptVM {
|
|||
return Ok(TextScriptExecutionState::Ended);
|
||||
}
|
||||
|
||||
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, &mut game_scene.player2], &game_scene.npc_map.npcs, &mut game_scene.stage, &game_scene.bullet_manager))?;
|
||||
}
|
||||
}
|
||||
|
||||
if npc_remove_type != 0 {
|
||||
game_scene.npc_map.remove_by_type(npc_remove_type, state);
|
||||
}
|
||||
|
||||
Ok(exec_state)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use crate::settings::Settings;
|
|||
use crate::shared_game_state::Season;
|
||||
use crate::str;
|
||||
|
||||
pub static mut g_mag: f32 = 1.0;
|
||||
pub static mut G_MAG: f32 = 1.0;
|
||||
|
||||
pub struct SizedBatch {
|
||||
pub batch: SpriteBatch,
|
||||
|
@ -90,8 +90,8 @@ impl SizedBatch {
|
|||
}
|
||||
|
||||
unsafe {
|
||||
x = (x * g_mag).floor() / g_mag;
|
||||
y = (y * g_mag).floor() / g_mag;
|
||||
x = (x * G_MAG).floor() / G_MAG;
|
||||
y = (y * G_MAG).floor() / G_MAG;
|
||||
}
|
||||
|
||||
let param = DrawParam::new()
|
||||
|
|
Loading…
Reference in a new issue