1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-12-08 05:05:40 +00:00

Use RefCells in NPCList to prevent Undefined Behavior

This commit is contained in:
N.E.C. 2025-08-21 21:59:09 -07:00 committed by alula
parent 20f541d469
commit c7160c9ea8
34 changed files with 446 additions and 248 deletions

View file

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

View file

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

View file

@ -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<T = ()> = Result<T, GameError>;
/// Convert Result::Err(e) to ControlFlow::Break(e). Useful in try_for_each methods.
pub fn map_err_to_break<T, E>(result: Result<T, E>) -> ControlFlow<E, T> {
match result {
Result::Ok(t) => ControlFlow::Continue(t),
Result::Err(e) => ControlFlow::Break(e)
}
}
impl From<std::io::Error> for GameError {
fn from(e: std::io::Error) -> GameError {
GameError::IOError(Arc::new(e))

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}
}
_ => (),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}
}
_ => (),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}
}
_ => (),

View file

@ -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<NPC>);
/// 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<F, T>(&mut self, f: F) -> T
where
F: FnOnce(&mut NPCAccessToken) -> T;
}
impl NPCTokenProvider for NPCAccessToken {
fn unborrow_then<F, T>(&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<F, T>(&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<UnsafeCell<[NPC; NPC_LIST_MAX_CAP]>>,
npcs: Box<[NPCCell; NPC_LIST_MAX_CAP]>,
max_npc: Cell<u16>,
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<NPC> = MaybeUninit::uninit();
let mut parts_uninit: [MaybeUninit<NPC>; 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<Item = &NPCCell> {
// 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<F>(&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<F, B>(&self, token: &mut impl NPCTokenProvider, mut f: F) -> Result<(), B>
where
F: FnMut(BorrowedNPC<'_>) -> ControlFlow<B, ()>
{
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<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;
impl<'a> Iterator for NPCListAliveIterator<'a> {
type Item = Ref<'a, NPC>;
fn next(&mut self) -> Option<Self::Item> {
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())?;

View file

@ -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<NPCContext<'_>> for NPC {
impl GameEntity<NPCContext<'_>> 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<NPCContext<'_>> 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(());
}

View file

@ -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<Ref<'a, NPC>> {
self.get_parent_cell(npc_list).map(|npc| npc.borrow_unmanaged())
}
pub fn get_parent_mut<'a>(&self, npc_list: &'a NPCList) -> Option<RefMut<'a, NPC>> {
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.

View file

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

View file

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

View file

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

View file

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