diff --git a/src/components/boss_life_bar.rs b/src/components/boss_life_bar.rs index 3b48a5f..9a15ba7 100644 --- a/src/components/boss_life_bar.rs +++ b/src/components/boss_life_bar.rs @@ -5,7 +5,7 @@ use crate::framework::error::GameResult; use crate::game::frame::Frame; use crate::game::shared_game_state::SharedGameState; use crate::game::npc::boss::BossNPC; -use crate::game::npc::list::NPCList; +use crate::game::npc::list::{NPCAccessToken, NPCList}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] @@ -28,8 +28,10 @@ impl BossLifeBar { BossLifeBar { target: BossLifeTarget::None, life: 0, max_life: 0, prev_life: 0, counter: 0 } } - pub fn set_npc_target(&mut self, npc_id: u16, npc_list: &NPCList) { + pub fn set_npc_target(&mut self, npc_id: u16, npc_list: &NPCList, npc_token: &NPCAccessToken) { if let Some(npc) = npc_list.get_npc(npc_id as usize) { + let npc = npc.borrow(npc_token); + self.target = BossLifeTarget::NPC(npc.id); self.life = npc.life; self.max_life = self.life; @@ -130,11 +132,13 @@ impl BossLifeBar { } } -impl GameEntity<(&NPCList, &BossNPC)> for BossLifeBar { - fn tick(&mut self, _state: &mut SharedGameState, (npc_list, boss): (&NPCList, &BossNPC)) -> GameResult<()> { +impl GameEntity<(&NPCList, &NPCAccessToken, &BossNPC)> for BossLifeBar { + fn tick(&mut self, _state: &mut SharedGameState, (npc_list, npc_token, boss): (&NPCList, &NPCAccessToken, &BossNPC)) -> GameResult<()> { match self.target { BossLifeTarget::NPC(npc_id) => { if let Some(npc) = npc_list.get_npc(npc_id as usize) { + let npc = npc.borrow(npc_token); + self.life = npc.life; } } diff --git a/src/components/water_renderer.rs b/src/components/water_renderer.rs index be2da69..0e1aff5 100644 --- a/src/components/water_renderer.rs +++ b/src/components/water_renderer.rs @@ -11,7 +11,7 @@ use crate::game::map::{WaterParamEntry, WaterParams, WaterRegionType}; use crate::game::physics::PhysicalEntity; use crate::game::shared_game_state::SharedGameState; use crate::game::stage::{BackgroundType, Stage}; -use crate::game::npc::list::NPCList; +use crate::game::npc::list::{NPCAccessToken, NPCList}; use crate::game::player::Player; const TENSION: f32 = 0.03; @@ -100,7 +100,7 @@ impl DynamicWater { } } - pub fn interact(&mut self, players: &[&Player], npc_list: &NPCList) { + pub fn interact(&mut self, players: &[&Player], npc_list: &NPCList, npc_token: &NPCAccessToken) { let cols_i32 = self.columns.len() as i32; let mut tick_object = |obj: &dyn PhysicalEntity| { @@ -129,13 +129,13 @@ impl DynamicWater { tick_object(*player); } - for npc in npc_list.iter_alive() { + for npc in npc_list.iter_alive(npc_token) { static NO_COLL_NPCS: [u16; 6] = [0, 3, 4, 18, 191, 195]; if NO_COLL_NPCS.contains(&npc.npc_type) { continue; } - tick_object(npc); + tick_object(&*npc); } } } @@ -211,9 +211,9 @@ impl WaterRenderer { } } - pub fn tick(&mut self, state: &mut SharedGameState, (players, npc_list): (&[&Player], &NPCList)) -> GameResult<()> { + pub fn tick(&mut self, state: &mut SharedGameState, (players, npc_list, npc_token): (&[&Player], &NPCList, &NPCAccessToken)) -> GameResult<()> { for surf in &mut self.water_surfaces { - surf.interact(players, npc_list); + surf.interact(players, npc_list, npc_token); surf.tick(); } @@ -222,7 +222,7 @@ impl WaterRenderer { core_water.y = level; core_depth.rect.top = (level + 16.0).min(core_depth.rect.bottom); - core_water.interact(players, npc_list); + core_water.interact(players, npc_list, npc_token); core_water.tick(); } diff --git a/src/framework/error.rs b/src/framework/error.rs index 1a41e89..42e46c8 100644 --- a/src/framework/error.rs +++ b/src/framework/error.rs @@ -2,6 +2,7 @@ use std::error::Error; use std::fmt; +use std::ops::ControlFlow; use std::string::FromUtf8Error; use std::sync::mpsc::SendError; use std::sync::{Arc, PoisonError}; @@ -74,6 +75,14 @@ impl Error for GameError { /// A convenient result type consisting of a return type and a `GameError` pub type GameResult = Result; +/// Convert Result::Err(e) to ControlFlow::Break(e). Useful in try_for_each methods. +pub fn map_err_to_break(result: Result) -> ControlFlow { + match result { + Result::Ok(t) => ControlFlow::Continue(t), + Result::Err(e) => ControlFlow::Break(e) + } +} + impl From for GameError { fn from(e: std::io::Error) -> GameError { GameError::IOError(Arc::new(e)) diff --git a/src/game/npc/ai/balcony.rs b/src/game/npc/ai/balcony.rs index f1c0a9b..3ca830e 100644 --- a/src/game/npc/ai/balcony.rs +++ b/src/game/npc/ai/balcony.rs @@ -101,7 +101,7 @@ impl NPC { } } - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { if parent.action_num >= 20 { self.action_num = 10; } @@ -118,7 +118,7 @@ impl NPC { _ => (), } - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { if self.direction == Direction::Left { self.x = parent.x + 0x2400; self.y = parent.y - 0x7200; diff --git a/src/game/npc/ai/balrog.rs b/src/game/npc/ai/balrog.rs index 770630d..6905899 100644 --- a/src/game/npc/ai/balrog.rs +++ b/src/game/npc/ai/balrog.rs @@ -3,11 +3,12 @@ use num_traits::clamp; use crate::common::{Direction, CDEG_RAD}; use crate::framework::error::GameResult; use crate::game::caret::CaretType; +use crate::game::npc::list::BorrowedNPC; use crate::game::npc::{NPCContext, NPC}; use crate::game::shared_game_state::SharedGameState; use crate::util::rng::RNG; -impl NPC { +impl BorrowedNPC<'_> { pub(crate) fn tick_n009_balrog_falling_in( &mut self, state: &mut SharedGameState, @@ -446,8 +447,8 @@ impl NPC { self.vel_y = -0x800; self.npc_flags.set_ignore_solidity(true); - npc_list.kill_npcs_by_type(150, false, state); - npc_list.kill_npcs_by_type(117, false, state); + npc_list.kill_npcs_by_type(150, false, state, self); + npc_list.kill_npcs_by_type(117, false, state, self); let mut npc = NPC::create(355, &state.npc_table); npc.cond.set_alive(true); diff --git a/src/game/npc/ai/characters.rs b/src/game/npc/ai/characters.rs index fc987be..1284f33 100644 --- a/src/game/npc/ai/characters.rs +++ b/src/game/npc/ai/characters.rs @@ -371,7 +371,7 @@ impl NPC { NPCContext { npc_list, .. }: NPCContext, ) -> GameResult { if self.action_num == 0 { - let parent = self.get_parent_ref_mut(npc_list); + let parent = self.get_parent(npc_list); if let Some(parent) = parent { if parent.action_counter2 != 0 { if parent.direction != Direction::Left { @@ -785,7 +785,7 @@ impl NPC { self.y -= 0x400; } - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { if parent.anim_num == 7 { self.action_num = 1; self.anim_num = 1; diff --git a/src/game/npc/ai/curly.rs b/src/game/npc/ai/curly.rs index 371b9b6..dc0e744 100644 --- a/src/game/npc/ai/curly.rs +++ b/src/game/npc/ai/curly.rs @@ -562,7 +562,7 @@ impl NPC { state: &mut SharedGameState, NPCContext { npc_list, bullet_manager, .. }: NPCContext, ) -> GameResult { - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(mut parent) = self.get_parent_mut(npc_list) { if parent.anim_num > 4 { self.direction = parent.direction; self.x = parent.x; @@ -655,7 +655,7 @@ impl NPC { state: &mut SharedGameState, NPCContext { npc_list, bullet_manager, .. }: NPCContext, ) -> GameResult { - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(mut parent) = self.get_parent_mut(npc_list) { if parent.anim_num > 4 { self.direction = parent.direction; self.x = parent.x; @@ -747,7 +747,7 @@ impl NPC { state: &mut SharedGameState, NPCContext { npc_list, .. }: NPCContext, ) -> GameResult { - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { if self.action_num == 0 { self.x = parent.x; self.y = parent.y; @@ -824,7 +824,7 @@ impl NPC { state: &mut SharedGameState, NPCContext { npc_list, .. }: NPCContext, ) -> GameResult { - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { self.x = parent.x; self.y = parent.y; self.direction = parent.direction; @@ -901,7 +901,7 @@ impl NPC { state: &mut SharedGameState, NPCContext { players, npc_list, bullet_manager, .. }: NPCContext, ) -> GameResult { - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { let player = &players[0]; self.x = npc.x; diff --git a/src/game/npc/ai/doctor.rs b/src/game/npc/ai/doctor.rs index 41ffabc..39c8627 100644 --- a/src/game/npc/ai/doctor.rs +++ b/src/game/npc/ai/doctor.rs @@ -1,10 +1,11 @@ use crate::common::{Direction, Rect, CDEG_RAD}; use crate::framework::error::GameResult; +use crate::game::npc::list::BorrowedNPC; use crate::game::npc::{NPCContext, NPC}; use crate::game::shared_game_state::SharedGameState; use crate::util::rng::RNG; -impl NPC { +impl BorrowedNPC<'_> { pub(crate) fn tick_n139_doctor(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult { match self.action_num { 0 | 1 => { @@ -920,7 +921,7 @@ impl NPC { } } 500 => { - npc_list.kill_npcs_by_type(269, true, state); + npc_list.kill_npcs_by_type(269, true, state, self); self.npc_flags.set_shootable(false); self.anim_num = 4; @@ -1092,7 +1093,7 @@ impl NPC { self.action_counter3 = self.rng.range(0x80..0x100) as u16; } - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { if self.x < parent.x { self.vel_x += 0x200 / self.action_counter2 as i32; } @@ -1167,7 +1168,7 @@ impl NPC { self.action_counter += 1; if self.action_counter > 250 { self.action_num = 22; - npc_list.kill_npcs_by_type(270, false, state); + npc_list.kill_npcs_by_type(270, false, state, self); } } _ => (), diff --git a/src/game/npc/ai/hell.rs b/src/game/npc/ai/hell.rs index 7126649..d5945b0 100644 --- a/src/game/npc/ai/hell.rs +++ b/src/game/npc/ai/hell.rs @@ -698,7 +698,7 @@ impl NPC { ) -> GameResult { match self.action_num { 0 => { - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { self.y = parent.y + 0x1400; self.x = parent.x + 0xE00 * parent.direction.opposite().vector_x(); diff --git a/src/game/npc/ai/maze.rs b/src/game/npc/ai/maze.rs index 8ae2d61..bb04314 100644 --- a/src/game/npc/ai/maze.rs +++ b/src/game/npc/ai/maze.rs @@ -1,11 +1,12 @@ use crate::common::{Direction, CDEG_RAD}; use crate::framework::error::GameResult; use crate::game::caret::CaretType; +use crate::game::npc::list::BorrowedNPC; use crate::game::npc::{NPCContext, NPC}; use crate::game::shared_game_state::SharedGameState; use crate::util::rng::RNG; -impl NPC { +impl BorrowedNPC<'_> { pub(crate) fn tick_n147_critter_purple( &mut self, state: &mut SharedGameState, @@ -539,7 +540,7 @@ impl NPC { 2 => { self.vel_y = 0xA00; if self.flags.hit_bottom_wall() { - npc_list.kill_npcs_by_type(161, true, state); + npc_list.kill_npcs_by_type(161, true, state, self); let mut npc = NPC::create(4, &state.npc_table); npc.cond.set_alive(true); @@ -706,7 +707,7 @@ impl NPC { match self.action_num { 0 | 1 => { if self.action_num == 0 { - npc_list.kill_npcs_by_type(161, true, state); + npc_list.kill_npcs_by_type(161, true, state, self); state.sound_manager.play_sfx(72); let mut npc = NPC::create(4, &state.npc_table); @@ -778,7 +779,7 @@ impl NPC { 3 => { self.action_counter3 += 1; if self.action_counter3 > 59 { - npc_list.kill_npcs_by_type(161, true, state); + npc_list.kill_npcs_by_type(161, true, state, self); self.cond.set_alive(false); } } @@ -1569,7 +1570,7 @@ impl NPC { let player = self.get_closest_player_mut(players); if self.action_num == 1 { - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { if parent.npc_type == 187 && parent.cond.alive() { let deg = (self.action_counter3.wrapping_add(parent.action_counter3) & 0xff) as f64 * CDEG_RAD; diff --git a/src/game/npc/ai/mimiga_village.rs b/src/game/npc/ai/mimiga_village.rs index 9b2b75d..d574faa 100644 --- a/src/game/npc/ai/mimiga_village.rs +++ b/src/game/npc/ai/mimiga_village.rs @@ -4,12 +4,13 @@ use num_traits::{abs, clamp}; use crate::common::Direction; use crate::framework::error::GameResult; +use crate::game::npc::list::BorrowedNPC; use crate::game::npc::{NPCContext, NPC}; use crate::game::player::{TargetPlayer}; use crate::game::shared_game_state::SharedGameState; use crate::util::rng::RNG; -impl NPC { +impl BorrowedNPC<'_> { pub(crate) fn tick_n069_pignon(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult { match self.action_num { 0 | 1 => { @@ -677,7 +678,7 @@ impl NPC { self.anim_num = 8; self.target_x = self.x; self.damage = 0; - npc_list.kill_npcs_by_type(315, true, state); + npc_list.kill_npcs_by_type(315, true, state, self); } self.vel_y += 0x20; diff --git a/src/game/npc/ai/misc.rs b/src/game/npc/ai/misc.rs index a9112b3..bca4e8e 100644 --- a/src/game/npc/ai/misc.rs +++ b/src/game/npc/ai/misc.rs @@ -3,12 +3,13 @@ use std::hint::unreachable_unchecked; use crate::common::{Direction, Rect}; use crate::framework::error::GameResult; use crate::game::caret::CaretType; +use crate::game::npc::list::{BorrowedNPC, NPCTokenProvider}; use crate::game::npc::{NPCContext, NPCLayer, NPC}; use crate::game::physics::HitExtents; use crate::game::shared_game_state::{GameDifficulty, SharedGameState}; use crate::util::rng::RNG; -impl NPC { +impl BorrowedNPC<'_> { pub(crate) fn tick_n000_null(&mut self, _: &mut SharedGameState, _: NPCContext) -> GameResult { if self.action_num == 0 { self.action_num = 1; @@ -2377,7 +2378,7 @@ impl NPC { state: &mut SharedGameState, NPCContext { npc_list, .. }: NPCContext, ) -> GameResult { - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { self.x = npc.x + 0x2000; self.y = npc.y + 0x1000; } @@ -2419,11 +2420,19 @@ impl NPC { self.action_num = 101; if self.tsc_direction != 0 { - for npc in npc_list.iter_alive() { - if npc.event_num == self.tsc_direction { - self.parent_id = npc.id; - break; + let self_tsc_direction = self.tsc_direction; + let npc_id = self.unborrow_then(|token| { + for npc in npc_list.iter_alive(token) { + if npc.event_num == self_tsc_direction { + return Some(npc.id); + } } + + None + }); + + if let Some(npc_id) = npc_id { + self.parent_id = npc_id; } if self.parent_id == 0 { @@ -2438,7 +2447,7 @@ impl NPC { if self.tsc_direction == 0 { self.x = (player.x + boss.parts[0].x) / 2; self.y = (player.y + boss.parts[0].y) / 2; - } else if let Some(npc) = self.get_parent_ref_mut(npc_list) { + } else if let Some(npc) = self.get_parent(npc_list) { self.x = (player.x + npc.x) / 2; self.y = (player.y + npc.y) / 2; } @@ -2632,7 +2641,7 @@ impl NPC { self.spritesheet_id = 16; self.anim_num = 0; - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { self.x = npc.x; self.y = npc.y + 0x1400; } @@ -2641,7 +2650,7 @@ impl NPC { self.spritesheet_id = 16; self.anim_num = 2; - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { self.x = npc.x + 0x1600; self.y = npc.y - 0x2200; } @@ -2651,7 +2660,7 @@ impl NPC { self.spritesheet_id = 23; self.anim_num = 3; - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { self.x = npc.x + 0x400; self.y = npc.y - 0x2600; } @@ -2661,7 +2670,7 @@ impl NPC { self.spritesheet_id = 16; self.anim_num = 0; - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { self.x = npc.x - 0x1c00; self.y = npc.y + 0x1400; } @@ -2670,7 +2679,7 @@ impl NPC { self.spritesheet_id = 23; self.anim_num = 1; - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { self.x = npc.x + 0x1c00; self.y = npc.y + 0x1400; } @@ -2679,7 +2688,7 @@ impl NPC { self.spritesheet_id = 16; self.anim_num = 2; - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { self.x = npc.x - 0xe00; if state.constants.is_switch { self.y = npc.y - 0x2200; @@ -2692,7 +2701,7 @@ impl NPC { self.spritesheet_id = 23; self.anim_num = 3; - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { self.x = npc.x + 0x800; self.y = npc.y - 0x2600; } diff --git a/src/game/npc/ai/misery.rs b/src/game/npc/ai/misery.rs index 05a3b96..77524fd 100644 --- a/src/game/npc/ai/misery.rs +++ b/src/game/npc/ai/misery.rs @@ -5,11 +5,12 @@ use num_traits::clamp; use crate::common::{Direction, CDEG_RAD}; use crate::framework::error::GameResult; use crate::game::caret::CaretType; +use crate::game::npc::list::{BorrowedNPC, NPCTokenProvider}; use crate::game::npc::{NPCContext, NPC}; use crate::game::shared_game_state::SharedGameState; use crate::util::rng::RNG; -impl NPC { +impl BorrowedNPC<'_> { pub(crate) fn tick_n066_misery_bubble( &mut self, state: &mut SharedGameState, @@ -18,7 +19,13 @@ impl NPC { match self.action_num { 0 | 1 => { if self.action_num == 0 { - if let Some(npc) = npc_list.iter().find(|npc| npc.event_num == 1000) { + let npc = self.unborrow_then(|token| { + npc_list.iter().find(|npc| npc.borrow(token).event_num == 1000) + }); + + if let Some(npc) = npc { + let npc = npc.borrow_unmanaged(); + self.action_counter2 = npc.id; self.target_x = npc.x; self.target_y = npc.y; @@ -44,6 +51,7 @@ impl NPC { state.sound_manager.play_sfx(21); if let Some(npc) = npc_list.get_npc(self.action_counter2 as usize) { + let mut npc = npc.borrow_mut_unmanaged(); npc.cond.set_alive(false); } } @@ -661,7 +669,7 @@ impl NPC { self.vel_x = 0; self.vel_y = 0; - npc_list.kill_npcs_by_type(252, true, state); + npc_list.kill_npcs_by_type(252, true, state, self); let mut npc = NPC::create(4, &state.npc_table); npc.cond.set_alive(true); @@ -844,7 +852,7 @@ impl NPC { self.action_counter += 1; } - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { self.x = parent.x + self.action_counter as i32 * ((self.action_counter2 as f64 * CDEG_RAD).cos() * 512.0) as i32 / 4; diff --git a/src/game/npc/ai/plantation.rs b/src/game/npc/ai/plantation.rs index eab13e5..368d485 100644 --- a/src/game/npc/ai/plantation.rs +++ b/src/game/npc/ai/plantation.rs @@ -560,7 +560,7 @@ impl NPC { self.vel_y2 = self.rng.range(-32..32) * 0x200; } - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { if parent.npc_type == 232 { self.target_x = parent.x; self.target_y = parent.y; diff --git a/src/game/npc/ai/quote.rs b/src/game/npc/ai/quote.rs index f82b292..d5cfb29 100644 --- a/src/game/npc/ai/quote.rs +++ b/src/game/npc/ai/quote.rs @@ -281,7 +281,7 @@ impl NPC { // Curly Clone Grabbed Player (Switch) 200 => { self.anim_num = 2; - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { self.x = parent.x; self.vel_x = parent.vel_x; self.y = parent.y; @@ -454,7 +454,7 @@ impl NPC { } 200 => { self.anim_num = 9; - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { self.x = parent.x + parent.vel_x + 0xA00; self.y = parent.y + parent.vel_y - 0x1C00; } diff --git a/src/game/npc/ai/sand_zone.rs b/src/game/npc/ai/sand_zone.rs index f17351d..46a2102 100644 --- a/src/game/npc/ai/sand_zone.rs +++ b/src/game/npc/ai/sand_zone.rs @@ -298,7 +298,7 @@ impl NPC { state: &mut SharedGameState, NPCContext { players, npc_list, .. }: NPCContext, ) -> GameResult { - let parent = self.get_parent_ref_mut(npc_list); + let parent = self.get_parent_mut(npc_list); if self.action_num > 9 && parent.as_ref().map(|n| n.npc_type == 3).unwrap_or(false) { self.action_num = 3; @@ -390,7 +390,7 @@ impl NPC { } if self.action_num > 9 { - if let Some(parent) = parent { + if let Some(mut parent) = parent { self.x = parent.x; self.y = parent.y + 0x2000; self.direction = parent.direction; @@ -573,14 +573,14 @@ impl NPC { state: &mut SharedGameState, NPCContext { npc_list, .. }: NPCContext, ) -> GameResult { - let parent = self.get_parent_ref_mut(npc_list); + let parent = self.get_parent_mut(npc_list); if parent.is_none() || parent.as_ref().unwrap().npc_type == 3 { self.vanish(state); npc_list.create_death_smoke(self.x, self.y, self.display_bounds.right as usize, 4, state, &self.rng); return Ok(()); } - let parent = parent.unwrap(); + let mut parent = parent.unwrap(); let angle = (self.vel_x + parent.vel_y2) & 0xFF; diff --git a/src/game/npc/ai/sue.rs b/src/game/npc/ai/sue.rs index 8169c76..ba5b1c4 100644 --- a/src/game/npc/ai/sue.rs +++ b/src/game/npc/ai/sue.rs @@ -1,10 +1,11 @@ use crate::common::Direction; use crate::framework::error::GameResult; +use crate::game::npc::list::{BorrowedNPC, NPCTokenProvider}; use crate::game::npc::{NPCContext, NPC}; use crate::game::shared_game_state::SharedGameState; use crate::util::rng::RNG; -impl NPC { +impl BorrowedNPC<'_> { pub fn tick_n042_sue( &mut self, state: &mut SharedGameState, @@ -112,13 +113,15 @@ impl NPC { self.vel_y = 0; self.action_num = 14; - self.parent_id = npc_list - .iter_alive() - .find_map(|npc| if npc.event_num == 501 { Some(npc.id) } else { None }) - .unwrap_or(0); + self.parent_id = self.unborrow_then(|token| { + npc_list + .iter_alive(token) + .find_map(|npc| if npc.event_num == 501 { Some(npc.id) } else { None }) + .unwrap_or(0) + }); } - if let Some(npc) = self.get_parent_ref_mut(npc_list) { + if let Some(npc) = self.get_parent(npc_list) { self.direction = npc.direction.opposite(); self.x = npc.x + npc.direction.vector_x() * 0xc00; self.y = npc.y + 0x800; @@ -370,7 +373,7 @@ impl NPC { self.display_bounds.right = 0x2000; self.display_bounds.left = 0x2000; - npc_list.kill_npcs_by_type(257, true, state); + npc_list.kill_npcs_by_type(257, true, state, self); } 20 | 21 => { if self.action_num == 20 { diff --git a/src/game/npc/ai/toroko.rs b/src/game/npc/ai/toroko.rs index 98917bf..02c3f49 100644 --- a/src/game/npc/ai/toroko.rs +++ b/src/game/npc/ai/toroko.rs @@ -594,7 +594,7 @@ impl NPC { self.action_num = 1; self.action_counter = 0; } - let parent = self.get_parent_ref_mut(npc_list); + let parent = self.get_parent(npc_list); if let Some(parent) = parent { let player = self.get_closest_player_mut(players); diff --git a/src/game/npc/ai/wind_fortress.rs b/src/game/npc/ai/wind_fortress.rs index bb9cdd8..6e372b2 100644 --- a/src/game/npc/ai/wind_fortress.rs +++ b/src/game/npc/ai/wind_fortress.rs @@ -1,11 +1,12 @@ use crate::common::{Direction, CDEG_RAD}; use crate::framework::error::GameResult; use crate::game::caret::CaretType; +use crate::game::npc::list::BorrowedNPC; use crate::game::npc::{NPCContext, NPC}; use crate::game::shared_game_state::SharedGameState; use crate::util::rng::RNG; -impl NPC { +impl BorrowedNPC<'_> { // Gaudi from room 2 pub(crate) fn tick_n361_flying_gaudi( &mut self, @@ -597,7 +598,7 @@ impl NPC { state.sound_manager.play_sfx(52); } - npc_list.kill_npcs_by_type(369, true, state); + npc_list.kill_npcs_by_type(369, true, state, self); npc_list.create_death_smoke( self.x + (self.rng.range(-32..32) << 9) as i32, diff --git a/src/game/npc/boss/ballos.rs b/src/game/npc/boss/ballos.rs index e7dc885..900786c 100644 --- a/src/game/npc/boss/ballos.rs +++ b/src/game/npc/boss/ballos.rs @@ -597,7 +597,7 @@ impl NPC { state: &mut SharedGameState, NPCContext { npc_list, .. }: NPCContext, ) -> GameResult { - if let Some(parent) = self.get_parent_ref_mut(npc_list) { + if let Some(parent) = self.get_parent(npc_list) { if parent.action_num == 11 && parent.action_counter > 50 { self.anim_counter += 1; } @@ -1424,7 +1424,7 @@ impl BossNPC { pub(crate) fn tick_b09_ballos( &mut self, state: &mut SharedGameState, - BossNPCContext { players, npc_list, flash, .. }: BossNPCContext, + BossNPCContext { players, npc_list, npc_token, flash, .. }: BossNPCContext, ) { let player = self.parts[0].get_closest_player_mut(players); @@ -1770,7 +1770,7 @@ impl BossNPC { self.parts[0].action_counter = 0; self.parts[0].vel_x = 0; self.parts[0].vel_y = 0; - npc_list.kill_npcs_by_type(339, false, state); + npc_list.kill_npcs_by_type(339, false, state, npc_token); } self.parts[0].y += (0x13E00 - self.parts[0].y) / 8; @@ -1920,8 +1920,8 @@ impl BossNPC { self.parts[4].cond.set_alive(false); self.parts[5].cond.set_alive(false); - npc_list.kill_npcs_by_type(350, true, state); - npc_list.kill_npcs_by_type(348, true, state); + npc_list.kill_npcs_by_type(350, true, state, npc_token); + npc_list.kill_npcs_by_type(348, true, state, npc_token); } } _ => (), diff --git a/src/game/npc/boss/heavy_press.rs b/src/game/npc/boss/heavy_press.rs index 286a3a9..533cddc 100644 --- a/src/game/npc/boss/heavy_press.rs +++ b/src/game/npc/boss/heavy_press.rs @@ -56,7 +56,7 @@ impl BossNPC { pub(crate) fn tick_b08_heavy_press( &mut self, state: &mut SharedGameState, - BossNPCContext { npc_list, stage, .. }: BossNPCContext, + BossNPCContext { npc_list, npc_token, stage, .. }: BossNPCContext, ) { match self.parts[0].action_num { 0 => { @@ -216,8 +216,8 @@ impl BossNPC { self.parts[0].action_counter = 0; self.parts[0].action_counter2 = 0; - npc_list.kill_npcs_by_type(325, true, state); - npc_list.kill_npcs_by_type(330, true, state); + npc_list.kill_npcs_by_type(325, true, state, npc_token); + npc_list.kill_npcs_by_type(330, true, state, npc_token); } self.parts[0].action_counter += 1; diff --git a/src/game/npc/boss/ironhead.rs b/src/game/npc/boss/ironhead.rs index 9d4b748..0ea21fb 100644 --- a/src/game/npc/boss/ironhead.rs +++ b/src/game/npc/boss/ironhead.rs @@ -177,7 +177,7 @@ impl BossNPC { pub(crate) fn tick_b05_ironhead( &mut self, state: &mut SharedGameState, - BossNPCContext { players, npc_list, .. }: BossNPCContext, + BossNPCContext { players, npc_list, npc_token, .. }: BossNPCContext, ) { match self.parts[0].action_num { 0 => { @@ -309,9 +309,9 @@ impl BossNPC { let _ = npc_list.spawn(0x100, npc); } - npc_list.kill_npcs_by_type(197, true, state); - npc_list.kill_npcs_by_type(271, true, state); - npc_list.kill_npcs_by_type(272, true, state); + npc_list.kill_npcs_by_type(197, true, state, npc_token); + npc_list.kill_npcs_by_type(271, true, state, npc_token); + npc_list.kill_npcs_by_type(272, true, state, npc_token); } self.parts[0].target_x -= 0x200; diff --git a/src/game/npc/boss/mod.rs b/src/game/npc/boss/mod.rs index 09ab0f3..1c43031 100644 --- a/src/game/npc/boss/mod.rs +++ b/src/game/npc/boss/mod.rs @@ -7,7 +7,7 @@ use crate::entity::GameEntity; use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::game::frame::Frame; -use crate::game::npc::list::NPCList; +use crate::game::npc::list::{NPCAccessToken, NPCList}; use crate::game::npc::NPC; use crate::game::player::Player; use crate::game::shared_game_state::SharedGameState; @@ -71,6 +71,7 @@ impl BossNPC { pub struct BossNPCContext<'a> { pub players: [&'a mut Player; 2], pub npc_list: &'a NPCList, + pub npc_token: &'a mut NPCAccessToken, pub stage: &'a mut Stage, pub bullet_manager: &'a mut BulletManager, pub flash: &'a mut Flash, diff --git a/src/game/npc/boss/monster_x.rs b/src/game/npc/boss/monster_x.rs index 2abe442..1641ff1 100644 --- a/src/game/npc/boss/monster_x.rs +++ b/src/game/npc/boss/monster_x.rs @@ -140,7 +140,7 @@ impl BossNPC { pub(crate) fn tick_b03_monster_x( &mut self, state: &mut SharedGameState, - BossNPCContext { players, npc_list, flash, .. }: BossNPCContext, + BossNPCContext { players, npc_list, npc_token, flash, .. }: BossNPCContext, ) { match self.parts[0].action_num { 0 => { @@ -491,7 +491,7 @@ impl BossNPC { part.cond.set_alive(false); } - npc_list.kill_npcs_by_type(158, true, state); + npc_list.kill_npcs_by_type(158, true, state, npc_token); let mut npc = NPC::create(159, &state.npc_table); npc.cond.set_alive(true); diff --git a/src/game/npc/boss/omega.rs b/src/game/npc/boss/omega.rs index 4386113..1659a10 100644 --- a/src/game/npc/boss/omega.rs +++ b/src/game/npc/boss/omega.rs @@ -52,7 +52,7 @@ impl BossNPC { pub(crate) fn tick_b01_omega( &mut self, state: &mut SharedGameState, - BossNPCContext { players, npc_list, bullet_manager, flash, .. }: BossNPCContext, + BossNPCContext { players, npc_list, npc_token, bullet_manager, flash, .. }: BossNPCContext, ) { match self.parts[0].action_num { 0 => { @@ -430,7 +430,7 @@ impl BossNPC { self.parts[0].damage = 0; self.parts[5].damage = 0; - npc_list.kill_npcs_by_type(48, true, state); + npc_list.kill_npcs_by_type(48, true, state, npc_token); } } } diff --git a/src/game/npc/boss/sisters.rs b/src/game/npc/boss/sisters.rs index ece68f2..f59e58d 100644 --- a/src/game/npc/boss/sisters.rs +++ b/src/game/npc/boss/sisters.rs @@ -13,7 +13,7 @@ impl BossNPC { pub(crate) fn tick_b06_sisters( &mut self, state: &mut SharedGameState, - BossNPCContext { players, npc_list, flash, .. }: BossNPCContext, + BossNPCContext { players, npc_list, npc_token, flash, .. }: BossNPCContext, ) { match self.parts[0].action_num { 0 => { @@ -225,7 +225,7 @@ impl BossNPC { 1020 => { self.parts[0].action_counter += 1; if self.parts[0].action_counter > 50 { - npc_list.kill_npcs_by_type(211, true, state); + npc_list.kill_npcs_by_type(211, true, state, npc_token); self.parts[0].cond.set_alive(false); self.parts[1].cond.set_alive(false); diff --git a/src/game/npc/boss/undead_core.rs b/src/game/npc/boss/undead_core.rs index df05a8e..23e8a94 100644 --- a/src/game/npc/boss/undead_core.rs +++ b/src/game/npc/boss/undead_core.rs @@ -322,7 +322,7 @@ impl BossNPC { pub(crate) fn tick_b07_undead_core( &mut self, state: &mut SharedGameState, - BossNPCContext { npc_list, stage, flash, .. }: BossNPCContext, + BossNPCContext { npc_list, npc_token, stage, flash, .. }: BossNPCContext, ) { let mut v19 = false; @@ -689,7 +689,7 @@ impl BossNPC { let _ = npc_list.spawn(0, npc.clone()); } - npc_list.kill_npcs_by_type(282, true, state); + npc_list.kill_npcs_by_type(282, true, state, npc_token); self.parts[11].npc_flags.set_shootable(false); @@ -754,8 +754,8 @@ impl BossNPC { self.parts[i].cond.set_alive(false); } - npc_list.kill_npcs_by_type(158, true, state); - npc_list.kill_npcs_by_type(301, true, state); + npc_list.kill_npcs_by_type(158, true, state, npc_token); + npc_list.kill_npcs_by_type(301, true, state, npc_token); } } _ => (), diff --git a/src/game/npc/list.rs b/src/game/npc/list.rs index 9c21b3d..94931a9 100644 --- a/src/game/npc/list.rs +++ b/src/game/npc/list.rs @@ -1,5 +1,6 @@ -use std::cell::{Cell, UnsafeCell}; +use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell}; use std::mem::{MaybeUninit, transmute}; +use std::ops::{ControlFlow, Deref, DerefMut}; use crate::framework::error::{GameError, GameResult}; use crate::game::npc::NPC; @@ -7,41 +8,143 @@ use crate::game::npc::NPC; /// Maximum capacity of NPCList const NPC_LIST_MAX_CAP: usize = 512; +pub struct NPCCell(RefCell); + +/// A zero-sized token used to control access to the NPC list and prevent borrow +/// panics. Some operations require this token to be provided. +pub struct NPCAccessToken { + /// Prevent forging an NPCAccessToken outside this module. + _private: () +} + +pub trait NPCTokenProvider { + fn unborrow_then(&mut self, f: F) -> T + where + F: FnOnce(&mut NPCAccessToken) -> T; +} + +impl NPCTokenProvider for NPCAccessToken { + fn unborrow_then(&mut self, f: F) -> T + where + F: FnOnce(&mut NPCAccessToken) -> T + { + f(self) + } +} + +/// A mutably borrowed NPC from the NPC list. This object can be temporarily +/// unborrowed using `unborrow_then` to retrieve the token. +pub enum BorrowedNPC<'a> { + Borrowed { + ref_mut: RefMut<'a, NPC>, + cell: &'a NPCCell, + token: &'a mut NPCAccessToken, + }, + Unborrowed, +} + +impl Deref for BorrowedNPC<'_> { + type Target = NPC; + + #[inline] + fn deref(&self) -> &Self::Target { + match self { + BorrowedNPC::Borrowed { ref_mut, .. } => &*ref_mut, + _ => unreachable!() + } + } +} + +impl DerefMut for BorrowedNPC<'_> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + BorrowedNPC::Borrowed { ref_mut, .. } => &mut *ref_mut, + _ => unreachable!() + } + } +} + +impl NPCTokenProvider for BorrowedNPC<'_> { + fn unborrow_then(&mut self, f: F) -> T + where + F: FnOnce(&mut NPCAccessToken) -> T + { + match self { + BorrowedNPC::Borrowed { .. } => { + let old = std::mem::replace(self, BorrowedNPC::Unborrowed); + let (old_ref_mut, token, cell) = match old { + BorrowedNPC::Borrowed { ref_mut, token, cell } => (ref_mut, token, cell), + _ => unreachable!() + }; + drop(old_ref_mut); + + let result = f(token); + + *self = cell.borrow_mut(token); + result + } + _ => unreachable!() + } + } +} + +impl NPCCell { + /// Immutably borrows the NPC. The access token is held immutably until the + /// borrow is dropped. + pub fn borrow<'a>(&'a self, _token: &'a NPCAccessToken) -> Ref<'a, NPC> { + // The access token's rules are enforced by this function's signature. + self.0.borrow() + } + + /// Borrows the NPC without requiring an access token. The caller must ensure + /// this does not cause borrow panics. + /// This should only be used for quick, temporary access. + pub fn borrow_unmanaged(&self) -> Ref<'_, NPC> { + self.0.borrow() + } + + /// Mutably borrows the NPC. The access token is held until the borrow is dropped. + pub fn borrow_mut<'a>(&'a self, token: &'a mut NPCAccessToken) -> BorrowedNPC<'a> { + BorrowedNPC::Borrowed { + ref_mut: self.0.borrow_mut(), + cell: self, + token, + } + } + + /// Borrows the NPC without requiring an access token. The caller must ensure + /// this does not cause borrow panics. + /// This should only be used for quick, temporary access. + pub fn borrow_mut_unmanaged(&self) -> RefMut<'_, NPC> { + self.0.borrow_mut() + } +} + /// 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>, + npcs: Box<[NPCCell; NPC_LIST_MAX_CAP]>, max_npc: Cell, seed: i32, } #[allow(dead_code)] impl NPCList { - pub fn new() -> NPCList { + pub fn new() -> (NPCList, NPCAccessToken) { let map = NPCList { - npcs: Box::new(UnsafeCell::new(unsafe { - const PART: MaybeUninit = MaybeUninit::uninit(); - let mut parts_uninit: [MaybeUninit; NPC_LIST_MAX_CAP] = [PART; NPC_LIST_MAX_CAP]; - - for part in &mut parts_uninit { - part.write(NPC::empty()); - } - - transmute(parts_uninit) - })), + npcs: Box::new(std::array::from_fn(|_| NPCCell(RefCell::new(NPC::empty())))), max_npc: Cell::new(0), seed: 0, }; - unsafe { - for (idx, npc_ref) in map.npcs_mut().iter_mut().enumerate() { - npc_ref.id = idx as u16; - } + let mut token = NPCAccessToken { _private: () }; + + for (idx, npc_ref) in map.npcs.iter().enumerate() { + npc_ref.borrow_mut(&mut token).id = idx as u16; } - map + (map, token) } pub fn set_rng_seed(&mut self, seed: i32) { @@ -50,16 +153,16 @@ impl NPCList { /// 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() }; + let npc_len = 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) }; + let npc_ref = self.npcs.get(id as usize).unwrap(); - if !npc_ref.cond.alive() { + if npc_ref.0.try_borrow().is_ok_and(|npc_ref| !npc_ref.cond.alive()) { npc.id = id; if npc.tsc_direction == 0 { @@ -68,7 +171,7 @@ impl NPCList { npc.init_rng(self.seed); - *npc_ref = npc; + *npc_ref.0.borrow_mut() = npc; if self.max_npc.get() <= id { self.max_npc.replace(id + 1); @@ -83,7 +186,7 @@ impl NPCList { /// 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() }; + let npc_len = self.npcs.len(); if id as usize >= npc_len { return Err(GameError::InvalidValue("NPC ID is out of bounds".to_string())); @@ -97,10 +200,8 @@ impl NPCList { npc.init_rng(self.seed); - unsafe { - let npc_ref = self.npcs_mut().get_unchecked_mut(id as usize); - *npc_ref = npc; - } + let npc_ref = self.npcs.get(id as usize).unwrap(); + *npc_ref.0.borrow_mut() = npc; if self.max_npc.get() <= id { self.max_npc.replace(id + 1); @@ -109,24 +210,62 @@ impl NPCList { 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 NPC cell from this list. + pub fn get_npc(&self, id: usize) -> Option<&NPCCell> { + self.npcs.get(id) } /// Returns an iterator that iterates over allocated (not up to it's capacity) NPC slots. - pub fn iter(&self) -> NPCListMutableIterator { - NPCListMutableIterator::new(self) + pub fn iter(&self) -> impl Iterator { + // FIXME: what if max_npc changes during iteration? + // should we take that into account? + self.npcs.iter().take(self.max_npc.get() as usize) } /// Returns an iterator over alive NPC slots. - pub fn iter_alive(&self) -> NPCListMutableAliveIterator { - NPCListMutableAliveIterator::new(self) + /// Unfortunately, due to Rust's borrowing rules, a mutable iterator version isn't possible. + /// To iterate mutably over alive NPC's, use `for_each_alive_mut`. + pub fn iter_alive<'a>(&'a self, token: &'a NPCAccessToken) -> NPCListAliveIterator<'a> { + NPCListAliveIterator::new(self, token) + } + + /// Calls a closure for each alive NPC. + /// To allow early exit, use `try_for_each_alive_mut`. + pub fn for_each_alive_mut(&self, token: &mut impl NPCTokenProvider, mut f: F) + where + F: FnMut(BorrowedNPC<'_>) + { + token.unborrow_then(|token| { + for cell in self.iter() { + if cell.borrow(token).cond.alive() { + f(cell.borrow_mut(token)); + } + } + }); + } + + pub fn try_for_each_alive_mut(&self, token: &mut impl NPCTokenProvider, mut f: F) -> Result<(), B> + where + F: FnMut(BorrowedNPC<'_>) -> ControlFlow + { + token.unborrow_then(|token| { + for cell in self.iter() { + if cell.borrow(token).cond.alive() { + if let ControlFlow::Break(b) = f(cell.borrow_mut(token)) { + return Err(b); + } + } + } + + Ok(()) + }) } /// Removes all NPCs from this list and resets it's capacity. - pub fn clear(&self) { - for (idx, npc) in self.iter_alive().enumerate() { + pub fn clear(&self, token: &mut NPCAccessToken) { + for (idx, npc) in self.iter().enumerate() { + let mut npc = npc.borrow_mut(token); + *npc = NPC::empty(); npc.id = idx as u16; } @@ -143,55 +282,22 @@ impl NPCList { 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> { +pub struct NPCListAliveIterator<'a> { index: u16, map: &'a NPCList, + token: &'a NPCAccessToken, } -impl<'a> NPCListMutableIterator<'a> { - pub fn new(map: &'a NPCList) -> NPCListMutableIterator<'a> { - NPCListMutableIterator { index: 0, map } +impl<'a> NPCListAliveIterator<'a> { + pub fn new(map: &'a NPCList, token: &'a NPCAccessToken) -> NPCListAliveIterator<'a> { + NPCListAliveIterator { index: 0, map, token } } } -impl<'a> Iterator for NPCListMutableIterator<'a> { - type Item = &'a mut NPC; - - fn next(&mut self) -> Option { - 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; +impl<'a> Iterator for NPCListAliveIterator<'a> { + type Item = Ref<'a, NPC>; fn next(&mut self) -> Option { loop { @@ -199,15 +305,15 @@ impl<'a> Iterator for NPCListMutableAliveIterator<'a> { return None; } - let item = unsafe { self.map.npcs_mut().get_mut(self.index as usize) }; + let item = self.map.npcs.get(self.index as usize); self.index += 1; match item { None => { return None; } - Some(ref npc) if npc.cond.alive() => { - return item; + Some(ref npc) if (*npc).borrow(self.token).cond.alive() => { + return Some(item?.borrow(self.token)); } _ => {} } @@ -229,16 +335,18 @@ pub fn test_npc_list() -> GameResult { npc.cond.set_alive(true); { - let map = Box::new(NPCList::new()); + let (map, mut token) = 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); + assert_eq!(map.iter_alive(&token).count(), 3); for npc_ref in map.iter() { + let mut npc_ref = npc_ref.borrow_mut(&mut token); + if ctr > 0 { ctr -= 1; map.spawn(100, npc.clone())?; @@ -250,20 +358,22 @@ pub fn test_npc_list() -> GameResult { } } - assert_eq!(map.iter_alive().count(), 43); + assert_eq!(map.iter_alive(&token).count(), 43); for npc_ref in map.iter().skip(256) { + let mut npc_ref = npc_ref.borrow_mut(&mut token); + if npc_ref.cond.alive() { npc_ref.cond.set_alive(false); } } - assert_eq!(map.iter_alive().count(), 22); + assert_eq!(map.iter_alive(&token).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); + map.clear(&mut token); + assert_eq!(map.iter_alive(&token).count(), 0); for i in 0..map.max_capacity() { map.spawn(i, npc.clone())?; diff --git a/src/game/npc/mod.rs b/src/game/npc/mod.rs index 1e58d74..6e2aa00 100644 --- a/src/game/npc/mod.rs +++ b/src/game/npc/mod.rs @@ -17,7 +17,7 @@ use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::game::frame::Frame; use crate::game::npc::boss::BossNPC; -use crate::game::npc::list::NPCList; +use crate::game::npc::list::{BorrowedNPC, NPCList}; use crate::game::physics::PhysicalEntity; use crate::game::player::Player; use crate::game::shared_game_state::SharedGameState; @@ -184,7 +184,7 @@ impl NPC { layer: NPCLayer, ) -> GameResult { if self.layer == layer { - self.draw(state, ctx, frame)? + self.npc_draw(state, ctx, frame)? } Ok(()) @@ -240,7 +240,7 @@ pub struct NPCContext<'a> { pub boss: &'a mut BossNPC, } -impl GameEntity> for NPC { +impl GameEntity> for BorrowedNPC<'_> { fn tick(&mut self, state: &mut SharedGameState, ctx: NPCContext) -> GameResult { match self.npc_type { 0 => self.tick_n000_null(state, ctx), @@ -641,6 +641,12 @@ impl GameEntity> for NPC { } fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult { + self.npc_draw(state, ctx, frame) + } +} + +impl NPC { + pub fn npc_draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult { if !self.cond.alive() || self.cond.hidden() { return Ok(()); } diff --git a/src/game/npc/utils.rs b/src/game/npc/utils.rs index 8c93027..06b4d3d 100644 --- a/src/game/npc/utils.rs +++ b/src/game/npc/utils.rs @@ -1,3 +1,5 @@ +use std::cell::{Ref, RefMut}; + ///! Various utility functions for NPC-related objects use num_traits::abs; @@ -6,7 +8,7 @@ use crate::components::number_popup::NumberPopup; use crate::game::caret::CaretType; use crate::game::map::NPCData; use crate::game::npc::{NPC, NPCFlag, NPCLayer, NPCTable}; -use crate::game::npc::list::NPCList; +use crate::game::npc::list::{NPCAccessToken, NPCCell, NPCList, NPCTokenProvider}; use crate::game::player::Player; use crate::game::shared_game_state::{SharedGameState, TileSize}; use crate::game::weapon::bullet::Bullet; @@ -108,13 +110,21 @@ impl NPC { } /// 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> { + pub fn get_parent_cell<'a>(&self, npc_list: &'a NPCList) -> Option<&'a NPCCell> { match self.parent_id { 0 => None, id if id == self.id => None, id => npc_list.get_npc(id as usize), } } + + pub fn get_parent<'a>(&self, npc_list: &'a NPCList) -> Option> { + self.get_parent_cell(npc_list).map(|npc| npc.borrow_unmanaged()) + } + + pub fn get_parent_mut<'a>(&self, npc_list: &'a NPCList) -> Option> { + self.get_parent_cell(npc_list).map(|npc| npc.borrow_mut_unmanaged()) + } /// Cycles animation frames in given range and speed. pub fn animate(&mut self, ticks_between_frames: u16, start_frame: u16, end_frame: u16) { @@ -243,19 +253,23 @@ impl NPC { 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) + pub fn is_alive_by_type(&self, npc_type: u16, token: &NPCAccessToken) -> bool { + self.iter_alive(token).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) + pub fn is_alive_by_event(&self, event_num: u16, token: &NPCAccessToken) -> bool { + self.iter_alive(token).any(|npc| npc.event_num == event_num) } /// Deletes NPCs with specified type. - pub fn kill_npcs_by_type(&self, npc_type: u16, smoke: bool, state: &mut SharedGameState) { - for npc in self.iter_alive().filter(|n| n.npc_type == npc_type) { + pub fn kill_npcs_by_type(&self, npc_type: u16, smoke: bool, state: &mut SharedGameState, token: &mut impl NPCTokenProvider) { + self.for_each_alive_mut(token, |mut npc| { + if npc.npc_type != npc_type { + return; + } + state.set_flag(npc.flag_num as usize, true); npc.cond.set_alive(false); @@ -277,12 +291,14 @@ impl NPCList { _ => {} }; } - } + }); } /// 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) { + pub fn kill_npc(&self, id: usize, vanish: bool, can_drop_missile: bool, state: &mut SharedGameState, token: &mut NPCAccessToken) { if let Some(npc) = self.get_npc(id) { + let mut npc = npc.borrow_mut(token); + if let Some(table_entry) = state.npc_table.get_entry(npc.npc_type) { state.sound_manager.play_sfx(table_entry.death_sound); } @@ -348,13 +364,13 @@ impl NPCList { } /// Removes NPCs whose event number matches the provided one. - pub fn kill_npcs_by_event(&self, event_num: u16, state: &mut SharedGameState) { - for npc in self.iter_alive() { + pub fn kill_npcs_by_event(&self, event_num: u16, state: &mut SharedGameState, token: &mut NPCAccessToken) { + self.for_each_alive_mut(token, |mut npc| { if npc.event_num == event_num { npc.cond.set_alive(false); state.set_flag(npc.flag_num as usize, true); } - } + }); } /// Creates NPC death smoke diffusing in random directions. diff --git a/src/game/player/player_hit.rs b/src/game/player/player_hit.rs index 871aff0..4a44c01 100644 --- a/src/game/player/player_hit.rs +++ b/src/game/player/player_hit.rs @@ -6,7 +6,7 @@ use crate::common::{Condition, Direction, Flag, Rect}; use crate::game::caret::CaretType; use crate::game::inventory::Inventory; use crate::game::npc::boss::BossNPC; -use crate::game::npc::list::NPCList; +use crate::game::npc::list::{NPCAccessToken, NPCList}; use crate::game::npc::NPC; use crate::game::physics::{HitExtents, PhysicalEntity}; use crate::game::player::{ControlMode, Player, TargetPlayer}; @@ -366,6 +366,7 @@ impl Player { id: TargetPlayer, state: &mut SharedGameState, npc_list: &NPCList, + npc_token: &mut NPCAccessToken, boss: &mut BossNPC, inventory: &mut Inventory, ) { @@ -373,9 +374,9 @@ impl Player { return; } - for npc in npc_list.iter_alive() { - self.tick_npc_collision(id, state, npc, npc_list, inventory); - } + npc_list.for_each_alive_mut(npc_token, |mut npc| { + self.tick_npc_collision(id, state, &mut npc, npc_list, inventory); + }); for boss_npc in &mut boss.parts { if boss_npc.cond.alive() { diff --git a/src/game/scripting/tsc/text_script.rs b/src/game/scripting/tsc/text_script.rs index b1f7b4f..997f3d8 100644 --- a/src/game/scripting/tsc/text_script.rs +++ b/src/game/scripting/tsc/text_script.rs @@ -5,6 +5,7 @@ use std::io; use std::io::Cursor; use std::io::Seek; use std::io::SeekFrom; +use std::ops::ControlFlow; use std::ops::Not; use std::rc::Rc; @@ -16,6 +17,7 @@ use crate::common::{Direction, FadeDirection, FadeState, Rect}; use crate::engine_constants::EngineConstants; use crate::entity::GameEntity; use crate::framework::context::Context; +use crate::framework::error::map_err_to_break; use crate::framework::error::GameResult; use crate::game::frame::UpdateTarget; use crate::game::npc::NPCContext; @@ -900,7 +902,7 @@ impl TextScriptVM { game_scene.player2.direction = direction; } } else if new_direction >= 10 { - for npc in game_scene.npc_list.iter_alive() { + for npc in game_scene.npc_list.iter_alive(&game_scene.npc_token) { // The vanilla game treats this as a 1-byte value lol //if npc.event_num == (new_direction & 0xFF) as u16 { if npc.event_num == new_direction as u16 { @@ -954,7 +956,7 @@ impl TextScriptVM { _ => (), } } else { - for npc in game_scene.npc_list.iter_alive() { + for npc in game_scene.npc_list.iter_alive(&game_scene.npc_token) { if npc.event_num == new_direction as u16 { if game_scene.player1.x >= npc.x { game_scene.player1.direction = Left; @@ -1095,7 +1097,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_list.is_alive_by_type(npc_type) { + if game_scene.npc_list.is_alive_by_type(npc_type, &game_scene.npc_token) { state.textscript_vm.clear_text_box(); exec_state = TextScriptExecutionState::Running(event_num, 0); } else { @@ -1106,7 +1108,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_list.is_alive_by_event(npc_event_num) { + if game_scene.npc_list.is_alive_by_event(npc_event_num, &game_scene.npc_token) { state.textscript_vm.clear_text_box(); exec_state = TextScriptExecutionState::Running(event_num, 0); } else { @@ -1483,14 +1485,14 @@ impl TextScriptVM { TSCOpCode::DNP => { let event_num = read_cur_varint(&mut cursor)? as u16; - game_scene.npc_list.kill_npcs_by_event(event_num, state); + game_scene.npc_list.kill_npcs_by_event(event_num, state, &mut game_scene.npc_token); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } TSCOpCode::DNA => { let npc_remove_type = read_cur_varint(&mut cursor)? as u16; - game_scene.npc_list.kill_npcs_by_type(npc_remove_type, true, state); + game_scene.npc_list.kill_npcs_by_type(npc_remove_type, true, state, &mut game_scene.npc_token); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } @@ -1516,6 +1518,8 @@ impl TextScriptVM { game_scene.frame.wait = ticks; for npc in game_scene.npc_list.iter() { + let npc = npc.borrow(&game_scene.npc_token); + if event_num == npc.event_num { game_scene.frame.update_target = UpdateTarget::NPC(npc.id); break; @@ -1530,9 +1534,9 @@ impl TextScriptVM { if event_num == 0 { game_scene.boss_life_bar.set_boss_target(&game_scene.boss); } else { - for npc in game_scene.npc_list.iter_alive() { + for npc in game_scene.npc_list.iter_alive(&game_scene.npc_token) { if event_num == npc.event_num { - game_scene.boss_life_bar.set_npc_target(npc.id, &game_scene.npc_list); + game_scene.boss_life_bar.set_npc_target(npc.id, &game_scene.npc_list, &game_scene.npc_token); break; } } @@ -1553,7 +1557,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); - for npc in game_scene.npc_list.iter_alive() { + game_scene.npc_list.for_each_alive_mut(&mut game_scene.npc_token, |mut npc| { if npc.event_num == event_num { npc.action_num = action_num; npc.tsc_direction = tsc_direction as u16; @@ -1569,7 +1573,7 @@ impl TextScriptVM { npc.direction = direction; } } - } + }); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } @@ -1579,7 +1583,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); - for npc in game_scene.npc_list.iter_alive() { + game_scene.npc_list.try_for_each_alive_mut(&mut game_scene.npc_token, |mut npc| { if npc.event_num == event_num { npc.npc_flags.set_solid_soft(false); npc.npc_flags.set_ignore_tile_44(false); @@ -1626,7 +1630,7 @@ impl TextScriptVM { npc.direction = direction; } - npc.tick( + map_err_to_break(npc.tick( state, NPCContext { players: [&mut game_scene.player1, &mut game_scene.player2], @@ -1636,9 +1640,11 @@ impl TextScriptVM { flash: &mut game_scene.flash, boss: &mut game_scene.boss, }, - )?; + ))?; } - } + + ControlFlow::Continue(()) + })?; exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } @@ -1650,7 +1656,7 @@ impl TextScriptVM { let direction = Direction::from_int_facing(tsc_direction).unwrap_or(Direction::Left); let block_size = state.tile_size.as_int() * 0x200; - for npc in game_scene.npc_list.iter_alive() { + let _ = game_scene.npc_list.try_for_each_alive_mut(&mut game_scene.npc_token, |mut npc| { if npc.event_num == event_num { npc.x = x * block_size; npc.y = y * block_size; @@ -1667,9 +1673,11 @@ impl TextScriptVM { npc.direction = direction; } - break; + return ControlFlow::Break(()); } - } + + ControlFlow::Continue(()) + }); exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } diff --git a/src/live_debugger/mod.rs b/src/live_debugger/mod.rs index 2ef928b..3a21ee3 100644 --- a/src/live_debugger/mod.rs +++ b/src/live_debugger/mod.rs @@ -165,7 +165,7 @@ impl LiveDebugger { ui.text(format!( "NPC Count: {}/{}/{} Booster fuel: {}", - game_scene.npc_list.iter_alive().count(), + game_scene.npc_list.iter_alive(&game_scene.npc_token).count(), game_scene.npc_list.current_capacity(), game_scene.npc_list.max_capacity(), game_scene.player1.booster_fuel @@ -423,7 +423,7 @@ impl LiveDebugger { .scrollable(true) .always_vertical_scrollbar(true) .build(|| { - for npc in game_scene.npc_list.iter_alive() { + game_scene.npc_list.for_each_alive_mut(&mut game_scene.npc_token, |mut npc| { if CollapsingHeader::new(&ImString::from(format!("id={} type={}", npc.id, npc.npc_type))) .default_open(false) .build(ui) @@ -466,7 +466,7 @@ impl LiveDebugger { cond_flags(ui, &mut npc.cond); } - } + }); }); } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index f591875..8a2de3c 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -1,5 +1,5 @@ use std::cell::RefCell; -use std::ops::{Deref, Range}; +use std::ops::{ControlFlow, Deref, Range}; use std::rc::Rc; use log::info; @@ -25,7 +25,7 @@ use crate::components::whimsical_star::WhimsicalStar; use crate::entity::GameEntity; use crate::framework::backend::SpriteBatchCommand; use crate::framework::context::Context; -use crate::framework::error::GameResult; +use crate::framework::error::{map_err_to_break, GameResult}; use crate::framework::graphics::{draw_rect, BlendMode, FilterMode}; use crate::framework::keyboard::ScanCode; use crate::framework::ui::Components; @@ -35,7 +35,7 @@ use crate::game::frame::{Frame, UpdateTarget}; use crate::game::inventory::{Inventory, TakeExperienceResult}; use crate::game::map::WaterParams; use crate::game::npc::boss::{BossNPC, BossNPCContext}; -use crate::game::npc::list::NPCList; +use crate::game::npc::list::{NPCAccessToken, NPCList, NPCTokenProvider}; use crate::game::npc::{NPCContext, NPCLayer, NPC}; use crate::game::physics::{PhysicalEntity, OFFSETS}; use crate::game::player::{ControlMode, Player, TargetPlayer}; @@ -81,6 +81,7 @@ pub struct GameScene { pub inventory_player2: Inventory, pub stage_id: usize, pub npc_list: NPCList, + pub npc_token: NPCAccessToken, pub boss: BossNPC, pub bullet_manager: BulletManager, pub lighting_mode: LightingMode, @@ -146,6 +147,8 @@ impl GameScene { player2.load_skin(skinsheet_name.to_owned(), state, ctx); } + let (npc_list, npc_token) = NPCList::new(); + Ok(Self { tick: 0, stage, @@ -172,7 +175,8 @@ impl GameScene { fade: Fade::new(), frame: Frame::new(), stage_id: id, - npc_list: NPCList::new(), + npc_list, + npc_token, boss: BossNPC::new(), bullet_manager: BulletManager::new(), lighting_mode: LightingMode::None, @@ -210,7 +214,7 @@ impl GameScene { } fn draw_npc_layer(&self, state: &mut SharedGameState, ctx: &mut Context, layer: NPCLayer) -> GameResult { - for npc in self.npc_list.iter_alive() { + for npc in self.npc_list.iter_alive(&self.npc_token) { if npc.layer != layer || npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as i32 * 0x200) || npc.x @@ -226,14 +230,14 @@ impl GameScene { continue; } - npc.draw(state, ctx, &self.frame)?; + npc.npc_draw(state, ctx, &self.frame)?; } Ok(()) } fn draw_npc_popup(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - for npc in self.npc_list.iter_alive() { + for npc in self.npc_list.iter_alive(&self.npc_token) { npc.popup.draw(state, ctx, &self.frame)?; } Ok(()) @@ -513,7 +517,7 @@ impl GameScene { graphics::clear(ctx, Color::from_rgb(100, 100, 110)); - for npc in self.npc_list.iter_alive() { + for npc in self.npc_list.iter_alive(&self.npc_token) { if npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as i32 * 0x200) || npc.x > (self.frame.x @@ -627,7 +631,7 @@ impl GameScene { } } - for npc in self.npc_list.iter_alive() { + for npc in self.npc_list.iter_alive(&self.npc_token) { if npc.cond.hidden() || (npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as i32 * 0x200) || npc.x @@ -1106,10 +1110,10 @@ impl GameScene { } fn tick_npc_splash(&mut self, state: &mut SharedGameState) { - for npc in self.npc_list.iter_alive() { + self.npc_list.for_each_alive_mut(&mut self.npc_token, |mut npc| { // Water Droplet if npc.npc_type == 73 { - continue; + return; } if !npc.splash && npc.flags.in_water() { @@ -1144,13 +1148,13 @@ impl GameScene { if !npc.flags.in_water() { npc.splash = false; } - } + }); } fn tick_npc_bullet_collissions(&mut self, state: &mut SharedGameState) { - for npc in self.npc_list.iter_alive() { + self.npc_list.for_each_alive_mut(&mut self.npc_token, |mut npc| { if npc.npc_flags.shootable() && npc.npc_flags.interactable() { - continue; + return; } for bullet in self.bullet_manager.bullets.iter_mut() { @@ -1228,9 +1232,13 @@ impl GameScene { inv.has_weapon(WeaponType::MissileLauncher) || inv.has_weapon(WeaponType::SuperMissileLauncher) }); - self.npc_list.kill_npc(npc.id as usize, !npc.cond.drs_novanish(), can_drop_missile, state); + let npc_id = npc.id; + let npc_cond = npc.cond; + npc.unborrow_then(|token| { + self.npc_list.kill_npc(npc_id as usize, !npc_cond.drs_novanish(), can_drop_missile, state, token); + }); } - } + }); for i in 0..self.boss.parts.len() { let mut idx = i; @@ -1375,8 +1383,8 @@ impl GameScene { self.player2.damage = 0; } - for npc in self.npc_list.iter_alive() { - npc.tick( + self.npc_list.try_for_each_alive_mut(&mut self.npc_token, |mut npc| { + map_err_to_break(npc.tick( state, NPCContext { players: [&mut self.player1, &mut self.player2], @@ -1386,18 +1394,23 @@ impl GameScene { flash: &mut self.flash, boss: &mut self.boss, }, - )?; - } + ))?; + + ControlFlow::Continue(()) + })?; + self.boss.tick( state, BossNPCContext { players: [&mut self.player1, &mut self.player2], npc_list: &self.npc_list, + npc_token: &mut self.npc_token, stage: &mut self.stage, bullet_manager: &mut self.bullet_manager, flash: &mut self.flash, }, )?; + //decides if the player is tangible or not if !state.settings.noclip { self.player1.tick_map_collisions(state, &self.npc_list, &mut self.stage); @@ -1407,6 +1420,7 @@ impl GameScene { TargetPlayer::Player1, state, &self.npc_list, + &mut self.npc_token, &mut self.boss, &mut self.inventory_player1, ); @@ -1414,16 +1428,18 @@ impl GameScene { TargetPlayer::Player2, state, &self.npc_list, + &mut self.npc_token, &mut self.boss, &mut self.inventory_player2, ); } - for npc in self.npc_list.iter_alive() { + self.npc_list.for_each_alive_mut(&mut self.npc_token, |mut npc| { if !npc.npc_flags.ignore_solidity() { npc.tick_map_collisions(state, &self.npc_list, &mut self.stage); } - } + }); + for npc in self.boss.parts.iter_mut() { if npc.cond.alive() && !npc.npc_flags.ignore_solidity() { npc.tick_map_collisions(state, &self.npc_list, &mut self.stage); @@ -1500,6 +1516,8 @@ impl GameScene { } UpdateTarget::NPC(npc_id) => { if let Some(npc) = self.npc_list.get_npc(npc_id as usize) { + let npc = npc.borrow(&self.npc_token); + if npc.cond.alive() { self.frame.target_x = npc.x; self.frame.target_y = npc.y; @@ -1523,7 +1541,7 @@ impl GameScene { if state.control_flags.control_enabled() { 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_list, &self.boss))?; + self.boss_life_bar.tick(state, (&self.npc_list, &self.npc_token, &self.boss))?; if state.textscript_vm.state == TextScriptExecutionState::Ended { if self.player1.controller.trigger_inventory() { @@ -1541,7 +1559,7 @@ impl GameScene { self.player2.has_dog = self.inventory_player2.has_item(14); } - self.water_renderer.tick(state, (&[&self.player1, &self.player2], &self.npc_list))?; + self.water_renderer.tick(state, (&[&self.player1, &self.player2], &self.npc_list, &self.npc_token))?; if self.map_name_counter > 0 { self.map_name_counter -= 1; @@ -1624,8 +1642,8 @@ impl GameScene { } fn draw_debug_outlines(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - for npc in self.npc_list.iter_alive() { - self.draw_debug_npc(npc, state, ctx)?; + for npc in self.npc_list.iter_alive(&self.npc_token) { + self.draw_debug_npc(&npc, state, ctx)?; } for boss in self.boss.parts.iter().filter(|n| n.cond.alive()) { @@ -1927,12 +1945,12 @@ impl Scene for GameScene { self.player2.exp_popup.prev_x = self.player2.exp_popup.x; self.player2.exp_popup.prev_y = self.player2.exp_popup.y; - for npc in self.npc_list.iter_alive() { + self.npc_list.for_each_alive_mut(&mut self.npc_token, |mut npc| { npc.prev_x = npc.x; npc.prev_y = npc.y; npc.popup.prev_x = npc.prev_x; npc.popup.prev_y = npc.prev_y; - } + }); for npc in self.boss.parts.iter_mut() { if npc.cond.alive() {