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:
parent
20f541d469
commit
c7160c9ea8
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
|
|
|||
|
|
@ -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())?;
|
||||
|
|
|
|||
|
|
@ -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(());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue