1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-09-20 03:03:50 +00:00

Compare commits

...

13 commits

Author SHA1 Message Date
biroder 1042d9e57d
Merge bf0dc34894 into 8b5406d941 2025-08-26 22:14:00 +00:00
biroder bf0dc34894 Implement import of CS+ saves [ci skip] 2025-08-27 01:11:59 +03:00
biroder b495396c52 Add import of freeware and generic saves [ci skip] 2025-08-27 01:11:59 +03:00
biroder 22823dccac Implement export of the profile [ci skip] 2025-08-27 01:11:58 +03:00
biroder ed1734dd73 Implement more or less valid saving in CS+ format [ci skip]
Saving whether eggfish is killed is not implemented. Also saving of empty slots is not accurately implemented, but this shouldn't cause problems.
2025-08-27 01:11:57 +03:00
biroder 047bf22594 Implement deletion of freeware format saves 2025-08-27 01:11:57 +03:00
biroder 1347952f65 Add a basis for working with all possible save formats [ci skip] 2025-08-27 01:11:57 +03:00
N.E.C. 8b5406d941 Improve code generation when accessing BorrowedNPC's 2025-08-23 02:15:39 +02:00
N.E.C. c7160c9ea8 Use RefCells in NPCList to prevent Undefined Behavior 2025-08-23 02:15:39 +02:00
Alula 20f541d469 Fix apt install failing on ubuntu-latest sometimes 2025-08-23 02:15:39 +02:00
Alula f5d729c8a1 Refactor: Unify NPC AI handler signature 2025-08-23 02:15:39 +02:00
Alula 2335cf0064 Use HitExtents instead of Rect for hitboxes 2025-08-23 02:15:39 +02:00
biroder f4602687eb Fix editor compilation errors
The `--editor` flag has no practical use and has never been used, so it's been removed.
2025-08-04 23:42:59 +03:00
74 changed files with 4610 additions and 1712 deletions

View file

@ -68,7 +68,9 @@ jobs:
- uses: actions/checkout@v4
- name: Install dependencies
if: ${{ matrix.os == 'ubuntu-latest' }}
run: sudo apt install libasound2-dev libudev-dev libgl1-mesa-dev libxext-dev
run: |
sudo apt update
sudo apt install libasound2-dev libudev-dev libgl1-mesa-dev libxext-dev
- name: Set version
id: set_version

958
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -20,8 +20,6 @@ required-features = ["exe"]
# This is intentional. Enabling LTO inflates build times and does not provide us any significant benefits.
lto = "off"
panic = "abort"
codegen-units = 256
incremental = true
split-debuginfo = "packed"
[profile.dev.package."*"]
@ -84,10 +82,12 @@ pelite = { version = ">=0.9.2", default-features = false, features = ["std"] }
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "244ae85833cff4f97ab4b58331741be20e422bd7", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
sdl2-sys = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "244ae85833cff4f97ab4b58331741be20e422bd7", optional = true, features = ["bundled", "static-link"] }
rc-box = "1.2.0"
rfd = "0.15.4"
serde = { version = "1", features = ["derive"] }
serde_derive = "1"
serde_cbor = { version = "0.11", optional = true }
serde_json = "1.0"
serde_json = { version = "1.0", features = ["alloc", "unbounded_depth"] }
serde_with = { version = "3.11", default_features = false, features = ["macros"] }
strum = "0.24"
strum_macros = "0.24"
# remove and replace with extract_if, when our MSRV is 1.87

75
drsandroid/Cargo.lock generated
View file

@ -533,8 +533,18 @@ version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [
"darling_core",
"darling_macro",
"darling_core 0.13.4",
"darling_macro 0.13.4",
]
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core 0.20.11",
"darling_macro 0.20.11",
]
[[package]]
@ -547,21 +557,46 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim",
"strsim 0.10.0",
"syn 1.0.109",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.11.1",
"syn 2.0.101",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"darling_core 0.13.4",
"quote",
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core 0.20.11",
"quote",
"syn 2.0.101",
]
[[package]]
name = "dasp_sample"
version = "0.11.0"
@ -663,6 +698,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"serde_with",
"strum",
"strum_macros",
"vec_mut_scan",
@ -1452,7 +1488,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
dependencies = [
"darling",
"darling 0.13.4",
"proc-macro-crate 1.3.1",
"proc-macro2",
"quote",
@ -1962,6 +1998,29 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_with"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
dependencies = [
"serde",
"serde_derive",
"serde_with_macros",
]
[[package]]
name = "serde_with_macros"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
dependencies = [
"darling 0.20.11",
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "shared_library"
version = "0.1.9"
@ -2002,6 +2061,12 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.24.1"

View file

@ -194,7 +194,7 @@ pub enum FadeState {
FadeOut(i8, FadeDirection),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[repr(u8)]
pub enum Direction {
Left = 0,
@ -279,10 +279,20 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
Rect { left: x, top: y, right: x.add(width), bottom: y.add(height) }
}
pub fn has_point(&self, x: T, y: T) -> bool {
/**
* Returns true if the point (x, y) is inside the rectangle (inclusive).
*/
pub fn has_point_incl(&self, x: T, y: T) -> bool {
self.left.ge(&x) && self.right.le(&x) && self.top.ge(&y) && self.bottom.le(&y)
}
/**
* Returns true if the point (x, y) is inside the rectangle (exclusive).
*/
pub fn has_point_excl(&self, x: T, y: T) -> bool {
self.left.le(&x) && self.right.gt(&x) && self.top.le(&y) && self.bottom.gt(&y)
}
pub fn width(&self) -> T {
if self.left.gt(&self.right) {
self.left.sub(self.right)

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

@ -8,7 +8,8 @@
"yes": "Yes",
"no": "No",
"on": "ON",
"off": "OFF"
"off": "OFF",
"choose_file": "No file selected…"
},
"menus": {
"main_menu": {
@ -60,6 +61,32 @@
"replay_last": "Replay Last",
"delete_replay": "Delete Best Replay"
},
"save_manage_menu": {
"import_export_save": "Import/Export Save...",
"import_format": "Import format",
"export_format": "Export format",
"action_type": {
"entry": "Action:",
"import": "Import",
"export": "Export"
},
"save_location": {
"import": "Import location",
"export": "Export location"
},
"save_format": {
"entry": "Save format:",
"freeware": "Freeware",
"plus": "Cave Story+ (PC)",
"switch": "Cave Story+ (Switch)",
"auto": "Auto-detect"
},
"file_filters": {
"freeware": "Cave Story save",
"plus": "Cave Story+ save",
"switch": "Cave Story+ save"
}
},
"options_menu": {
"graphics": "Graphics...",
"graphics_menu": {
@ -134,7 +161,13 @@
"auto": "Auto"
},
"discord_rpc": "Discord Rich Presence:",
"allow_strafe": "Allow strafe:"
"allow_strafe": "Allow strafe:",
"save_format": {
"entry": "Save format:",
"freeware": "Freeware",
"plus": "Cave Story+ (PC)",
"switch": "Cave Story+ (Switch)"
}
},
"links": "Links...",
"advanced": "Advanced...",

View file

@ -180,7 +180,7 @@ impl EditorInstance {
let (scale_x, scale_y) = batch.scale();
if let Some(tex) = batch.get_texture() {
let (width, height) = tex.dimensions();
let (width, height) = (width as f32 / scale_x, height as f32 / scale_y);
let (width, height) = (width as f32 * scale_x, height as f32 * scale_y);
if let Ok(tex_id) = graphics::imgui_texture_id(ctx, tex) {
Image::new(tex_id, [width, height]).build(ui);

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

@ -46,10 +46,6 @@ pub struct LaunchOptions {
/// Do not create a window and skip audio initialization.
pub server_mode: bool,
#[arg(long, hide = cfg!(not(feature = "editor")))]
/// Enable built-in editor.
pub editor: bool,
#[arg(long)]
/// Window height in pixels.
pub window_height: Option<u16>,
@ -73,7 +69,6 @@ impl Default for LaunchOptions {
fn default() -> Self {
Self {
server_mode: false,
editor: false,
window_height: None,
window_width: None,
window_fullscreen: false,

View file

@ -1,13 +1,15 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n254_helicopter(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n254_helicopter(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -84,7 +86,7 @@ impl NPC {
pub(crate) fn tick_n255_helicopter_blades(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -99,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;
}
@ -116,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;
@ -136,8 +138,7 @@ impl NPC {
pub(crate) fn tick_n260_shovel_brigade_caged(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -190,7 +191,7 @@ impl NPC {
pub(crate) fn tick_n261_chie_caged(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -231,7 +232,7 @@ impl NPC {
pub(crate) fn tick_n262_chaco_caged(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {

View file

@ -1,20 +1,18 @@
use num_traits::clamp;
use crate::common::{CDEG_RAD, Direction};
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::list::BorrowedNPC;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
impl NPC {
impl BorrowedNPC<'_> {
pub(crate) fn tick_n009_balrog_falling_in(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -81,8 +79,7 @@ impl NPC {
pub(crate) fn tick_n010_balrog_shooting(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -182,7 +179,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n011_balrogs_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n011_balrogs_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.flags.hit_anything() {
self.cond.set_alive(false);
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
@ -207,9 +204,7 @@ impl NPC {
pub(crate) fn tick_n012_balrog_cutscene(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { players, npc_list, stage, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -452,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);
@ -542,7 +537,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n019_balrog_bust_in(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n019_balrog_bust_in(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -619,7 +618,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n033_balrog_bouncing_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n033_balrog_bouncing_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.flags.hit_left_wall() || self.flags.hit_right_wall() {
self.cond.set_alive(false);
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
@ -656,8 +655,7 @@ impl NPC {
pub(crate) fn tick_n036_balrog_hover(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -819,8 +817,7 @@ impl NPC {
pub(crate) fn tick_n068_balrog_running(
&mut self,
state: &mut SharedGameState,
mut players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { mut players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1000,8 +997,7 @@ impl NPC {
pub(crate) fn tick_n169_balrog_shooting_missiles(
&mut self,
state: &mut SharedGameState,
mut players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { mut players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1217,8 +1213,7 @@ impl NPC {
pub(crate) fn tick_n170_balrog_missile(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
if (self.direction == Direction::Left && self.flags.hit_left_wall())
|| (self.direction == Direction::Right && self.flags.hit_right_wall())
@ -1279,7 +1274,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n306_balrog_nurse(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n306_balrog_nurse(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -1315,8 +1310,7 @@ impl NPC {
pub(crate) fn tick_n356_balrog_rescuing(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 11 => {

View file

@ -1,11 +1,11 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::NPC;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n113_professor_booster(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n113_professor_booster(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {

View file

@ -1,13 +1,16 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n093_chaco(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n093_chaco(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -23,9 +26,7 @@ impl NPC {
}
let player = self.get_closest_player_mut(players);
if (self.x - player.x).abs() < 0x4000
&& self.y - 0x4000 < player.y
&& self.y + 0x2000 > player.y {
if (self.x - player.x).abs() < 0x4000 && self.y - 0x4000 < player.y && self.y + 0x2000 > player.y {
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
}
}

View file

@ -2,14 +2,16 @@ use num_traits::{abs, clamp};
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n029_cthulhu(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n029_cthulhu(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 0;
@ -31,7 +33,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n052_sitting_blue_robot(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n052_sitting_blue_robot(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n052_sitting_blue_robot;
@ -40,7 +42,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n055_kazuma(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n055_kazuma(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -74,7 +76,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n061_king(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n061_king(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -246,7 +252,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n062_kazuma_computer(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n062_kazuma_computer(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -307,7 +313,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n074_jack(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n074_jack(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -359,9 +365,13 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n145_king_sword(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n145_king_sword(
&mut self,
state: &mut SharedGameState,
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 {
@ -387,7 +397,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n151_blue_robot_standing(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n151_blue_robot_standing(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -419,7 +429,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n167_booster_falling(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n167_booster_falling(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -470,7 +484,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n217_itoh(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n217_itoh(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -560,7 +574,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n278_little_family(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n278_little_family(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -633,7 +647,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n305_small_puppy(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n305_small_puppy(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x2000;
@ -651,11 +665,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n326_sue_itoh_human_transition(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
) -> GameResult {
pub(crate) fn tick_n326_sue_itoh_human_transition(&mut self, state: &mut SharedGameState, NPCContext { npc_list, .. }: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -763,7 +773,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n327_sneeze(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n327_sneeze(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
self.action_counter += 1;
if self.action_num == 0 {
@ -771,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

@ -3,19 +3,16 @@ use num_traits::{abs, clamp};
use crate::common::{Direction, Rect};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::{Player, TargetPlayer};
use crate::game::npc::{NPCContext, NPC};
use crate::game::player::{TargetPlayer};
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::bullet::BulletManager;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n117_curly(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -126,13 +123,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n118_curly_boss(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
bullet_manager: &BulletManager,
) -> GameResult {
pub(crate) fn tick_n118_curly_boss(&mut self, state: &mut SharedGameState, NPCContext { players, npc_list, bullet_manager, .. }: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -269,7 +260,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n123_curly_boss_bullet(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n123_curly_boss_bullet(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
@ -322,7 +313,7 @@ impl NPC {
pub(crate) fn tick_n165_curly_collapsed(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -357,8 +348,7 @@ impl NPC {
pub(crate) fn tick_n180_curly_ai(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_ref(&players);
@ -570,10 +560,9 @@ impl NPC {
pub(crate) fn tick_n181_curly_ai_machine_gun(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
bullet_manager: &mut BulletManager,
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;
@ -582,12 +571,12 @@ impl NPC {
} else {
self.x = parent.x
+ if parent.direction == Direction::Left {
self.direction = Direction::Left;
-0x1000
} else {
self.direction = Direction::Right;
0x1000
};
self.direction = Direction::Left;
-0x1000
} else {
self.direction = Direction::Right;
0x1000
};
self.y = parent.y;
self.anim_num = 0;
}
@ -664,10 +653,9 @@ impl NPC {
pub(crate) fn tick_n182_curly_ai_polar_star(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
bullet_manager: &mut BulletManager,
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;
@ -676,12 +664,12 @@ impl NPC {
} else {
self.x = parent.x
+ if parent.direction == Direction::Left {
self.direction = Direction::Left;
-0x1000
} else {
self.direction = Direction::Right;
0x1000
};
self.direction = Direction::Left;
-0x1000
} else {
self.direction = Direction::Right;
0x1000
};
self.y = parent.y;
self.anim_num = 0;
}
@ -757,9 +745,9 @@ impl NPC {
pub(crate) fn tick_n183_curly_air_tank_bubble(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
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;
@ -784,8 +772,7 @@ impl NPC {
pub(crate) fn tick_n259_curly_unconscious(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -835,9 +822,9 @@ impl NPC {
pub(crate) fn tick_n303_curly_machine_gun(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
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;
@ -863,8 +850,7 @@ impl NPC {
pub(crate) fn tick_n320_curly_carried(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = &players[0];
@ -913,11 +899,9 @@ impl NPC {
pub(crate) fn tick_n321_curly_nemesis(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
bullet_manager: &mut BulletManager,
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,14 +1,12 @@
use crate::common::{CDEG_RAD, Direction, Rect};
use crate::common::{Direction, Rect, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::list::BorrowedNPC;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n139_doctor(&mut self, state: &mut SharedGameState) -> GameResult {
impl BorrowedNPC<'_> {
pub(crate) fn tick_n139_doctor(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -119,7 +117,7 @@ impl NPC {
pub(crate) fn tick_n256_doctor_facing_away(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -208,7 +206,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n257_red_crystal(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n257_red_crystal(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
}
@ -263,8 +261,7 @@ impl NPC {
pub(crate) fn tick_n263_doctor_boss(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -486,8 +483,7 @@ impl NPC {
pub(crate) fn tick_n264_doctor_boss_red_projectile(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { npc_list, stage, .. }: NPCContext,
) -> GameResult {
if self.x < 0 || self.x > stage.map.width as i32 * state.tile_size.as_int() * 0x200 {
self.vanish(state);
@ -531,7 +527,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n265_doctor_boss_red_projectile_trail(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n265_doctor_boss_red_projectile_trail(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
self.anim_counter += 1;
if self.anim_counter > 3 {
self.anim_counter = 0;
@ -550,7 +550,7 @@ impl NPC {
pub(crate) fn tick_n266_doctor_boss_red_projectile_bouncing(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.flags.hit_left_wall() {
self.vel_x = -self.vel_x;
@ -599,8 +599,7 @@ impl NPC {
pub(crate) fn tick_n267_muscle_doctor(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 | 2 => {
@ -661,7 +660,10 @@ impl NPC {
if self.flags.hit_bottom_wall() {
if self.life + 20 >= self.action_counter3 {
self.animate(10, 1, 2);
} else if player.flags.hit_bottom_wall() && player.x > self.x - 0x6000 && player.x < self.x + 0x6000 && self.anim_num != 6
} else if player.flags.hit_bottom_wall()
&& player.x > self.x - 0x6000
&& player.x < self.x + 0x6000
&& self.anim_num != 6
{
self.anim_num = 6;
state.quake_counter = 10;
@ -919,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;
@ -1034,7 +1036,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n269_red_bat_bouncing(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n269_red_bat_bouncing(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.vel_x2 = self.vel_x;
@ -1068,7 +1070,7 @@ impl NPC {
pub(crate) fn tick_n270_doctor_red_energy(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.direction == Direction::Bottom || self.direction == Direction::Up {
self.vel_y += self.direction.vector_y() * 0x40;
@ -1091,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;
}
@ -1129,7 +1131,7 @@ impl NPC {
pub(crate) fn tick_n281_doctor_energy_form(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -1166,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

@ -1,17 +1,19 @@
use num_traits::{abs, clamp};
use crate::common::{CDEG_RAD, Direction, Rect};
use crate::common::{Direction, Rect, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::{Player, TargetPlayer};
use crate::game::npc::{NPCContext, NPC};
use crate::game::player::{TargetPlayer};
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::bullet::BulletManager;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n002_behemoth(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n002_behemoth(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.flags.hit_left_wall() {
self.direction = Direction::Right;
} else if self.flags.hit_right_wall() {
@ -97,7 +99,7 @@ impl NPC {
pub(crate) fn tick_n005_green_critter(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -181,7 +183,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n006_green_beetle(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n006_green_beetle(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -268,7 +270,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n007_basil(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n007_basil(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
let player = self.get_closest_player_mut(players);
@ -330,7 +336,7 @@ impl NPC {
pub(crate) fn tick_n008_blue_beetle(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -387,7 +393,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n025_lift(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n025_lift(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -475,8 +481,7 @@ impl NPC {
pub(crate) fn tick_n058_basu(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -593,7 +598,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n084_basu_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n084_basu_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.x += self.vel_x;
self.y += self.vel_y;
@ -621,8 +626,7 @@ impl NPC {
pub(crate) fn tick_n200_zombie_dragon(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num < 100 && self.life < 950 {
self.action_num = 100;
@ -721,14 +725,18 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n201_zombie_dragon_dead(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n201_zombie_dragon_dead(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
let dir_offset = if self.direction == Direction::Left { 0 } else { 1 };
self.anim_rect = state.constants.npc.n201_zombie_dragon_dead[dir_offset];
Ok(())
}
pub(crate) fn tick_n202_zombie_dragon_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n202_zombie_dragon_projectile(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
self.y += self.vel_y;
self.x += self.vel_x;
@ -747,7 +755,7 @@ impl NPC {
pub(crate) fn tick_n203_critter_destroyed_egg_corridor(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -847,8 +855,7 @@ impl NPC {
pub(crate) fn tick_n204_small_falling_spike(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -908,9 +915,7 @@ impl NPC {
pub(crate) fn tick_n205_large_falling_spike(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
bullet_manager: &mut BulletManager,
NPCContext { players, npc_list, bullet_manager, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1003,8 +1008,7 @@ impl NPC {
pub(crate) fn tick_n206_counter_bomb(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1104,7 +1108,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n207_counter_bomb_countdown(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n207_counter_bomb_countdown(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -1138,8 +1146,7 @@ impl NPC {
pub(crate) fn tick_n208_basu_destroyed_egg_corridor(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -1258,6 +1265,7 @@ impl NPC {
pub(crate) fn tick_n209_basu_projectile_destroyed_egg_corridor(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
self.x += self.vel_x;
self.y += self.vel_y;
@ -1286,7 +1294,7 @@ impl NPC {
pub(crate) fn tick_n210_beetle_destroyed_egg_corridor(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {

View file

@ -2,13 +2,16 @@ use num_traits::clamp;
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n059_eye_door(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n059_eye_door(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -72,7 +75,7 @@ impl NPC {
pub(crate) fn tick_n064_first_cave_critter(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -164,7 +167,7 @@ impl NPC {
pub(crate) fn tick_n065_first_cave_bat(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {

View file

@ -4,8 +4,8 @@ use num_traits::clamp;
use crate::common::{Direction, Rect, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::{NPC, NPCList};
use crate::game::player::Player;
use crate::game::npc::NPCContext;
use crate::game::npc::NPC;
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
@ -13,7 +13,7 @@ impl NPC {
pub(crate) fn tick_n024_power_critter(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -144,7 +144,7 @@ impl NPC {
pub(crate) fn tick_n026_bat_flying(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -153,13 +153,11 @@ impl NPC {
if self.action_num == 0 {
let angle = self.rng.range(0..0xff) as f64 * CDEG_RAD;
self.vel_x = (angle.cos() * 512.0) as i32;
self.target_x =
self.x + ((angle + 64.0 * CDEG_RAD).cos() * 8.0 * 512.0) as i32;
self.target_x = self.x + ((angle + 64.0 * CDEG_RAD).cos() * 8.0 * 512.0) as i32;
let angle = self.rng.range(0..0xff) as f64 * CDEG_RAD;
self.vel_y = (angle.sin() * 512.0) as i32;
self.target_y =
self.y + ((angle + 64.0 * CDEG_RAD).sin() * 8.0 * 512.0) as i32;
self.target_y = self.y + ((angle + 64.0 * CDEG_RAD).sin() * 8.0 * 512.0) as i32;
self.action_num = 1;
self.action_counter2 = 120;
@ -216,7 +214,7 @@ impl NPC {
pub(crate) fn tick_n028_flying_critter(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -349,7 +347,7 @@ impl NPC {
pub(crate) fn tick_n031_bat_hanging(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -434,7 +432,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n035_mannan(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n035_mannan(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num <= 2 && self.life < 90 {
self.action_num = 3;
self.action_counter = 0;
@ -489,7 +491,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n094_kulala(&mut self, state: &SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n094_kulala(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.anim_num = 4;
@ -596,7 +602,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n095_jelly(&mut self, state: &SharedGameState) -> GameResult {
pub(crate) fn tick_n095_jelly(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 | 10 => {
if self.action_num == 0 {
@ -685,7 +691,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n100_grate(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n100_grate(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.y += 0x2000;
self.action_num = 1;
@ -700,14 +706,14 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n101_malco_screen(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n101_malco_screen(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.animate(3, 0, 2);
self.anim_rect = state.constants.npc.n101_malco_screen[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n102_malco_computer_wave(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n102_malco_computer_wave(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y += 0x1000;
@ -719,7 +725,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n103_mannan_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n103_mannan_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
}
@ -746,7 +752,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n104_frog(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n104_frog(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
match self.action_num {
@ -833,7 +843,8 @@ impl NPC {
&& self.action_num != 3
&& self.action_counter > 10
&& ((self.shock > 0)
|| (abs(self.x - player.x) <= 0x14000 && abs(self.y - player.y) <= 0x8000) && self.rng.range(0..50) == 2)
|| (abs(self.x - player.x) <= 0x14000 && abs(self.y - player.y) <= 0x8000)
&& self.rng.range(0..50) == 2)
{
self.direction = if self.x >= player.x { Direction::Left } else { Direction::Right };
self.action_num = 10;
@ -857,7 +868,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n107_malco_broken(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n107_malco_broken(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -1022,8 +1037,7 @@ impl NPC {
pub(crate) fn tick_n109_malco_powered_on(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1086,7 +1100,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n110_puchi(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n110_puchi(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
match self.action_num {
@ -1174,7 +1192,8 @@ impl NPC {
&& self.action_num != 3
&& self.action_counter > 10
&& ((self.shock > 0)
|| (abs(self.x - player.x) <= 0x14000 && abs(self.y - player.y) <= 0x8000) && self.rng.range(0..50) == 2)
|| (abs(self.x - player.x) <= 0x14000 && abs(self.y - player.y) <= 0x8000)
&& self.rng.range(0..50) == 2)
{
self.direction = if self.x >= player.x { Direction::Left } else { Direction::Right };
self.action_num = 10;
@ -1200,8 +1219,7 @@ impl NPC {
pub(crate) fn tick_n115_ravil(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1215,9 +1233,9 @@ impl NPC {
let player = self.get_closest_player_mut(players);
if self.shock != 0
|| (player.x < self.x + 0xc000
&& player.x > self.x - 0xc000
&& player.y < self.y + 0x4000
&& player.y > self.y - 0xc000)
&& player.x > self.x - 0xc000
&& player.y < self.y + 0x4000
&& player.y > self.y - 0xc000)
{
self.action_num = 10;
}
@ -1334,7 +1352,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n192_scooter(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n192_scooter(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -1425,7 +1443,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n193_broken_scooter(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n193_broken_scooter(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.x += 0x3000;

View file

@ -1,15 +1,12 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n337_numahachi(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n337_numahachi(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x1000;
@ -36,7 +33,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n357_puppy_ghost(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n357_puppy_ghost(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.anim_rect = state.constants.npc.n357_puppy_ghost;
match self.action_num {
@ -75,7 +72,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n309_bute(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n309_bute(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
match self.action_num {
@ -136,7 +137,7 @@ impl NPC {
pub(crate) fn tick_n310_bute_sword(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -254,8 +255,7 @@ impl NPC {
pub(crate) fn tick_n311_bute_archer(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -265,7 +265,7 @@ impl NPC {
if (player.y > self.y - 0x14000 && player.y < self.y + 0x14000)
&& ((self.direction == Direction::Left && player.x > self.x - 0x28000 && player.x < self.x)
|| (self.direction != Direction::Left && player.x > self.x && player.x < self.x + 0x28000))
|| (self.direction != Direction::Left && player.x > self.x && player.x < self.x + 0x28000))
{
self.action_num = 10;
}
@ -356,7 +356,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n312_bute_arrow_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n312_bute_arrow_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.flags.hit_anything() && self.action_num > 0 && self.action_num < 20 {
self.action_num = 20;
}
@ -437,7 +437,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n316_bute_dead(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n316_bute_dead(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
// (nearly) same as Gaudi death
match self.action_num {
0 => {
@ -486,7 +486,7 @@ impl NPC {
pub(crate) fn tick_n323_bute_spinning(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 || self.action_num == 1 {
if self.action_num == 0 {
@ -511,9 +511,9 @@ impl NPC {
let player = self.get_closest_player_ref(&players);
if self.action_counter > 20
&& ((self.direction == Direction::Left && self.x <= player.x + 0x4000)
|| (self.direction == Direction::Up && self.y <= player.y + 0x4000)
|| (self.direction == Direction::Right && self.x >= player.x - 0x4000)
|| (self.direction == Direction::Bottom && self.y >= player.y - 0x4000))
|| (self.direction == Direction::Up && self.y <= player.y + 0x4000)
|| (self.direction == Direction::Right && self.x >= player.x - 0x4000)
|| (self.direction == Direction::Bottom && self.y >= player.y - 0x4000))
{
self.action_num = 10
}
@ -534,7 +534,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n324_bute_generator(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n324_bute_generator(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 10 {
self.action_num = 11;
self.action_counter = 0;
@ -563,8 +567,7 @@ impl NPC {
pub(crate) fn tick_n317_mesa(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_ref(&players);
@ -644,7 +647,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n318_mesa_dead(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n318_mesa_dead(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
// (nearly) same as Gaudi death
match self.action_num {
0 => {
@ -688,10 +691,14 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n319_mesa_block(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n319_mesa_block(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> 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();
@ -739,8 +746,7 @@ impl NPC {
pub(crate) fn tick_n322_deleet(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { npc_list, stage, .. }: NPCContext,
) -> GameResult {
if self.action_num < 2 && self.life <= 968 {
self.action_num = 2;
@ -844,7 +850,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n330_rolling(&mut self, state: &mut SharedGameState, npc_list: &NPCList, stage: &mut Stage) -> GameResult {
pub(crate) fn tick_n330_rolling(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, stage, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
let x = (self.x / (state.tile_size.as_int() * 0x200)) as usize;

View file

@ -1,13 +1,11 @@
use crate::common::{CDEG_RAD, Direction};
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n083_igor_cutscene(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n083_igor_cutscene(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -89,8 +87,7 @@ impl NPC {
pub(crate) fn tick_n088_igor_boss(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -273,8 +270,7 @@ impl NPC {
pub(crate) fn tick_n089_igor_dead(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -389,8 +385,7 @@ impl NPC {
pub(crate) fn tick_n268_igor_enemy(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_ref(&players);

View file

@ -1,12 +1,12 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::NPC;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n298_intro_doctor(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n298_intro_doctor(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -82,7 +82,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n299_intro_balrog_misery(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n299_intro_balrog_misery(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -101,18 +101,14 @@ impl NPC {
}
self.action_counter += 1;
self.y += if (self.action_counter / 50) % 2 != 0 {
0x40
} else {
-0x40
};
self.y += if (self.action_counter / 50) % 2 != 0 { 0x40 } else { -0x40 };
self.anim_rect = state.constants.npc.n299_intro_balrog_misery[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n300_intro_demon_crown(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n300_intro_demon_crown(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y += 0xc00;
@ -121,9 +117,12 @@ impl NPC {
self.anim_counter += 1;
if (self.anim_counter % 8) == 1 {
state.create_caret(self.x + state.effect_rng.range(-8..8) as i32 * 0x200,
self.y + 0x1000,
CaretType::LittleParticles, Direction::Up);
state.create_caret(
self.x + state.effect_rng.range(-8..8) as i32 * 0x200,
self.y + 0x1000,
CaretType::LittleParticles,
Direction::Up,
);
}
Ok(())

View file

@ -1,18 +1,15 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n241_critter_red(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -104,7 +101,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n242_bat_last_cave(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> GameResult {
pub(crate) fn tick_n242_bat_last_cave(
&mut self,
state: &mut SharedGameState,
NPCContext { stage, .. }: NPCContext,
) -> GameResult {
if self.x < 0 || self.x > stage.map.width as i32 * state.tile_size.as_int() * 0x200 {
self.vanish(state);
return Ok(());
@ -148,7 +149,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n243_bat_generator(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n243_bat_generator(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -176,7 +181,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n244_lava_drop(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n244_lava_drop(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
self.vel_y += 0x40;
// idfk why was that there in original code but I'll leave it there in case
@ -215,7 +224,7 @@ impl NPC {
pub(crate) fn tick_n245_lava_drop_generator(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -266,8 +275,7 @@ impl NPC {
pub(crate) fn tick_n276_red_demon(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 | 2 => {
@ -441,7 +449,7 @@ impl NPC {
pub(crate) fn tick_n277_red_demon_projectile(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;

View file

@ -1,18 +1,16 @@
use crate::common::{CDEG_RAD, Direction};
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
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,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -159,7 +157,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n148_critter_purple_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n148_critter_purple_projectile(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
if self.flags.hit_anything() {
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
self.cond.set_alive(false);
@ -183,7 +185,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n153_gaudi(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n153_gaudi(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
if !(self.x <= player.x + 0x28000
@ -332,7 +338,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n154_gaudi_dead(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n154_gaudi_dead(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.npc_flags.set_shootable(false);
@ -382,8 +388,7 @@ impl NPC {
pub(crate) fn tick_n155_gaudi_flying(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -489,7 +494,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n156_gaudi_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n156_gaudi_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_counter > 300 || (self.flags.0 & 0xff) != 0 {
self.cond.set_alive(false);
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
@ -508,8 +513,7 @@ impl NPC {
pub(crate) fn tick_n160_puu_black(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -536,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);
@ -638,7 +642,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n161_puu_black_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n161_puu_black_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.exp = 0;
self.vel_x = if self.x >= state.npc_super_pos.0 { self.vel_x - 64 } else { self.vel_x + 64 };
@ -665,7 +669,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n166_chaba(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n166_chaba(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -698,13 +702,12 @@ impl NPC {
pub(crate) fn tick_n162_puu_black_dead(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
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);
@ -776,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);
}
}
@ -795,7 +798,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n163_dr_gero(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n163_dr_gero(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -827,7 +830,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n164_nurse_hasumi(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n164_nurse_hasumi(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -859,7 +862,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n168_boulder(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n168_boulder(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -912,8 +915,7 @@ impl NPC {
pub(crate) fn tick_n171_fire_whirrr(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
self.face_player(player);
@ -977,7 +979,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n172_fire_whirrr_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n172_fire_whirrr_projectile(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
if self.action_num == 0 {
// pixel what?
self.action_num = 1;
@ -1001,8 +1007,7 @@ impl NPC {
pub(crate) fn tick_n173_gaudi_armored(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -1139,7 +1144,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n174_gaudi_armored_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n174_gaudi_armored_projectile(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.direction == Direction::Right {
@ -1203,7 +1212,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n175_gaudi_egg(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n175_gaudi_egg(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num < 3 && self.life < 90 {
self.cond.set_drs_novanish(true);
self.cond.set_explode_die(true);
@ -1233,8 +1242,7 @@ impl NPC {
pub(crate) fn tick_n176_buyo_buyo_base(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num < 3 && self.life < 940 {
self.cond.set_drs_novanish(true);
@ -1316,7 +1324,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n177_buyo_buyo(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n177_buyo_buyo(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
if self.flags.hit_anything() {
state.create_caret(self.x, self.y, CaretType::Shoot, Direction::Left);
self.cond.set_alive(false);
@ -1374,7 +1386,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n184_shutter(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n184_shutter(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -1429,7 +1445,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n185_small_shutter(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n185_small_shutter(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.anim_rect = state.constants.npc.n185_small_shutter;
@ -1461,7 +1477,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n186_lift_block(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n186_lift_block(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -1493,8 +1509,7 @@ impl NPC {
pub(crate) fn tick_n187_fuzz_core(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -1545,8 +1560,7 @@ impl NPC {
pub(crate) fn tick_n188_fuzz(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -1556,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;
@ -1594,7 +1608,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n304_gaudi_hospital(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n304_gaudi_hospital(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {

View file

@ -4,16 +4,14 @@ use num_traits::{abs, clamp};
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::{Player, TargetPlayer};
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::game::stage::Stage;
use crate::game::weapon::bullet::BulletManager;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n069_pignon(&mut self, state: &mut SharedGameState) -> GameResult {
impl BorrowedNPC<'_> {
pub(crate) fn tick_n069_pignon(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -96,7 +94,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n071_chinfish(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n071_chinfish(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.target_x = self.x;
@ -129,7 +127,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n075_kanpachi(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n075_kanpachi(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 0;
@ -154,7 +156,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n077_yamashita(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n077_yamashita(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 0;
@ -188,7 +190,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n079_mahin(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n079_mahin(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -237,7 +243,7 @@ impl NPC {
pub(crate) fn tick_n080_gravekeeper(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -332,7 +338,7 @@ impl NPC {
pub(crate) fn tick_n081_giant_pignon(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -420,7 +426,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n091_mimiga_cage(&mut self, state: &SharedGameState) -> GameResult {
pub(crate) fn tick_n091_mimiga_cage(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y += 0x2000;
@ -433,9 +439,7 @@ impl NPC {
pub(crate) fn tick_n313_ma_pignon(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
bullet_manager: &mut BulletManager,
NPCContext { players, npc_list, bullet_manager, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -674,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;
@ -716,9 +720,7 @@ impl NPC {
pub(crate) fn tick_n314_ma_pignon_rock(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &Stage,
NPCContext { players, npc_list, stage, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -783,8 +785,7 @@ impl NPC {
pub(crate) fn tick_n315_ma_pignon_clone(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
bullet_manager: &mut BulletManager,
NPCContext { players, bullet_manager, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);

View file

@ -1,19 +1,16 @@
use std::hint::unreachable_unchecked;
use crate::common::{Direction, Rect};
use crate::components::flash::Flash;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::{NPC, NPCLayer};
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::player::Player;
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::game::stage::Stage;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n000_null(&mut self) -> GameResult {
impl BorrowedNPC<'_> {
pub(crate) fn tick_n000_null(&mut self, _: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -30,7 +27,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n003_dead_enemy(&mut self) -> GameResult {
pub(crate) fn tick_n003_dead_enemy(&mut self, _: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num != 0xffff {
self.action_num = 0xffff;
self.action_counter2 = 0;
@ -48,7 +45,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n004_smoke(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n004_smoke(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = self.rng.range(0..4) as u16;
@ -91,14 +88,18 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n013_forcefield(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n013_forcefield(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.anim_num = (self.anim_num + 1) % 4;
self.anim_rect = state.constants.npc.n013_forcefield[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n014_key(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n014_key(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -138,7 +139,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n015_chest_closed(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n015_chest_closed(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if state.difficulty == GameDifficulty::Hard && state.constants.missile_flags.contains(&self.flag_num) {
self.cond.set_alive(false);
return Ok(());
@ -192,15 +197,19 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n016_save_point(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n016_save_point(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.npc_flags.set_interactable(true);
self.action_num = 1;
if self.direction == Direction::Right {
self.npc_flags.set_interactable(false);
self.vel_y = -0x200;
//Creates smoke
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
@ -232,10 +241,14 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n017_health_refill(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n017_health_refill(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
//Creates smoke when spawned in a shelter
if self.direction == Direction::Right {
self.vel_y = -0x200;
@ -315,7 +328,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n018_door(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n018_door(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => match self.direction {
Direction::Left => self.anim_rect = state.constants.npc.n018_door[0],
@ -344,7 +361,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n020_computer(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n020_computer(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.direction {
Direction::Left if self.anim_num == 0 => {
self.anim_num = 1;
@ -361,7 +378,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n021_chest_open(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n021_chest_open(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if state.difficulty == GameDifficulty::Hard && state.constants.missile_flags.contains(&self.flag_num) {
self.cond.set_alive(false);
return Ok(());
@ -380,7 +397,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n022_teleporter(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n022_teleporter(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.anim_num = 0;
@ -396,7 +413,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n023_teleporter_lights(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n023_teleporter_lights(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.anim_counter += 1;
if self.anim_counter > 1 {
self.anim_counter = 0;
@ -411,7 +428,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n027_death_trap(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n027_death_trap(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n027_death_trap;
@ -420,7 +437,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n030_gunsmith(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n030_gunsmith(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.direction == Direction::Left {
match self.action_num {
0 | 1 => {
@ -462,7 +479,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n032_life_capsule(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n032_life_capsule(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if state.difficulty == GameDifficulty::Hard {
self.cond.set_alive(false);
return Ok(());
@ -475,7 +492,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n034_bed(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n034_bed(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -489,7 +506,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n037_sign(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n037_sign(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.anim_counter = (self.anim_counter + 1) % 4;
self.anim_num = self.anim_counter / 2;
self.anim_rect = state.constants.npc.n037_sign[self.anim_num as usize];
@ -497,7 +514,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n038_fireplace(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n038_fireplace(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.anim_counter = (self.anim_counter + 1) % 16;
@ -526,7 +547,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n039_save_sign(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n039_save_sign(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -540,7 +561,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n041_busted_door(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n041_busted_door(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.layer = NPCLayer::Background;
@ -551,7 +572,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n043_chalkboard(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n043_chalkboard(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x2000;
@ -569,7 +590,7 @@ impl NPC {
pub(crate) fn tick_n046_hv_trigger(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
// Nicalis
if state.constants.is_cs_plus && self.tsc_direction != 0 {
@ -594,7 +615,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n070_sparkle(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n070_sparkle(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.anim_counter = (self.anim_counter + 1) % 16;
self.anim_num = self.anim_counter / 4;
self.anim_rect = state.constants.npc.n070_sparkle[self.anim_num as usize];
@ -605,8 +626,7 @@ impl NPC {
pub(crate) fn tick_n072_sprinkler(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
if self.direction == Direction::Left {
self.animate(1, 0, 2);
@ -641,7 +661,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n073_water_droplet(&mut self, state: &mut SharedGameState, stage: &Stage) -> GameResult {
pub(crate) fn tick_n073_water_droplet(
&mut self,
state: &mut SharedGameState,
NPCContext { stage, .. }: NPCContext,
) -> GameResult {
self.vel_y += 0x20;
self.anim_rect = state.constants.npc.n073_water_droplet[self.rng.range(0..4) as usize];
@ -659,9 +683,9 @@ impl NPC {
self.action_counter += 1;
if self.action_counter > 10
&& (self.flags.hit_left_wall()
|| self.flags.hit_right_wall()
|| self.flags.hit_bottom_wall()
|| self.flags.in_water())
|| self.flags.hit_right_wall()
|| self.flags.hit_bottom_wall()
|| self.flags.in_water())
{
// hit something
self.cond.set_alive(false);
@ -675,7 +699,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n076_flowers(&mut self) -> GameResult {
pub(crate) fn tick_n076_flowers(&mut self, _: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -688,7 +712,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n078_pot(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n078_pot(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -702,7 +726,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n085_terminal(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n085_terminal(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.anim_num = 0;
@ -729,7 +757,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n090_background(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n090_background(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n090_background;
@ -741,8 +769,7 @@ impl NPC {
pub(crate) fn tick_n096_fan_left(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -801,8 +828,7 @@ impl NPC {
pub(crate) fn tick_n097_fan_up(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -860,8 +886,7 @@ impl NPC {
pub(crate) fn tick_n098_fan_right(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -916,8 +941,7 @@ impl NPC {
pub(crate) fn tick_n099_fan_down(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -968,7 +992,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n105_hey_bubble_low(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n105_hey_bubble_low(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.action_counter += 1;
if self.action_counter < 5 {
@ -982,7 +1006,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n106_hey_bubble_high(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n106_hey_bubble_high(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -1000,8 +1028,7 @@ impl NPC {
pub(crate) fn tick_n114_press(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1072,7 +1099,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n116_red_petals(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n116_red_petals(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n116_red_petals;
@ -1081,7 +1108,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n119_table_chair(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n119_table_chair(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n119_table_chair;
@ -1090,7 +1117,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n125_hidden_item(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n125_hidden_item(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.life < 990 {
npc_list.create_death_smoke(self.x, self.y, self.display_bounds.right as usize, 8, state, &self.rng);
self.cond.set_alive(false);
@ -1128,7 +1159,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n137_large_door_frame(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n137_large_door_frame(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n137_large_door_frame;
@ -1140,8 +1171,7 @@ impl NPC {
pub(crate) fn tick_n146_lightning(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
flash: &mut Flash,
NPCContext { npc_list, flash, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1184,8 +1214,7 @@ impl NPC {
pub(crate) fn tick_n149_horizontal_moving_block(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -1313,7 +1342,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n152_shutter_stuck(&mut self) -> GameResult {
pub(crate) fn tick_n152_shutter_stuck(&mut self, _: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -1330,8 +1359,7 @@ impl NPC {
pub(crate) fn tick_n157_vertical_moving_block(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -1462,7 +1490,7 @@ impl NPC {
pub(crate) fn tick_n189_homing_flame(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_ref(&players);
@ -1503,7 +1531,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n190_broken_robot(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n190_broken_robot(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => self.anim_num = 0,
10 => {
@ -1531,7 +1563,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n191_water_level(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n191_water_level(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 10 => {
if self.action_num == 0 {
@ -1587,7 +1619,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n194_broken_blue_robot(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n194_broken_blue_robot(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y += 0x800;
@ -1598,7 +1630,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n195_background_grate(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n195_background_grate(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.layer = NPCLayer::Background;
self.action_num = 1;
@ -1608,7 +1640,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n199_wind_particles(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n199_wind_particles(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = self.rng.range(0..2) as u16;
@ -1636,7 +1668,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n211_small_spikes(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n211_small_spikes(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n211_small_spikes[self.event_num as usize % 4];
@ -1645,7 +1677,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n216_debug_cat(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n216_debug_cat(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n216_debug_cat;
@ -1654,7 +1686,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n219_smoke_generator(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n219_smoke_generator(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.direction != Direction::Left {
let mut npc = NPC::create(199, &state.npc_table);
npc.x = self.x + self.rng.range(-160..160) * 0x200;
@ -1677,7 +1713,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n222_prison_bars(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n222_prison_bars(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.y -= 0x1000;
@ -1688,7 +1724,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n227_bucket(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n227_bucket(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n227_bucket;
@ -1697,7 +1733,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n229_red_flowers_sprouts(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n229_red_flowers_sprouts(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x2000;
@ -1710,7 +1746,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n230_red_flowers_blooming(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n230_red_flowers_blooming(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.x -= 0x2000;
@ -1724,7 +1760,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n234_red_flowers_picked(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n234_red_flowers_picked(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y += 0x2000;
@ -1742,8 +1778,7 @@ impl NPC {
pub(crate) fn tick_n238_press_sideways(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_ref(&players);
@ -1838,7 +1873,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n239_cage_bars(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n239_cage_bars(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
if self.direction == Direction::Left {
@ -1861,8 +1896,7 @@ impl NPC {
pub(crate) fn tick_n246_press_proximity(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1950,7 +1984,7 @@ impl NPC {
pub(crate) fn tick_n253_experience_capsule(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -1972,7 +2006,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n258_mimiga_sleeping(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n258_mimiga_sleeping(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n258_mimiga_sleeping;
@ -1981,7 +2015,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n271_ironhead_block(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> GameResult {
pub(crate) fn tick_n271_ironhead_block(
&mut self,
state: &mut SharedGameState,
NPCContext { stage, .. }: NPCContext,
) -> GameResult {
if self.vel_x < 0 && self.x < -0x2000
|| self.vel_x > 0 && self.x > stage.map.width as i32 * state.tile_size.as_int() * 0x200 + 0x2000
{
@ -1996,7 +2034,7 @@ impl NPC {
// Big Block
self.anim_rect = Rect::new(0, 64, 32, 96);
self.display_bounds = Rect::new(0x2000, 0x2000, 0x2000, 0x2000);
self.hit_bounds = Rect::new(0x1800, 0x1800, 0x1800, 0x1800);
self.hit_bounds = HitExtents { left: 0x1800, top: 0x1800, right: 0x1800, bottom: 0x1800 };
} else {
// Small Blocks
let scale = state.tile_size.as_int() as u16;
@ -2028,7 +2066,7 @@ impl NPC {
pub(crate) fn tick_n272_ironhead_block_generator(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -2058,9 +2096,7 @@ impl NPC {
pub(crate) fn tick_n279_large_falling_block(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { players, npc_list, stage, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 10 | 11 => {
@ -2080,7 +2116,7 @@ impl NPC {
self.npc_flags.set_invulnerable(true);
self.anim_num = 1;
self.display_bounds = Rect::new(0x1000, 0x1000, 0x1000, 0x1000);
self.hit_bounds = Rect::new(0x1000, 0x1000, 0x1000, 0x1000);
self.hit_bounds = HitExtents { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
}
_ => (),
}
@ -2091,7 +2127,7 @@ impl NPC {
self.action_num = 11;
self.action_counter = 16;
}
self.action_counter = self.action_counter.saturating_sub(2);
if self.action_counter == 0 {
self.action_num = 100;
@ -2156,7 +2192,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n292_quake(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n292_quake(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
state.quake_counter = 10;
Ok(())
@ -2165,9 +2201,7 @@ impl NPC {
pub(crate) fn tick_n294_quake_falling_block_generator(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { players, npc_list, stage, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -2204,11 +2238,11 @@ impl NPC {
npc.cond.set_alive(true);
npc.x = self.x
+ if player.equip.has_booster_2_0() {
self.rng.range(-14..14)
} else {
self.rng.range(-11..11)
} * state.tile_size.as_int()
* 0x200;
self.rng.range(-14..14)
} else {
self.rng.range(-11..11)
} * state.tile_size.as_int()
* 0x200;
npc.y = player.y - 0x1C000;
npc.direction = if self.rng.range(0..10) & 1 != 0 { Direction::Left } else { Direction::Right };
@ -2223,7 +2257,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n295_cloud(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n295_cloud(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = self.tsc_direction % 4;
@ -2289,7 +2323,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n296_cloud_generator(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n296_cloud_generator(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
self.action_counter += 1;
if self.action_counter <= 16 {
return Ok(());
@ -2335,8 +2373,12 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n297_sue_dragon_mouth(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
if let Some(npc) = self.get_parent_ref_mut(npc_list) {
pub(crate) fn tick_n297_sue_dragon_mouth(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if let Some(npc) = self.get_parent(npc_list) {
self.x = npc.x + 0x2000;
self.y = npc.y + 0x1000;
}
@ -2349,9 +2391,7 @@ impl NPC {
pub(crate) fn tick_n302_camera_focus_marker(
&mut self,
state: &mut SharedGameState,
mut players: [&mut Player; 2],
npc_list: &NPCList,
boss: &mut BossNPC,
NPCContext { mut players, npc_list, boss, .. }: NPCContext,
) -> GameResult {
let player = &mut players[state.textscript_vm.executor_player.index()];
@ -2380,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 {
@ -2399,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;
}
@ -2410,7 +2458,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n328_human_transform_machine(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n328_human_transform_machine(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_rect = state.constants.npc.n328_human_transform_machine;
@ -2419,14 +2471,18 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n329_laboratory_fan(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n329_laboratory_fan(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.anim_counter = self.anim_counter.wrapping_add(1);
self.anim_rect = state.constants.npc.n329_laboratory_fan[(self.anim_counter as usize / 2) & 1];
Ok(())
}
pub(crate) fn tick_n334_sweat(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n334_sweat(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
match self.action_num {
@ -2458,7 +2514,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n349_statue(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n349_statue(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -2474,7 +2530,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n351_statue_shootable(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n351_statue_shootable(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -2518,7 +2578,7 @@ impl NPC {
pub(crate) fn tick_n352_ending_characters(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -2572,7 +2632,7 @@ impl NPC {
pub(crate) fn tick_n355_quote_and_curly_on_balrog(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
match (self.tsc_direction, self.direction) {
@ -2581,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;
}
@ -2590,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;
}
@ -2600,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;
}
@ -2610,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;
}
@ -2619,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;
}
@ -2628,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;
@ -2641,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;
}
@ -2662,7 +2722,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n358_misery_credits(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n358_misery_credits(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.animate(6, 0, 1);
} else if self.action_num == 10 {
@ -2677,8 +2737,7 @@ impl NPC {
pub(crate) fn tick_n359_water_droplet_generator(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
if (player.x - self.x).abs() < 0x28000
@ -2697,7 +2756,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n360_credits_thank_you(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n360_credits_thank_you(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.x -= 0x1000;

View file

@ -2,29 +2,35 @@ use std::hint::unreachable_unchecked;
use num_traits::clamp;
use crate::common::{CDEG_RAD, Direction};
use crate::components::flash::Flash;
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::list::{BorrowedNPC, NPCTokenProvider};
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n066_misery_bubble(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
impl BorrowedNPC<'_> {
pub(crate) fn tick_n066_misery_bubble(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
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;
let angle = f64::atan2((self.y - self.target_y) as f64, (self.x - self.target_x) as f64);
let angle = f64::atan2((self.y - self.target_y) as f64, (self.x - self.target_x) as f64);
self.vel_x = (angle.cos() * -1024.0) as i32;
self.vel_y = (angle.sin() * -1024.0) as i32;
}
@ -45,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);
}
}
@ -76,8 +83,7 @@ impl NPC {
pub(crate) fn tick_n067_misery_floating(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
flash: &mut Flash,
NPCContext { npc_list, flash, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -234,8 +240,7 @@ impl NPC {
pub(crate) fn tick_n082_misery_standing(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
flash: &mut Flash,
NPCContext { npc_list, flash, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -416,8 +421,7 @@ impl NPC {
pub(crate) fn tick_n247_misery_boss(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -614,7 +618,8 @@ impl NPC {
}
let player = self.get_closest_player_ref(&players);
self.action_num = if player.x >= self.x - 0xe000 && player.x <= self.x + 0xe000 { 100 } else { 160 };
self.action_num =
if player.x >= self.x - 0xe000 && player.x <= self.x + 0xe000 { 100 } else { 160 };
}
}
160 | 161 => {
@ -664,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);
@ -702,7 +707,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n248_misery_boss_vanishing(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n248_misery_boss_vanishing(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.flags.hit_anything() {
self.cond.set_alive(false);
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
@ -723,7 +728,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n249_misery_boss_appearing(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n249_misery_boss_appearing(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.action_counter2 += 1;
if self.action_counter2 > 8 {
self.cond.set_alive(false);
@ -743,8 +748,7 @@ impl NPC {
pub(crate) fn tick_n250_misery_boss_lightning_ball(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -807,7 +811,7 @@ impl NPC {
pub(crate) fn tick_n251_misery_boss_lightning(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -831,8 +835,7 @@ impl NPC {
pub(crate) fn tick_n252_misery_boss_bats(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -849,13 +852,13 @@ 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;
/ 4;
self.y = parent.y
+ self.action_counter as i32 * ((self.action_counter2 as f64 * CDEG_RAD).sin() * 512.0) as i32
/ 4;
/ 4;
if parent.action_num == 151 {
self.action_num = 10;
@ -912,14 +915,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n283_misery_possessed(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
boss: &mut BossNPC,
) -> GameResult {
pub(crate) fn tick_n283_misery_possessed(&mut self, state: &mut SharedGameState, NPCContext { players, npc_list, stage, boss, .. }: NPCContext) -> GameResult {
if self.action_num < 100 && (!boss.parts[0].cond.alive() || self.life < 400) {
self.action_num = 100;
}
@ -1147,12 +1143,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n289_critter_orange(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
stage: &mut Stage,
) -> GameResult {
pub(crate) fn tick_n289_critter_orange(&mut self, state: &mut SharedGameState, NPCContext { players, stage, .. }: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -1229,12 +1220,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n290_bat_misery(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
stage: &mut Stage,
) -> GameResult {
pub(crate) fn tick_n290_bat_misery(&mut self, state: &mut SharedGameState, NPCContext { players, stage, .. }: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -1293,7 +1279,7 @@ impl NPC {
pub(crate) fn tick_n301_misery_fish_missile(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {

View file

@ -2,9 +2,7 @@ use num_traits::abs;
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
@ -12,8 +10,7 @@ impl NPC {
pub(crate) fn tick_n212_sky_dragon(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -104,8 +101,7 @@ impl NPC {
pub(crate) fn tick_n213_night_spirit(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
// Nicalis
if state.constants.is_cs_plus && self.tsc_direction != 0 {
@ -215,7 +211,7 @@ impl NPC {
pub(crate) fn tick_n214_night_spirit_projectile(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -248,7 +244,7 @@ impl NPC {
pub(crate) fn tick_n215_sandcroc_outer_wall(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -331,7 +327,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n347_hoppy(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n347_hoppy(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {

View file

@ -1,13 +1,19 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::NPC;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::{BackgroundType, Stage};
use crate::game::stage::BackgroundType;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n001_experience(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> GameResult {
if stage.data.background_type == BackgroundType::Scrolling || stage.data.background_type == BackgroundType::OutsideWind {
pub(crate) fn tick_n001_experience(
&mut self,
state: &mut SharedGameState,
NPCContext { stage, .. }: NPCContext,
) -> GameResult {
if stage.data.background_type == BackgroundType::Scrolling
|| stage.data.background_type == BackgroundType::OutsideWind
{
if self.action_num == 0 {
self.action_num = 1;
@ -138,7 +144,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n086_missile_pickup(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> GameResult {
pub(crate) fn tick_n086_missile_pickup(
&mut self,
state: &mut SharedGameState,
NPCContext { stage, .. }: NPCContext,
) -> GameResult {
if self.direction == Direction::Left {
self.anim_counter += 1;
if self.anim_counter > 2 {
@ -152,7 +162,9 @@ impl NPC {
self.action_counter2 += 1;
}
if stage.data.background_type == BackgroundType::Scrolling || stage.data.background_type == BackgroundType::OutsideWind {
if stage.data.background_type == BackgroundType::Scrolling
|| stage.data.background_type == BackgroundType::OutsideWind
{
if self.action_num == 0 {
self.action_num = 1;
self.vel_x = self.rng.range(0x7f..0x100) as i32;
@ -206,7 +218,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n087_heart_pickup(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> GameResult {
pub(crate) fn tick_n087_heart_pickup(
&mut self,
state: &mut SharedGameState,
NPCContext { stage, .. }: NPCContext,
) -> GameResult {
if self.direction == Direction::Left {
self.anim_counter += 1;
if self.anim_counter > 2 {
@ -220,7 +236,9 @@ impl NPC {
self.action_counter2 += 1;
}
if stage.data.background_type == BackgroundType::Scrolling || stage.data.background_type == BackgroundType::OutsideWind {
if stage.data.background_type == BackgroundType::Scrolling
|| stage.data.background_type == BackgroundType::OutsideWind
{
if self.action_num == 0 {
self.action_num = 1;
self.vel_x = self.rng.range(0x7f..0x100) as i32;

View file

@ -1,14 +1,12 @@
use crate::common::{CDEG_RAD, Direction};
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n220_shovel_brigade(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n220_shovel_brigade(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -40,7 +38,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n221_shovel_brigade_walking(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n221_shovel_brigade_walking(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -117,7 +119,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n223_momorin(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n223_momorin(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -162,7 +168,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n224_chie(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n224_chie(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -203,7 +213,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n225_megane(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n225_megane(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 0;
@ -232,7 +242,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n226_kanpachi_plantation(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n226_kanpachi_plantation(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -285,7 +295,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n228_droll(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n228_droll(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -364,8 +378,7 @@ impl NPC {
pub(crate) fn tick_n231_rocket(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -476,7 +489,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n232_orangebell(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n232_orangebell(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -530,8 +547,7 @@ impl NPC {
pub(crate) fn tick_n233_orangebell_bat(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -544,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;
@ -606,7 +622,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n235_midorin(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n235_midorin(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -687,8 +703,7 @@ impl NPC {
pub(crate) fn tick_n236_gunfish(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -789,7 +804,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n237_gunfish_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n237_gunfish_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
}
@ -821,7 +836,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n240_mimiga_jailed(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n240_mimiga_jailed(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -893,7 +908,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n273_droll_projectile(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n273_droll_projectile(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
}
@ -935,8 +954,7 @@ impl NPC {
pub(crate) fn tick_n274_droll(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 | 2 => {
@ -1044,7 +1062,7 @@ impl NPC {
pub(crate) fn tick_n275_puppy_plantation(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1091,7 +1109,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n308_stumpy(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n308_stumpy(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
self.action_num = 1;

View file

@ -1,8 +1,6 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
@ -10,7 +8,7 @@ impl NPC {
pub fn tick_n111_quote_teleport_out(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -83,7 +81,7 @@ impl NPC {
pub fn tick_n112_quote_teleport_in(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -145,8 +143,7 @@ impl NPC {
pub(crate) fn tick_n150_quote(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -284,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;
@ -316,8 +313,7 @@ impl NPC {
pub(crate) fn tick_n370_second_quote(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
if !players[1].cond.alive() {
self.cond.set_alive(false);
@ -458,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

@ -1,17 +1,18 @@
use num_traits::{abs, clamp};
use crate::common::{CDEG_RAD, Direction};
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::bullet::BulletManager;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n044_polish(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n044_polish(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 | 2 => {
if self.action_num <= 1 {
@ -151,7 +152,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n045_baby(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n045_baby(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_num = 2;
self.vel_x = if self.rng.next_u16() & 1 != 0 {
@ -210,7 +211,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n047_sandcroc(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n047_sandcroc(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -291,10 +296,9 @@ impl NPC {
pub(crate) fn tick_n049_skullhead(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
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;
@ -386,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;
@ -407,7 +411,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n050_skeleton_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n050_skeleton_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 && self.direction == Direction::Right {
@ -478,8 +482,7 @@ impl NPC {
pub(crate) fn tick_n051_crow_and_skullhead(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -565,15 +568,19 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n053_skullstep_leg(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
let parent = self.get_parent_ref_mut(npc_list);
pub(crate) fn tick_n053_skullstep_leg(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
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;
@ -613,7 +620,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n054_skullstep(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n054_skullstep(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -696,7 +707,7 @@ impl NPC {
pub(crate) fn tick_n056_tan_beetle(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -764,7 +775,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n057_crow(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n057_crow(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -856,7 +871,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n120_colon_a(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n120_colon_a(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
let anim = if self.direction == Direction::Left { 0 } else { 1 };
self.anim_rect = state.constants.npc.n120_colon_a[anim];
@ -864,7 +879,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n121_colon_b(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n121_colon_b(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.direction != Direction::Left {
self.anim_rect = state.constants.npc.n121_colon_b[2];
@ -909,7 +924,7 @@ impl NPC {
pub(crate) fn tick_n122_colon_enraged(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1036,7 +1051,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n124_sunstone(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n124_sunstone(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -1084,7 +1099,7 @@ impl NPC {
pub(crate) fn tick_n126_puppy_running(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1180,7 +1195,7 @@ impl NPC {
pub(crate) fn tick_n130_puppy_sitting(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1248,7 +1263,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n131_puppy_sleeping(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n131_puppy_sleeping(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.action_counter += 1;
if self.action_counter > 100 {
self.action_counter = 0;
@ -1265,7 +1280,7 @@ impl NPC {
pub(crate) fn tick_n132_puppy_barking(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
match self.action_num {
@ -1368,7 +1383,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n133_jenka(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n133_jenka(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -1403,7 +1418,7 @@ impl NPC {
pub(crate) fn tick_n136_puppy_carried(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1453,8 +1468,7 @@ impl NPC {
pub(crate) fn tick_n134_armadillo(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
bullet_manager: &BulletManager,
NPCContext { players, bullet_manager, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -1527,8 +1541,7 @@ impl NPC {
pub(crate) fn tick_n135_skeleton(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -1626,7 +1639,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n138_large_door(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n138_large_door(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num != 1 {
if self.action_num > 1 {
if self.action_num == 10 {
@ -1681,7 +1694,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n143_jenka_collapsed(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n143_jenka_collapsed(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
let anim = if self.direction == Direction::Left { 0 } else { 1 };
self.anim_rect = state.constants.npc.n143_jenka_collapsed[anim];

View file

@ -2,13 +2,16 @@ use num_traits::abs;
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n040_santa(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n040_santa(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -67,7 +70,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n307_santa_caged(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n307_santa_caged(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {

View file

@ -1,19 +1,15 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::list::{BorrowedNPC, NPCTokenProvider};
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
impl NPC {
impl BorrowedNPC<'_> {
pub fn tick_n042_sue(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -117,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;
@ -225,7 +223,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n092_sue_at_pc(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n092_sue_at_pc(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -282,7 +280,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n280_sue_teleported(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n280_sue_teleported(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -335,10 +333,7 @@ impl NPC {
pub(crate) fn tick_n284_sue_possessed(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
boss: &mut BossNPC,
NPCContext { players, npc_list, stage, boss, .. }: NPCContext,
) -> GameResult {
if self.action_num < 100 && (!boss.parts[0].cond.alive() || self.life < 500) {
self.action_num = 100;
@ -378,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

@ -1,15 +1,16 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::bullet::BulletManager;
use crate::util::rng::RNG;
impl NPC {
pub(crate) fn tick_n060_toroko(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
pub(crate) fn tick_n060_toroko(
&mut self,
state: &mut SharedGameState,
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -168,7 +169,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n063_toroko_stick(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n063_toroko_stick(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -273,9 +274,7 @@ impl NPC {
pub(crate) fn tick_n140_toroko_frenzied(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
bullet_manager: &BulletManager,
NPCContext { players, npc_list, bullet_manager, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -587,8 +586,7 @@ impl NPC {
pub(crate) fn tick_n141_toroko_block_projectile(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -596,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);
@ -688,7 +686,7 @@ impl NPC {
pub(crate) fn tick_n142_flower_cub(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
10 | 11 => {
@ -755,7 +753,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n144_toroko_teleporting_in(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n144_toroko_teleporting_in(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {

View file

@ -1,10 +1,10 @@
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::game::npc::NPC;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
impl NPC {
pub(crate) fn tick_n127_machine_gun_trail_l2(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n127_machine_gun_trail_l2(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.anim_counter += 1;
if self.anim_counter > 0 {
self.anim_counter = 0;
@ -29,7 +29,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n128_machine_gun_trail_l3(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n128_machine_gun_trail_l3(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.anim_counter += 1;
if self.anim_counter > 0 {
self.anim_counter = 0;
@ -76,7 +76,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n129_fireball_snake_trail(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n129_fireball_snake_trail(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.anim_counter += 1;
if self.anim_counter > 1 {

View file

@ -1,18 +1,17 @@
use crate::common::{CDEG_RAD, Direction};
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
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,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -82,8 +81,7 @@ impl NPC {
pub(crate) fn tick_n362_curly_clone(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_ref(&players);
@ -231,7 +229,11 @@ impl NPC {
}
// Dead Curly Clone
pub(crate) fn tick_n363_dead_curly_clone(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n363_dead_curly_clone(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -287,7 +289,7 @@ impl NPC {
}
// Fast, machine gun-like bullets shot by Curly clone (NPC 362)
pub(crate) fn tick_n364_fast_bullet(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n364_fast_bullet(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
@ -336,8 +338,7 @@ impl NPC {
pub(crate) fn tick_n365_still_curly_clone(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_ref(&players);
@ -411,7 +412,7 @@ impl NPC {
pub(crate) fn tick_n366_zombie_curly_clone(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_ref(&players);
if self.x > player.x + 0x28000
@ -493,8 +494,7 @@ impl NPC {
pub(crate) fn tick_n367_curly_clone_incubator(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_ref(&players);
@ -525,8 +525,7 @@ impl NPC {
pub(crate) fn tick_n368_gclone(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -599,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,
@ -750,11 +749,10 @@ impl NPC {
pub(crate) fn tick_n369_gclone_curly_clone(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
// action_counter3 is used to keep track of grabbed player
let mut player = self.get_closest_player_mut(players);
let player = self.get_closest_player_mut(players);
match self.action_num {
0 | 1 | 10 => {

View file

@ -1,15 +1,16 @@
use crate::common::{CDEG_RAD, Direction, Rect};
use crate::common::{Direction, Rect, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::physics::HitExtents;
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
use super::BossNPCContext;
impl NPC {
pub(crate) fn tick_n108_balfrog_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n108_balfrog_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_counter > 300 || (self.flags.0 & 0xff) != 0 {
self.cond.set_alive(false);
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
@ -31,8 +32,7 @@ impl BossNPC {
pub(crate) fn tick_b02_balfrog(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
BossNPCContext { players, npc_list, .. }: BossNPCContext,
) {
match self.parts[0].action_num {
0 => {
@ -42,7 +42,8 @@ impl BossNPC {
self.parts[0].direction = Direction::Right;
self.parts[0].display_bounds =
Rect { left: 48 * 0x200, top: 48 * 0x200, right: 32 * 0x200, bottom: 0x2000 };
self.parts[0].hit_bounds = Rect { left: 24 * 0x200, top: 0x2000, right: 24 * 0x200, bottom: 0x2000 };
self.parts[0].hit_bounds =
HitExtents { left: 24 * 0x200, top: 0x2000, right: 24 * 0x200, bottom: 0x2000 };
self.parts[0].size = 3;
self.parts[0].exp = 1;
self.parts[0].event_num = 1000;
@ -478,12 +479,13 @@ impl BossNPC {
self.hurt_sound[1] = 52;
self.parts[1].size = 3;
self.parts[1].npc_flags.set_invulnerable(true);
self.parts[1].hit_bounds = Rect { left: 0x2000, top: 0x2000, right: 0x2000, bottom: 0x2000 };
self.parts[1].hit_bounds = HitExtents { left: 0x2000, top: 0x2000, right: 0x2000, bottom: 0x2000 };
self.hurt_sound[2] = 52;
self.parts[2].size = 3;
self.parts[2].npc_flags.set_invulnerable(true);
self.parts[2].hit_bounds = Rect { left: 24 * 0x200, top: 0x2000, right: 24 * 0x200, bottom: 0x2000 };
self.parts[2].hit_bounds =
HitExtents { left: 24 * 0x200, top: 0x2000, right: 24 * 0x200, bottom: 0x2000 };
}
1 => {
self.parts[1].x = self.parts[0].x + self.parts[0].direction.vector_x() * 24 * 0x200;

View file

@ -1,17 +1,21 @@
use crate::common::{CDEG_RAD, Direction, Rect};
use crate::components::flash::Flash;
use crate::common::{Direction, Rect, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::physics::HitExtents;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
use super::BossNPCContext;
impl NPC {
pub(crate) fn tick_n331_ballos_bone_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n331_ballos_bone_projectile(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
self.action_num = 1;
@ -53,7 +57,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n332_ballos_shockwave(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n332_ballos_shockwave(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -93,8 +101,7 @@ impl NPC {
pub(crate) fn tick_n333_ballos_lightning(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -128,7 +135,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n338_green_devil(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> GameResult {
pub(crate) fn tick_n338_green_devil(
&mut self,
state: &mut SharedGameState,
NPCContext { stage, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -171,7 +182,7 @@ impl NPC {
pub(crate) fn tick_n339_green_devil_generator(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -201,9 +212,7 @@ impl NPC {
pub(crate) fn tick_n340_ballos(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
flash: &mut Flash,
NPCContext { players, npc_list, flash, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -583,8 +592,12 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n341_ballos_1_head(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
if let Some(parent) = self.get_parent_ref_mut(npc_list) {
pub(crate) fn tick_n341_ballos_1_head(
&mut self,
state: &mut SharedGameState,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if let Some(parent) = self.get_parent(npc_list) {
if parent.action_num == 11 && parent.action_counter > 50 {
self.anim_counter += 1;
}
@ -609,9 +622,7 @@ impl NPC {
pub(crate) fn tick_n342_ballos_orbiting_eye(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
boss: &mut BossNPC,
NPCContext { players, npc_list, boss, .. }: NPCContext,
) -> GameResult {
if self.action_num < 1000 && boss.parts[0].action_num >= 1000 {
self.action_num = 1000;
@ -903,7 +914,7 @@ impl NPC {
pub(crate) fn tick_n343_ballos_3_cutscene(
&mut self,
state: &mut SharedGameState,
boss: &mut BossNPC,
NPCContext { boss, .. }: NPCContext,
) -> GameResult {
self.action_counter += 1;
if self.action_counter > 100 {
@ -918,7 +929,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n344_ballos_3_eyes(&mut self, state: &mut SharedGameState, boss: &mut BossNPC) -> GameResult {
pub(crate) fn tick_n344_ballos_3_eyes(
&mut self,
state: &mut SharedGameState,
NPCContext { boss, .. }: NPCContext,
) -> GameResult {
self.action_counter += 1;
if self.action_counter > 100 {
self.cond.set_alive(false);
@ -935,8 +950,7 @@ impl NPC {
pub(crate) fn tick_n345_ballos_skull_projectile(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { npc_list, stage, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 100 => {
@ -1004,9 +1018,7 @@ impl NPC {
pub(crate) fn tick_n346_ballos_orbiting_platform(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
stage: &mut Stage,
boss: &mut BossNPC,
NPCContext { players, stage, boss, .. }: NPCContext,
) -> GameResult {
if self.action_num < 1000 && boss.parts[0].action_num >= 1000 {
self.action_num = 1000;
@ -1138,7 +1150,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n348_ballos_4_spikes(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n348_ballos_4_spikes(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
self.action_num = 1;
@ -1164,9 +1176,7 @@ impl NPC {
pub(crate) fn tick_n350_flying_bute_archer(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { players, npc_list, stage, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -1287,7 +1297,7 @@ impl NPC {
pub(crate) fn tick_n353_bute_sword_flying(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
let player = self.get_closest_player_mut(players);
@ -1365,8 +1375,7 @@ impl NPC {
pub(crate) fn tick_n354_invisible_deathtrap_wall(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { npc_list, stage, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -1415,9 +1424,7 @@ impl BossNPC {
pub(crate) fn tick_b09_ballos(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
flash: &mut Flash,
BossNPCContext { players, npc_list, npc_token, flash, .. }: BossNPCContext,
) {
let player = self.parts[0].get_closest_player_mut(players);
@ -1430,7 +1437,7 @@ impl BossNPC {
self.parts[0].x = 0x28000;
self.parts[0].y = -0x8000;
self.hurt_sound[0] = 54;
self.parts[0].hit_bounds = Rect { left: 0x4000, top: 0x6000, right: 0x4000, bottom: 0x6000 };
self.parts[0].hit_bounds = HitExtents { left: 0x4000, top: 0x6000, right: 0x4000, bottom: 0x6000 };
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_solid_hard(true);
self.parts[0].npc_flags.set_event_when_killed(true);
@ -1445,7 +1452,7 @@ impl BossNPC {
self.parts[1].direction = Direction::Left;
self.parts[1].npc_flags.set_ignore_solidity(true);
self.parts[1].display_bounds = Rect { left: 0x1800, top: 0, right: 0x1800, bottom: 0x2000 };
self.parts[1].hit_bounds = Rect { left: 0x1800, top: 0, right: 0x1800, bottom: 0x2000 };
self.parts[1].hit_bounds = HitExtents { left: 0x1800, top: 0, right: 0x1800, bottom: 0x2000 };
self.parts[2] = self.parts[1].clone();
self.parts[2].direction = Direction::Right;
@ -1456,21 +1463,21 @@ impl BossNPC {
self.parts[3].npc_flags.set_invulnerable(true);
self.parts[3].npc_flags.set_ignore_solidity(true);
self.parts[3].display_bounds = Rect { left: 0x7800, top: 0x7800, right: 0x7800, bottom: 0x7800 };
self.parts[3].hit_bounds = Rect { left: 0x6000, top: 0x3000, right: 0x6000, bottom: 0x4000 };
self.parts[3].hit_bounds = HitExtents { left: 0x6000, top: 0x3000, right: 0x6000, bottom: 0x4000 };
self.parts[4].cond.set_alive(true);
self.parts[4].cond.set_damage_boss(true);
self.parts[4].npc_flags.set_solid_soft(true);
self.parts[4].npc_flags.set_invulnerable(true);
self.parts[4].npc_flags.set_ignore_solidity(true);
self.parts[4].hit_bounds = Rect { left: 0x4000, top: 0x1000, right: 0x4000, bottom: 0x1000 };
self.parts[4].hit_bounds = HitExtents { left: 0x4000, top: 0x1000, right: 0x4000, bottom: 0x1000 };
self.parts[5].cond.set_alive(true);
self.parts[5].cond.set_damage_boss(true);
self.parts[5].npc_flags.set_solid_hard(true);
self.parts[5].npc_flags.set_invulnerable(true);
self.parts[5].npc_flags.set_ignore_solidity(true);
self.parts[5].hit_bounds = Rect { left: 0x4000, top: 0, right: 0x4000, bottom: 0x6000 };
self.parts[5].hit_bounds = HitExtents { left: 0x4000, top: 0, right: 0x4000, bottom: 0x6000 };
}
100 | 101 => {
if self.parts[0].action_num == 100 {
@ -1763,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;
@ -1913,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

@ -1,16 +1,18 @@
use crate::common::{CDEG_RAD, Direction};
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::npc::{NPCContext, NPC};
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
use super::BossNPCContext;
impl NPC {
pub(crate) fn tick_n178_core_blade_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n178_core_blade_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.flags.hit_anything() {
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
self.cond.set_alive(false);
@ -37,7 +39,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n179_core_wisp_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n179_core_wisp_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.flags.hit_anything() {
state.create_caret(self.x, self.y, CaretType::ProjectileDissipation, Direction::Left);
self.cond.set_alive(false);
@ -66,7 +68,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n218_core_giant_ball(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n218_core_giant_ball(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.x += self.vel_x;
self.y += self.vel_y;
@ -86,14 +88,12 @@ impl BossNPC {
pub(crate) fn tick_b04_core(
&mut self,
state: &mut SharedGameState,
mut players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
BossNPCContext { mut players, npc_list, stage, .. }: BossNPCContext,
) {
let mut flag = false;
// i will refactor that one day
#[allow(mutable_transmutes)]
let flash_counter: &mut u16 = unsafe { std::mem::transmute(&self.parts[19].action_counter3) };
let flash_counter: &mut u16 = unsafe { std::mem::transmute(&self.parts[19].action_counter3) };
match self.parts[0].action_num {
0 => {
@ -180,7 +180,7 @@ impl BossNPC {
self.parts[7].x = self.parts[0].x - 0x6000;
self.parts[7].y = self.parts[0].y + 0x4000;
for i in [2,3,6,7] {
for i in [2, 3, 6, 7] {
self.hurt_sound[i] = 54;
}

View file

@ -1,17 +1,18 @@
use crate::common::{Direction, Rect};
use crate::framework::error::GameResult;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::npc::{NPCContext, NPC};
use crate::game::physics::HitExtents;
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
use super::BossNPCContext;
impl NPC {
pub(crate) fn tick_n325_heavy_press_lightning(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -52,7 +53,11 @@ impl NPC {
}
impl BossNPC {
pub(crate) fn tick_b08_heavy_press(&mut self, state: &mut SharedGameState, npc_list: &NPCList, stage: &mut Stage) {
pub(crate) fn tick_b08_heavy_press(
&mut self,
state: &mut SharedGameState,
BossNPCContext { npc_list, npc_token, stage, .. }: BossNPCContext,
) {
match self.parts[0].action_num {
0 => {
self.parts[0].action_num = 10;
@ -62,7 +67,7 @@ impl BossNPC {
self.parts[0].x = 0;
self.parts[0].y = 0;
self.parts[0].display_bounds = Rect { left: 0x5000, top: 0x7800, right: 0x5000, bottom: 0x7800 };
self.parts[0].hit_bounds = Rect { left: 0x6200, top: 0x7800, right: 0x5000, bottom: 0x6000 };
self.parts[0].hit_bounds = HitExtents { left: 0x6200, top: 0x7800, right: 0x5000, bottom: 0x6000 };
self.hurt_sound[0] = 54;
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_solid_hard(true);
@ -139,14 +144,14 @@ impl BossNPC {
self.parts[1].cond.set_alive(true);
self.parts[1].npc_flags.set_invulnerable(true);
self.parts[1].npc_flags.set_ignore_solidity(true);
self.parts[1].hit_bounds = Rect { left: 0x1C00, top: 0x1000, right: 0x1C00, bottom: 0x1000 };
self.parts[1].hit_bounds = HitExtents { left: 0x1C00, top: 0x1000, right: 0x1C00, bottom: 0x1000 };
self.parts[2] = self.parts[1].clone();
self.parts[3].cond.set_alive(true);
self.parts[3].cond.set_damage_boss(true);
self.parts[3].npc_flags.set_shootable(true);
self.parts[3].hit_bounds = Rect { left: 0xC00, top: 0x1000, right: 0xC00, bottom: 0x1000 };
self.parts[3].hit_bounds = HitExtents { left: 0xC00, top: 0x1000, right: 0xC00, bottom: 0x1000 };
let mut npc = NPC::create(325, &state.npc_table);
npc.cond.set_alive(true);
@ -162,7 +167,8 @@ impl BossNPC {
// This relies heavily on the map not being changed
// Need to calculate offset from the default starting location
for i in 0..5 {
let extra_smoke = if stage.change_tile(i + 8, self.parts[0].action_counter3 as usize, 0) { 3 } else { 0 };
let extra_smoke =
if stage.change_tile(i + 8, self.parts[0].action_counter3 as usize, 0) { 3 } else { 0 };
npc_list.create_death_smoke(
(i as i32 + 8) * 0x2000,
self.parts[0].action_counter3 as i32 * 0x2000,
@ -210,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

@ -1,14 +1,15 @@
use crate::common::{Direction, Rect};
use crate::framework::error::GameResult;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::physics::HitExtents;
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
use super::BossNPCContext;
impl NPC {
pub(crate) fn tick_n196_ironhead_wall(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n196_ironhead_wall(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
self.x -= 0xC00;
if self.x <= if !state.constants.is_switch { 0x26000 } else { 0x1E000 } {
self.x += if !state.constants.is_switch { 0x2C000 } else { 0x3B400 };
@ -21,7 +22,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n197_porcupine_fish(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n197_porcupine_fish(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 10 => {
if self.action_num == 0 {
@ -65,7 +66,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n198_ironhead_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n198_ironhead_projectile(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.action_counter += 1;
if self.action_counter > 20 {
@ -96,7 +97,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n335_ikachan(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n335_ikachan(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -145,8 +146,7 @@ impl NPC {
pub(crate) fn tick_n336_ikachan_generator(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
NPCContext { players, npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 => {
@ -177,8 +177,7 @@ impl BossNPC {
pub(crate) fn tick_b05_ironhead(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
BossNPCContext { players, npc_list, npc_token, .. }: BossNPCContext,
) {
match self.parts[0].action_num {
0 => {
@ -198,7 +197,7 @@ impl BossNPC {
self.parts[0].event_num = 1000;
self.parts[0].life = 400;
self.parts[0].display_bounds = Rect::new(0x5000, 0x1800, 0x3000, 0x1800);
self.parts[0].hit_bounds = Rect::new(0x2000, 0x1400, 0x2000, 0x1400);
self.parts[0].hit_bounds = HitExtents { left: 0x2000, top: 0x1400, right: 0x2000, bottom: 0x1400 };
}
100 | 101 => {
if self.parts[0].action_num == 100 {
@ -310,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

@ -1,13 +1,13 @@
use std::mem::{MaybeUninit, transmute};
use std::mem::{transmute, MaybeUninit};
use std::ops::Deref;
use crate::common::{Direction, interpolate_fix9_scale};
use crate::common::{interpolate_fix9_scale, Direction};
use crate::components::flash::Flash;
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;
@ -68,18 +68,17 @@ impl BossNPC {
}
}
impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Flash)> for BossNPC {
fn tick(
&mut self,
state: &mut SharedGameState,
(players, npc_list, stage, bullet_manager, flash): (
[&mut Player; 2],
&NPCList,
&mut Stage,
&BulletManager,
&mut Flash,
),
) -> GameResult {
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,
}
impl GameEntity<BossNPCContext<'_>> for BossNPC {
fn tick(&mut self, state: &mut SharedGameState, boss_ctx: BossNPCContext) -> GameResult {
if !self.parts[0].cond.alive() {
// Kind of hacky but fixes Monster X's damage popup being stuck on screen
self.parts[0].popup.tick(state, ())?;
@ -87,15 +86,15 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl
}
match self.boss_type {
1 => self.tick_b01_omega(state, players, npc_list, bullet_manager, flash),
2 => self.tick_b02_balfrog(state, players, npc_list),
3 => self.tick_b03_monster_x(state, players, npc_list, flash),
4 => self.tick_b04_core(state, players, npc_list, stage),
5 => self.tick_b05_ironhead(state, players, npc_list),
6 => self.tick_b06_sisters(state, players, npc_list, flash),
7 => self.tick_b07_undead_core(state, npc_list, stage, flash),
8 => self.tick_b08_heavy_press(state, npc_list, stage),
9 => self.tick_b09_ballos(state, players, npc_list, flash),
1 => self.tick_b01_omega(state, boss_ctx),
2 => self.tick_b02_balfrog(state, boss_ctx),
3 => self.tick_b03_monster_x(state, boss_ctx),
4 => self.tick_b04_core(state, boss_ctx),
5 => self.tick_b05_ironhead(state, boss_ctx),
6 => self.tick_b06_sisters(state, boss_ctx),
7 => self.tick_b07_undead_core(state, boss_ctx),
8 => self.tick_b08_heavy_press(state, boss_ctx),
9 => self.tick_b09_ballos(state, boss_ctx),
_ => {}
}

View file

@ -1,21 +1,23 @@
use num_traits::{abs, clamp};
use crate::common::{CDEG_RAD, Direction, Rect};
use crate::components::flash::Flash;
use crate::common::{Direction, Rect, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::npc::{NPCContext, NPC};
use crate::game::physics::HitExtents;
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
use super::BossNPCContext;
impl NPC {
pub(crate) fn tick_n158_fish_missile(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -39,7 +41,8 @@ impl NPC {
let player = self.get_closest_player_mut(players);
// Get angle between 0 and 2*PI
let direction = f64::atan2((self.y - player.y) as f64, (self.x - player.x) as f64) + std::f64::consts::PI;
let direction =
f64::atan2((self.y - player.y) as f64, (self.x - player.x) as f64) + std::f64::consts::PI;
if direction < radians {
if radians - direction < std::f64::consts::PI {
@ -76,7 +79,7 @@ impl NPC {
pub(crate) fn tick_n159_monster_x_defeated(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -137,9 +140,7 @@ impl BossNPC {
pub(crate) fn tick_b03_monster_x(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
flash: &mut Flash,
BossNPCContext { players, npc_list, npc_token, flash, .. }: BossNPCContext,
) {
match self.parts[0].action_num {
0 => {
@ -156,7 +157,7 @@ impl BossNPC {
self.parts[0].size = 3;
self.parts[0].event_num = 1000;
self.parts[0].hit_bounds =
Rect { left: 24 * 0x200, top: 24 * 0x200, right: 24 * 0x200, bottom: 24 * 0x200 };
HitExtents { left: 24 * 0x200, top: 24 * 0x200, right: 24 * 0x200, bottom: 24 * 0x200 };
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_event_when_killed(true);
self.parts[0].npc_flags.set_show_damage(true);
@ -178,7 +179,7 @@ impl BossNPC {
self.parts[3].target_x = 0;
self.parts[3].display_bounds = Rect { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
self.parts[3].hit_bounds =
Rect { left: 5 * 0x200, top: 5 * 0x200, right: 5 * 0x200, bottom: 5 * 0x200 };
HitExtents { left: 5 * 0x200, top: 5 * 0x200, right: 5 * 0x200, bottom: 5 * 0x200 };
self.parts[3].npc_flags.set_ignore_solidity(true);
self.parts[4] = self.parts[3].clone();
@ -203,7 +204,7 @@ impl BossNPC {
self.parts[7].anim_num = 0;
self.parts[7].display_bounds =
Rect { left: 52 * 0x200, top: 24 * 0x200, right: 52 * 0x200, bottom: 24 * 0x200 };
self.parts[7].hit_bounds = Rect { left: 0x1000, top: 24 * 0x200, right: 0x1000, bottom: 0x2000 };
self.parts[7].hit_bounds = HitExtents { left: 0x1000, top: 24 * 0x200, right: 0x1000, bottom: 0x2000 };
self.parts[7].npc_flags.set_ignore_solidity(true);
self.hurt_sound[7] = 52;
@ -215,7 +216,8 @@ impl BossNPC {
self.parts[9].direction = Direction::Up;
self.parts[9].display_bounds =
Rect { left: 36 * 0x200, top: 0x1000, right: 36 * 0x200, bottom: 24 * 0x200 };
self.parts[9].hit_bounds = Rect { left: 28 * 0x200, top: 0x1000, right: 28 * 0x200, bottom: 0x2000 };
self.parts[9].hit_bounds =
HitExtents { left: 28 * 0x200, top: 0x1000, right: 28 * 0x200, bottom: 0x2000 };
self.hurt_sound[9] = 52;
self.parts[9].npc_flags.set_rear_and_top_not_hurt(true);
self.parts[9].npc_flags.set_ignore_solidity(true);
@ -489,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

@ -1,17 +1,16 @@
use crate::common::{Direction, Rect};
use crate::components::flash::Flash;
use crate::framework::error::GameResult;
use crate::game::caret::CaretType;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::physics::HitExtents;
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::bullet::BulletManager;
use crate::util::rng::RNG;
use super::BossNPCContext;
impl NPC {
pub(crate) fn tick_n048_omega_projectiles(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n048_omega_projectiles(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if (self.flags.hit_left_wall() && self.vel_x < 0) || (self.flags.hit_right_wall() && self.vel_x > 0) {
self.vel_x = -self.vel_x;
} else if self.flags.hit_bottom_wall() {
@ -53,10 +52,7 @@ impl BossNPC {
pub(crate) fn tick_b01_omega(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
bullet_manager: &BulletManager,
flash: &mut Flash,
BossNPCContext { players, npc_list, npc_token, bullet_manager, flash, .. }: BossNPCContext,
) {
match self.parts[0].action_num {
0 => {
@ -74,7 +70,7 @@ impl BossNPC {
self.parts[0].target_y = self.parts[0].y;
self.parts[0].display_bounds =
Rect { left: 40 * 0x200, top: 40 * 0x200, right: 40 * 0x200, bottom: 0x2000 };
self.parts[0].hit_bounds = Rect { left: 0x1000, top: 24 * 0x200, right: 0x1000, bottom: 0x2000 };
self.parts[0].hit_bounds = HitExtents { left: 0x1000, top: 24 * 0x200, right: 0x1000, bottom: 0x2000 };
self.hurt_sound[0] = 52;
self.parts[1].cond.set_alive(true);
@ -94,7 +90,7 @@ impl BossNPC {
self.parts[3].x = self.parts[0].x + 0x2000;
self.parts[3].y = self.parts[0].y;
self.parts[3].display_bounds = Rect { left: 24 * 0x200, top: 0x2000, right: 0x2000, bottom: 0x2000 };
self.parts[3].hit_bounds = Rect { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
self.parts[3].hit_bounds = HitExtents { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
self.hurt_sound[3] = 52;
self.parts[4].cond.set_alive(true);
@ -421,7 +417,8 @@ impl BossNPC {
self.parts[5].action_num = 1;
self.parts[5].npc_flags.set_solid_soft(true);
self.parts[5].npc_flags.set_ignore_solidity(true);
self.parts[5].hit_bounds = Rect { left: 20 * 0x200, top: 36 * 0x200, right: 20 * 0x200, bottom: 0x2000 };
self.parts[5].hit_bounds =
HitExtents { left: 20 * 0x200, top: 36 * 0x200, right: 20 * 0x200, bottom: 0x2000 };
}
self.parts[5].x = self.parts[0].x;
@ -433,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

@ -1,19 +1,19 @@
use crate::common::{CDEG_RAD, Direction, Rect, SliceExt};
use crate::components::flash::Flash;
use crate::common::{Direction, Rect, SliceExt, CDEG_RAD};
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::HitExtents;
use crate::game::player::Player;
use crate::game::shared_game_state::SharedGameState;
use crate::util::rng::RNG;
use super::BossNPCContext;
impl BossNPC {
pub(crate) fn tick_b06_sisters(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
flash: &mut Flash,
BossNPCContext { players, npc_list, npc_token, flash, .. }: BossNPCContext,
) {
match self.parts[0].action_num {
0 => {
@ -24,7 +24,7 @@ impl BossNPC {
self.parts[0].x = 0x14000 + (state.constants.game.tile_offset_x * 0x2000);
self.parts[0].y = 0x10000;
self.parts[0].display_bounds = Rect::new(0x1000, 0x1000, 0x10000, 0x1000);
self.parts[0].hit_bounds = Rect::new(0x1000, 0x1000, 0x1000, 0x1000);
self.parts[0].hit_bounds = HitExtents { left: 0x1000, top: 0x1000, right: 0x1000, bottom: 0x1000 };
self.hurt_sound[0] = 54;
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_event_when_killed(true);
@ -37,7 +37,7 @@ impl BossNPC {
self.parts[0].target_y = 0x3D;
self.parts[2].display_bounds = Rect::new(0x2800, 0x2000, 0x2800, 0x2000);
self.parts[2].hit_bounds = Rect::new(0x1800, 0x1400, 0x1800, 0x1400);
self.parts[2].hit_bounds = HitExtents { left: 0x1800, top: 0x1400, right: 0x1800, bottom: 0x1400 };
self.parts[2].npc_flags.set_ignore_solidity(true);
self.parts[2].npc_flags.set_invulnerable(true);
self.parts[2].parent_id = 3;
@ -47,7 +47,7 @@ impl BossNPC {
self.parts[3].cond.set_alive(true);
self.parts[3].display_bounds = Rect::new(0x2800, 0x2800, 0x2800, 0x2800);
self.parts[3].hit_bounds = Rect::new(0x1800, 0x400, 0x1800, 0x2000);
self.parts[3].hit_bounds = HitExtents { left: 0x1800, top: 0x400, right: 0x1800, bottom: 0x2000 };
self.parts[3].npc_flags.set_ignore_solidity(true);
self.parts[3].parent_id = 0;
self.parts[3].damage = 10;
@ -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);
@ -261,7 +261,7 @@ impl BossNPC {
} else {
return;
};
match part.action_num {
0 => {
part.action_num = 1;
@ -316,14 +316,14 @@ impl BossNPC {
part.hit_bounds.left = 0x2000;
state.sound_manager.play_sfx(51);
npc_list.create_death_smoke(
part.x,
part.y,
part.display_bounds.right as usize,
4,
state,
&part.rng
&part.rng,
);
}
}

View file

@ -1,21 +1,21 @@
use std::hint::unreachable_unchecked;
use crate::common::{CDEG_RAD, Direction, Rect, SliceExt};
use crate::components::flash::Flash;
use crate::common::{Direction, Rect, SliceExt, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::player::Player;
use crate::game::npc::{NPCContext, NPC};
use crate::game::shared_game_state::SharedGameState;
use crate::game::stage::Stage;
use crate::util::rng::RNG;
use super::BossNPCContext;
impl NPC {
pub(crate) fn tick_n282_mini_undead_core_active(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
NPCContext { players, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 20;
@ -93,7 +93,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n291_mini_undead_core_inactive(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n291_mini_undead_core_inactive(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 20;
if self.direction == Direction::Right {
@ -110,7 +114,7 @@ impl NPC {
pub(crate) fn tick_n293_undead_core_energy_shot(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
NPCContext { npc_list, .. }: NPCContext,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
@ -143,8 +147,7 @@ impl NPC {
pub(crate) fn tick_n285_undead_core_spiral_projectile(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { npc_list, stage, .. }: NPCContext,
) -> GameResult {
if self.x < 0 || self.x > stage.map.width as i32 * state.tile_size.as_int() * 0x200 {
self.vanish(state);
@ -186,7 +189,11 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n286_undead_core_spiral_projectile_trail(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n286_undead_core_spiral_projectile_trail(
&mut self,
state: &mut SharedGameState,
_: NPCContext,
) -> GameResult {
self.anim_counter += 1;
if self.anim_counter > 0 {
self.anim_counter = 0;
@ -202,7 +209,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n287_orange_smoke(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n287_orange_smoke(&mut self, state: &mut SharedGameState, _: NPCContext) -> GameResult {
if self.action_num == 0 {
self.vel_x = self.rng.range(-4..4) * 0x200;
self.action_num = 1;
@ -231,9 +238,7 @@ impl NPC {
pub(crate) fn tick_n288_undead_core_exploding_rock(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
NPCContext { players, npc_list, stage, .. }: NPCContext,
) -> GameResult {
match self.action_num {
0 | 1 => {
@ -317,9 +322,7 @@ impl BossNPC {
pub(crate) fn tick_b07_undead_core(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
stage: &mut Stage,
flash: &mut Flash,
BossNPCContext { npc_list, npc_token, stage, flash, .. }: BossNPCContext,
) {
let mut v19 = false;
@ -686,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);
@ -751,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

@ -4,12 +4,12 @@ use std::io::Cursor;
use std::ops::Deref;
use std::rc::Rc;
use byteorder::{LE, ReadBytesExt};
use byteorder::{ReadBytesExt, LE};
use crate::bitfield;
use crate::common::{Condition, interpolate_fix9_scale, Rect};
use crate::common::Direction;
use crate::common::Flag;
use crate::common::{interpolate_fix9_scale, Condition, Rect};
use crate::components::flash::Flash;
use crate::components::number_popup::NumberPopup;
use crate::entity::GameEntity;
@ -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;
@ -25,6 +25,8 @@ use crate::game::stage::{Stage, StageTexturePaths};
use crate::game::weapon::bullet::BulletManager;
use crate::util::rng::Xoroshiro32PlusPlus;
use super::physics::HitExtents;
pub mod ai;
pub mod boss;
pub mod list;
@ -123,7 +125,7 @@ pub struct NPC {
pub anim_counter: u16,
pub anim_rect: Rect<u16>,
pub display_bounds: Rect<u32>,
pub hit_bounds: Rect<u32>,
pub hit_bounds: HitExtents,
pub rng: Xoroshiro32PlusPlus,
pub popup: NumberPopup,
pub splash: bool,
@ -157,7 +159,7 @@ impl NPC {
direction: Direction::Left,
tsc_direction: 0,
display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 },
hit_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 },
hit_bounds: HitExtents { left: 0, top: 0, right: 0, bottom: 0 },
parent_id: 0,
action_num: 0,
anim_num: 0,
@ -182,7 +184,7 @@ impl NPC {
layer: NPCLayer,
) -> GameResult {
if self.layer == layer {
self.draw(state, ctx, frame)?
self.npc_draw(state, ctx, frame)?
}
Ok(())
@ -229,395 +231,389 @@ impl NPC {
}
}
impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mut Flash, &mut BossNPC)> for NPC {
fn tick(
&mut self,
state: &mut SharedGameState,
(players, npc_list, stage, bullet_manager, flash, boss): (
[&mut Player; 2],
&NPCList,
&mut Stage,
&mut BulletManager,
&mut Flash,
&mut BossNPC,
),
) -> GameResult {
#[allow(unused_mut, unused_assignments)]
let mut npc_hook_ran = false;
pub struct NPCContext<'a> {
pub players: [&'a mut Player; 2],
pub npc_list: &'a NPCList,
pub stage: &'a mut Stage,
pub bullet_manager: &'a mut BulletManager,
pub flash: &'a mut Flash,
pub boss: &'a mut BossNPC,
}
impl GameEntity<NPCContext<'_>> for BorrowedNPC<'_> {
fn tick(&mut self, state: &mut SharedGameState, ctx: NPCContext) -> GameResult {
match self.npc_type {
_ if npc_hook_ran => Ok(()),
0 => self.tick_n000_null(),
1 => self.tick_n001_experience(state, stage),
2 => self.tick_n002_behemoth(state, npc_list),
3 => self.tick_n003_dead_enemy(),
4 => self.tick_n004_smoke(state),
5 => self.tick_n005_green_critter(state, players),
6 => self.tick_n006_green_beetle(state),
7 => self.tick_n007_basil(state, players),
8 => self.tick_n008_blue_beetle(state, players),
9 => self.tick_n009_balrog_falling_in(state, npc_list),
10 => self.tick_n010_balrog_shooting(state, players, npc_list),
11 => self.tick_n011_balrogs_projectile(state),
12 => self.tick_n012_balrog_cutscene(state, players, npc_list, stage),
13 => self.tick_n013_forcefield(state),
14 => self.tick_n014_key(state, npc_list),
15 => self.tick_n015_chest_closed(state, npc_list),
16 => self.tick_n016_save_point(state, npc_list),
17 => self.tick_n017_health_refill(state, npc_list),
18 => self.tick_n018_door(state, npc_list),
19 => self.tick_n019_balrog_bust_in(state, npc_list),
20 => self.tick_n020_computer(state),
21 => self.tick_n021_chest_open(state),
22 => self.tick_n022_teleporter(state),
23 => self.tick_n023_teleporter_lights(state),
24 => self.tick_n024_power_critter(state, players),
25 => self.tick_n025_lift(state),
26 => self.tick_n026_bat_flying(state, players),
27 => self.tick_n027_death_trap(state),
28 => self.tick_n028_flying_critter(state, players),
29 => self.tick_n029_cthulhu(state, players),
30 => self.tick_n030_gunsmith(state),
31 => self.tick_n031_bat_hanging(state, players),
32 => self.tick_n032_life_capsule(state),
33 => self.tick_n033_balrog_bouncing_projectile(state),
34 => self.tick_n034_bed(state),
35 => self.tick_n035_mannan(state, npc_list),
36 => self.tick_n036_balrog_hover(state, players, npc_list),
37 => self.tick_n037_sign(state),
38 => self.tick_n038_fireplace(state, npc_list),
39 => self.tick_n039_save_sign(state),
40 => self.tick_n040_santa(state, players),
41 => self.tick_n041_busted_door(state),
42 => self.tick_n042_sue(state, players, npc_list),
43 => self.tick_n043_chalkboard(state),
44 => self.tick_n044_polish(state, npc_list),
45 => self.tick_n045_baby(state),
46 => self.tick_n046_hv_trigger(state, players),
47 => self.tick_n047_sandcroc(state, players),
48 => self.tick_n048_omega_projectiles(state),
49 => self.tick_n049_skullhead(state, players, npc_list),
50 => self.tick_n050_skeleton_projectile(state),
51 => self.tick_n051_crow_and_skullhead(state, players, npc_list),
52 => self.tick_n052_sitting_blue_robot(state),
53 => self.tick_n053_skullstep_leg(state, npc_list),
54 => self.tick_n054_skullstep(state, npc_list),
55 => self.tick_n055_kazuma(state),
56 => self.tick_n056_tan_beetle(state, players),
57 => self.tick_n057_crow(state, players),
58 => self.tick_n058_basu(state, players, npc_list),
59 => self.tick_n059_eye_door(state, players),
60 => self.tick_n060_toroko(state, players),
61 => self.tick_n061_king(state, npc_list),
62 => self.tick_n062_kazuma_computer(state),
63 => self.tick_n063_toroko_stick(state),
64 => self.tick_n064_first_cave_critter(state, players),
65 => self.tick_n065_first_cave_bat(state, players),
66 => self.tick_n066_misery_bubble(state, npc_list),
67 => self.tick_n067_misery_floating(state, npc_list, flash),
68 => self.tick_n068_balrog_running(state, players, npc_list),
69 => self.tick_n069_pignon(state),
70 => self.tick_n070_sparkle(state),
71 => self.tick_n071_chinfish(state),
72 => self.tick_n072_sprinkler(state, players, npc_list),
73 => self.tick_n073_water_droplet(state, stage),
74 => self.tick_n074_jack(state),
75 => self.tick_n075_kanpachi(state, players),
76 => self.tick_n076_flowers(),
77 => self.tick_n077_yamashita(state),
78 => self.tick_n078_pot(state),
79 => self.tick_n079_mahin(state, players),
80 => self.tick_n080_gravekeeper(state, players),
81 => self.tick_n081_giant_pignon(state, players),
82 => self.tick_n082_misery_standing(state, npc_list, flash),
83 => self.tick_n083_igor_cutscene(state),
84 => self.tick_n084_basu_projectile(state),
85 => self.tick_n085_terminal(state, players),
86 => self.tick_n086_missile_pickup(state, stage),
87 => self.tick_n087_heart_pickup(state, stage),
88 => self.tick_n088_igor_boss(state, players, npc_list),
89 => self.tick_n089_igor_dead(state, players, npc_list),
90 => self.tick_n090_background(state),
91 => self.tick_n091_mimiga_cage(state),
92 => self.tick_n092_sue_at_pc(state),
93 => self.tick_n093_chaco(state, players),
94 => self.tick_n094_kulala(state, players),
95 => self.tick_n095_jelly(state),
96 => self.tick_n096_fan_left(state, players, npc_list),
97 => self.tick_n097_fan_up(state, players, npc_list),
98 => self.tick_n098_fan_right(state, players, npc_list),
99 => self.tick_n099_fan_down(state, players, npc_list),
100 => self.tick_n100_grate(state),
101 => self.tick_n101_malco_screen(state),
102 => self.tick_n102_malco_computer_wave(state),
103 => self.tick_n103_mannan_projectile(state),
104 => self.tick_n104_frog(state, players),
105 => self.tick_n105_hey_bubble_low(state),
106 => self.tick_n106_hey_bubble_high(state, npc_list),
107 => self.tick_n107_malco_broken(state, npc_list),
108 => self.tick_n108_balfrog_projectile(state),
109 => self.tick_n109_malco_powered_on(state, players, npc_list),
110 => self.tick_n110_puchi(state, players),
111 => self.tick_n111_quote_teleport_out(state, players),
112 => self.tick_n112_quote_teleport_in(state, players),
113 => self.tick_n113_professor_booster(state),
114 => self.tick_n114_press(state, players, npc_list),
115 => self.tick_n115_ravil(state, players, npc_list),
116 => self.tick_n116_red_petals(state),
117 => self.tick_n117_curly(state, players, npc_list),
118 => self.tick_n118_curly_boss(state, players, npc_list, bullet_manager),
119 => self.tick_n119_table_chair(state),
120 => self.tick_n120_colon_a(state),
121 => self.tick_n121_colon_b(state),
122 => self.tick_n122_colon_enraged(state, players),
123 => self.tick_n123_curly_boss_bullet(state),
124 => self.tick_n124_sunstone(state),
125 => self.tick_n125_hidden_item(state, npc_list),
126 => self.tick_n126_puppy_running(state, players),
127 => self.tick_n127_machine_gun_trail_l2(state),
128 => self.tick_n128_machine_gun_trail_l3(state),
129 => self.tick_n129_fireball_snake_trail(state),
130 => self.tick_n130_puppy_sitting(state, players),
131 => self.tick_n131_puppy_sleeping(state),
132 => self.tick_n132_puppy_barking(state, players),
133 => self.tick_n133_jenka(state),
134 => self.tick_n134_armadillo(state, players, bullet_manager),
135 => self.tick_n135_skeleton(state, players, npc_list),
136 => self.tick_n136_puppy_carried(state, players),
137 => self.tick_n137_large_door_frame(state),
138 => self.tick_n138_large_door(state),
139 => self.tick_n139_doctor(state),
140 => self.tick_n140_toroko_frenzied(state, players, npc_list, bullet_manager),
141 => self.tick_n141_toroko_block_projectile(state, players, npc_list),
142 => self.tick_n142_flower_cub(state, players),
143 => self.tick_n143_jenka_collapsed(state),
144 => self.tick_n144_toroko_teleporting_in(state),
145 => self.tick_n145_king_sword(state, npc_list),
146 => self.tick_n146_lightning(state, npc_list, flash),
147 => self.tick_n147_critter_purple(state, players, npc_list),
148 => self.tick_n148_critter_purple_projectile(state),
149 => self.tick_n149_horizontal_moving_block(state, players, npc_list),
150 => self.tick_n150_quote(state, players, npc_list),
151 => self.tick_n151_blue_robot_standing(state),
152 => self.tick_n152_shutter_stuck(),
153 => self.tick_n153_gaudi(state, players),
154 => self.tick_n154_gaudi_dead(state),
155 => self.tick_n155_gaudi_flying(state, players, npc_list),
156 => self.tick_n156_gaudi_projectile(state),
157 => self.tick_n157_vertical_moving_block(state, players, npc_list),
158 => self.tick_n158_fish_missile(state, players),
159 => self.tick_n159_monster_x_defeated(state, npc_list),
160 => self.tick_n160_puu_black(state, players, npc_list),
161 => self.tick_n161_puu_black_projectile(state),
162 => self.tick_n162_puu_black_dead(state, players, npc_list),
163 => self.tick_n163_dr_gero(state),
164 => self.tick_n164_nurse_hasumi(state),
165 => self.tick_n165_curly_collapsed(state, players),
166 => self.tick_n166_chaba(state),
167 => self.tick_n167_booster_falling(state, npc_list),
168 => self.tick_n168_boulder(state),
169 => self.tick_n169_balrog_shooting_missiles(state, players, npc_list),
170 => self.tick_n170_balrog_missile(state, players, npc_list),
171 => self.tick_n171_fire_whirrr(state, players, npc_list),
172 => self.tick_n172_fire_whirrr_projectile(state),
173 => self.tick_n173_gaudi_armored(state, players, npc_list),
174 => self.tick_n174_gaudi_armored_projectile(state),
175 => self.tick_n175_gaudi_egg(state),
176 => self.tick_n176_buyo_buyo_base(state, players, npc_list),
177 => self.tick_n177_buyo_buyo(state, players),
178 => self.tick_n178_core_blade_projectile(state),
179 => self.tick_n179_core_wisp_projectile(state),
180 => self.tick_n180_curly_ai(state, players, npc_list),
181 => self.tick_n181_curly_ai_machine_gun(state, npc_list, bullet_manager),
182 => self.tick_n182_curly_ai_polar_star(state, npc_list, bullet_manager),
183 => self.tick_n183_curly_air_tank_bubble(state, npc_list),
184 => self.tick_n184_shutter(state, npc_list),
185 => self.tick_n185_small_shutter(state),
186 => self.tick_n186_lift_block(state),
187 => self.tick_n187_fuzz_core(state, players, npc_list),
188 => self.tick_n188_fuzz(state, players, npc_list),
189 => self.tick_n189_homing_flame(state, players),
190 => self.tick_n190_broken_robot(state, npc_list),
191 => self.tick_n191_water_level(state),
192 => self.tick_n192_scooter(state),
193 => self.tick_n193_broken_scooter(state),
194 => self.tick_n194_broken_blue_robot(state),
195 => self.tick_n195_background_grate(state),
196 => self.tick_n196_ironhead_wall(state),
197 => self.tick_n197_porcupine_fish(state),
198 => self.tick_n198_ironhead_projectile(state),
199 => self.tick_n199_wind_particles(state),
200 => self.tick_n200_zombie_dragon(state, players, npc_list),
201 => self.tick_n201_zombie_dragon_dead(state),
202 => self.tick_n202_zombie_dragon_projectile(state),
203 => self.tick_n203_critter_destroyed_egg_corridor(state, players),
204 => self.tick_n204_small_falling_spike(state, players, npc_list),
205 => self.tick_n205_large_falling_spike(state, players, npc_list, bullet_manager),
206 => self.tick_n206_counter_bomb(state, players, npc_list),
207 => self.tick_n207_counter_bomb_countdown(state),
208 => self.tick_n208_basu_destroyed_egg_corridor(state, players, npc_list),
209 => self.tick_n209_basu_projectile_destroyed_egg_corridor(state),
210 => self.tick_n210_beetle_destroyed_egg_corridor(state, players),
211 => self.tick_n211_small_spikes(state),
212 => self.tick_n212_sky_dragon(state, players, npc_list),
213 => self.tick_n213_night_spirit(state, players, npc_list),
214 => self.tick_n214_night_spirit_projectile(state, npc_list),
215 => self.tick_n215_sandcroc_outer_wall(state, players),
216 => self.tick_n216_debug_cat(state),
217 => self.tick_n217_itoh(state),
218 => self.tick_n218_core_giant_ball(state),
219 => self.tick_n219_smoke_generator(state, npc_list),
220 => self.tick_n220_shovel_brigade(state),
221 => self.tick_n221_shovel_brigade_walking(state),
222 => self.tick_n222_prison_bars(state),
223 => self.tick_n223_momorin(state, players),
224 => self.tick_n224_chie(state, players),
225 => self.tick_n225_megane(state),
226 => self.tick_n226_kanpachi_plantation(state),
227 => self.tick_n227_bucket(state),
228 => self.tick_n228_droll(state, players),
229 => self.tick_n229_red_flowers_sprouts(state),
230 => self.tick_n230_red_flowers_blooming(state),
231 => self.tick_n231_rocket(state, players, npc_list),
232 => self.tick_n232_orangebell(state, npc_list),
233 => self.tick_n233_orangebell_bat(state, players, npc_list),
234 => self.tick_n234_red_flowers_picked(state),
235 => self.tick_n235_midorin(state),
236 => self.tick_n236_gunfish(state, players, npc_list),
237 => self.tick_n237_gunfish_projectile(state),
238 => self.tick_n238_press_sideways(state, players, npc_list),
239 => self.tick_n239_cage_bars(state),
240 => self.tick_n240_mimiga_jailed(state),
241 => self.tick_n241_critter_red(state, players),
242 => self.tick_n242_bat_last_cave(state, stage),
243 => self.tick_n243_bat_generator(state, npc_list),
244 => self.tick_n244_lava_drop(state, players),
245 => self.tick_n245_lava_drop_generator(state, npc_list),
246 => self.tick_n246_press_proximity(state, players, npc_list),
247 => self.tick_n247_misery_boss(state, players, npc_list),
248 => self.tick_n248_misery_boss_vanishing(state),
249 => self.tick_n249_misery_boss_appearing(state),
250 => self.tick_n250_misery_boss_lightning_ball(state, players, npc_list),
251 => self.tick_n251_misery_boss_lightning(state, npc_list),
252 => self.tick_n252_misery_boss_bats(state, players, npc_list),
253 => self.tick_n253_experience_capsule(state, npc_list),
254 => self.tick_n254_helicopter(state, npc_list),
255 => self.tick_n255_helicopter_blades(state, npc_list),
256 => self.tick_n256_doctor_facing_away(state, npc_list),
257 => self.tick_n257_red_crystal(state),
258 => self.tick_n258_mimiga_sleeping(state),
259 => self.tick_n259_curly_unconscious(state, players, npc_list),
260 => self.tick_n260_shovel_brigade_caged(state, players, npc_list),
261 => self.tick_n261_chie_caged(state, players),
262 => self.tick_n262_chaco_caged(state, players),
263 => self.tick_n263_doctor_boss(state, players, npc_list),
264 => self.tick_n264_doctor_boss_red_projectile(state, npc_list, stage),
265 => self.tick_n265_doctor_boss_red_projectile_trail(state),
266 => self.tick_n266_doctor_boss_red_projectile_bouncing(state, npc_list),
267 => self.tick_n267_muscle_doctor(state, players, npc_list),
268 => self.tick_n268_igor_enemy(state, players, npc_list),
269 => self.tick_n269_red_bat_bouncing(state),
270 => self.tick_n270_doctor_red_energy(state, npc_list),
271 => self.tick_n271_ironhead_block(state, stage),
272 => self.tick_n272_ironhead_block_generator(state, npc_list),
273 => self.tick_n273_droll_projectile(state, npc_list),
274 => self.tick_n274_droll(state, players, npc_list),
275 => self.tick_n275_puppy_plantation(state, players),
276 => self.tick_n276_red_demon(state, players, npc_list),
277 => self.tick_n277_red_demon_projectile(state, npc_list),
278 => self.tick_n278_little_family(state),
279 => self.tick_n279_large_falling_block(state, players, npc_list, stage),
280 => self.tick_n280_sue_teleported(state),
281 => self.tick_n281_doctor_energy_form(state, npc_list),
282 => self.tick_n282_mini_undead_core_active(state, players),
283 => self.tick_n283_misery_possessed(state, players, npc_list, stage, boss),
284 => self.tick_n284_sue_possessed(state, players, npc_list, stage, boss),
285 => self.tick_n285_undead_core_spiral_projectile(state, npc_list, stage),
286 => self.tick_n286_undead_core_spiral_projectile_trail(state),
287 => self.tick_n287_orange_smoke(state),
288 => self.tick_n288_undead_core_exploding_rock(state, players, npc_list, stage),
289 => self.tick_n289_critter_orange(state, players, stage),
290 => self.tick_n290_bat_misery(state, players, stage),
291 => self.tick_n291_mini_undead_core_inactive(state),
292 => self.tick_n292_quake(state),
293 => self.tick_n293_undead_core_energy_shot(state, npc_list),
294 => self.tick_n294_quake_falling_block_generator(state, players, npc_list, stage),
295 => self.tick_n295_cloud(state),
296 => self.tick_n296_cloud_generator(state, npc_list),
297 => self.tick_n297_sue_dragon_mouth(state, npc_list),
298 => self.tick_n298_intro_doctor(state),
299 => self.tick_n299_intro_balrog_misery(state),
300 => self.tick_n300_intro_demon_crown(state),
301 => self.tick_n301_misery_fish_missile(state, players),
302 => self.tick_n302_camera_focus_marker(state, players, npc_list, boss),
303 => self.tick_n303_curly_machine_gun(state, npc_list),
304 => self.tick_n304_gaudi_hospital(state),
305 => self.tick_n305_small_puppy(state),
306 => self.tick_n306_balrog_nurse(state),
307 => self.tick_n307_santa_caged(state),
308 => self.tick_n308_stumpy(state, players),
309 => self.tick_n309_bute(state, players),
310 => self.tick_n310_bute_sword(state, players),
311 => self.tick_n311_bute_archer(state, players, npc_list),
312 => self.tick_n312_bute_arrow_projectile(state),
313 => self.tick_n313_ma_pignon(state, players, npc_list, bullet_manager),
314 => self.tick_n314_ma_pignon_rock(state, players, npc_list, stage),
315 => self.tick_n315_ma_pignon_clone(state, players, bullet_manager),
316 => self.tick_n316_bute_dead(state),
317 => self.tick_n317_mesa(state, players, npc_list),
318 => self.tick_n318_mesa_dead(state),
319 => self.tick_n319_mesa_block(state, npc_list),
320 => self.tick_n320_curly_carried(state, players, npc_list),
321 => self.tick_n321_curly_nemesis(state, players, npc_list, bullet_manager),
322 => self.tick_n322_deleet(state, npc_list, stage),
323 => self.tick_n323_bute_spinning(state, players),
324 => self.tick_n324_bute_generator(state, npc_list),
325 => self.tick_n325_heavy_press_lightning(state, npc_list),
326 => self.tick_n326_sue_itoh_human_transition(state, npc_list),
327 => self.tick_n327_sneeze(state, npc_list),
328 => self.tick_n328_human_transform_machine(state),
329 => self.tick_n329_laboratory_fan(state),
330 => self.tick_n330_rolling(state, npc_list, stage),
331 => self.tick_n331_ballos_bone_projectile(state),
332 => self.tick_n332_ballos_shockwave(state, npc_list),
333 => self.tick_n333_ballos_lightning(state, players, npc_list),
334 => self.tick_n334_sweat(state, players),
335 => self.tick_n335_ikachan(state),
336 => self.tick_n336_ikachan_generator(state, players, npc_list),
337 => self.tick_n337_numahachi(state),
338 => self.tick_n338_green_devil(state, stage),
339 => self.tick_n339_green_devil_generator(state, npc_list),
340 => self.tick_n340_ballos(state, players, npc_list, flash),
341 => self.tick_n341_ballos_1_head(state, npc_list),
342 => self.tick_n342_ballos_orbiting_eye(state, players, npc_list, boss),
343 => self.tick_n343_ballos_3_cutscene(state, boss),
344 => self.tick_n344_ballos_3_eyes(state, boss),
345 => self.tick_n345_ballos_skull_projectile(state, npc_list, stage),
346 => self.tick_n346_ballos_orbiting_platform(state, players, stage, boss),
347 => self.tick_n347_hoppy(state, players),
348 => self.tick_n348_ballos_4_spikes(state),
349 => self.tick_n349_statue(state),
350 => self.tick_n350_flying_bute_archer(state, players, npc_list, stage),
351 => self.tick_n351_statue_shootable(state, npc_list),
352 => self.tick_n352_ending_characters(state, npc_list),
353 => self.tick_n353_bute_sword_flying(state, players),
354 => self.tick_n354_invisible_deathtrap_wall(state, npc_list, stage),
355 => self.tick_n355_quote_and_curly_on_balrog(state, npc_list),
356 => self.tick_n356_balrog_rescuing(state, players, npc_list),
357 => self.tick_n357_puppy_ghost(state),
358 => self.tick_n358_misery_credits(state),
359 => self.tick_n359_water_droplet_generator(state, players, npc_list),
360 => self.tick_n360_credits_thank_you(state),
361 => self.tick_n361_flying_gaudi(state, players),
362 => self.tick_n362_curly_clone(state, players, npc_list),
363 => self.tick_n363_dead_curly_clone(state, npc_list),
364 => self.tick_n364_fast_bullet(state),
365 => self.tick_n365_still_curly_clone(state, players, npc_list),
366 => self.tick_n366_zombie_curly_clone(state, players),
367 => self.tick_n367_curly_clone_incubator(state, players, npc_list),
368 => self.tick_n368_gclone(state, players, npc_list),
369 => self.tick_n369_gclone_curly_clone(state, players, npc_list),
370 => self.tick_n370_second_quote(state, players, npc_list),
0 => self.tick_n000_null(state, ctx),
1 => self.tick_n001_experience(state, ctx),
2 => self.tick_n002_behemoth(state, ctx),
3 => self.tick_n003_dead_enemy(state, ctx),
4 => self.tick_n004_smoke(state, ctx),
5 => self.tick_n005_green_critter(state, ctx),
6 => self.tick_n006_green_beetle(state, ctx),
7 => self.tick_n007_basil(state, ctx),
8 => self.tick_n008_blue_beetle(state, ctx),
9 => self.tick_n009_balrog_falling_in(state, ctx),
10 => self.tick_n010_balrog_shooting(state, ctx),
11 => self.tick_n011_balrogs_projectile(state, ctx),
12 => self.tick_n012_balrog_cutscene(state, ctx),
13 => self.tick_n013_forcefield(state, ctx),
14 => self.tick_n014_key(state, ctx),
15 => self.tick_n015_chest_closed(state, ctx),
16 => self.tick_n016_save_point(state, ctx),
17 => self.tick_n017_health_refill(state, ctx),
18 => self.tick_n018_door(state, ctx),
19 => self.tick_n019_balrog_bust_in(state, ctx),
20 => self.tick_n020_computer(state, ctx),
21 => self.tick_n021_chest_open(state, ctx),
22 => self.tick_n022_teleporter(state, ctx),
23 => self.tick_n023_teleporter_lights(state, ctx),
24 => self.tick_n024_power_critter(state, ctx),
25 => self.tick_n025_lift(state, ctx),
26 => self.tick_n026_bat_flying(state, ctx),
27 => self.tick_n027_death_trap(state, ctx),
28 => self.tick_n028_flying_critter(state, ctx),
29 => self.tick_n029_cthulhu(state, ctx),
30 => self.tick_n030_gunsmith(state, ctx),
31 => self.tick_n031_bat_hanging(state, ctx),
32 => self.tick_n032_life_capsule(state, ctx),
33 => self.tick_n033_balrog_bouncing_projectile(state, ctx),
34 => self.tick_n034_bed(state, ctx),
35 => self.tick_n035_mannan(state, ctx),
36 => self.tick_n036_balrog_hover(state, ctx),
37 => self.tick_n037_sign(state, ctx),
38 => self.tick_n038_fireplace(state, ctx),
39 => self.tick_n039_save_sign(state, ctx),
40 => self.tick_n040_santa(state, ctx),
41 => self.tick_n041_busted_door(state, ctx),
42 => self.tick_n042_sue(state, ctx),
43 => self.tick_n043_chalkboard(state, ctx),
44 => self.tick_n044_polish(state, ctx),
45 => self.tick_n045_baby(state, ctx),
46 => self.tick_n046_hv_trigger(state, ctx),
47 => self.tick_n047_sandcroc(state, ctx),
48 => self.tick_n048_omega_projectiles(state, ctx),
49 => self.tick_n049_skullhead(state, ctx),
50 => self.tick_n050_skeleton_projectile(state, ctx),
51 => self.tick_n051_crow_and_skullhead(state, ctx),
52 => self.tick_n052_sitting_blue_robot(state, ctx),
53 => self.tick_n053_skullstep_leg(state, ctx),
54 => self.tick_n054_skullstep(state, ctx),
55 => self.tick_n055_kazuma(state, ctx),
56 => self.tick_n056_tan_beetle(state, ctx),
57 => self.tick_n057_crow(state, ctx),
58 => self.tick_n058_basu(state, ctx),
59 => self.tick_n059_eye_door(state, ctx),
60 => self.tick_n060_toroko(state, ctx),
61 => self.tick_n061_king(state, ctx),
62 => self.tick_n062_kazuma_computer(state, ctx),
63 => self.tick_n063_toroko_stick(state, ctx),
64 => self.tick_n064_first_cave_critter(state, ctx),
65 => self.tick_n065_first_cave_bat(state, ctx),
66 => self.tick_n066_misery_bubble(state, ctx),
67 => self.tick_n067_misery_floating(state, ctx),
68 => self.tick_n068_balrog_running(state, ctx),
69 => self.tick_n069_pignon(state, ctx),
70 => self.tick_n070_sparkle(state, ctx),
71 => self.tick_n071_chinfish(state, ctx),
72 => self.tick_n072_sprinkler(state, ctx),
73 => self.tick_n073_water_droplet(state, ctx),
74 => self.tick_n074_jack(state, ctx),
75 => self.tick_n075_kanpachi(state, ctx),
76 => self.tick_n076_flowers(state, ctx),
77 => self.tick_n077_yamashita(state, ctx),
78 => self.tick_n078_pot(state, ctx),
79 => self.tick_n079_mahin(state, ctx),
80 => self.tick_n080_gravekeeper(state, ctx),
81 => self.tick_n081_giant_pignon(state, ctx),
82 => self.tick_n082_misery_standing(state, ctx),
83 => self.tick_n083_igor_cutscene(state, ctx),
84 => self.tick_n084_basu_projectile(state, ctx),
85 => self.tick_n085_terminal(state, ctx),
86 => self.tick_n086_missile_pickup(state, ctx),
87 => self.tick_n087_heart_pickup(state, ctx),
88 => self.tick_n088_igor_boss(state, ctx),
89 => self.tick_n089_igor_dead(state, ctx),
90 => self.tick_n090_background(state, ctx),
91 => self.tick_n091_mimiga_cage(state, ctx),
92 => self.tick_n092_sue_at_pc(state, ctx),
93 => self.tick_n093_chaco(state, ctx),
94 => self.tick_n094_kulala(state, ctx),
95 => self.tick_n095_jelly(state, ctx),
96 => self.tick_n096_fan_left(state, ctx),
97 => self.tick_n097_fan_up(state, ctx),
98 => self.tick_n098_fan_right(state, ctx),
99 => self.tick_n099_fan_down(state, ctx),
100 => self.tick_n100_grate(state, ctx),
101 => self.tick_n101_malco_screen(state, ctx),
102 => self.tick_n102_malco_computer_wave(state, ctx),
103 => self.tick_n103_mannan_projectile(state, ctx),
104 => self.tick_n104_frog(state, ctx),
105 => self.tick_n105_hey_bubble_low(state, ctx),
106 => self.tick_n106_hey_bubble_high(state, ctx),
107 => self.tick_n107_malco_broken(state, ctx),
108 => self.tick_n108_balfrog_projectile(state, ctx),
109 => self.tick_n109_malco_powered_on(state, ctx),
110 => self.tick_n110_puchi(state, ctx),
111 => self.tick_n111_quote_teleport_out(state, ctx),
112 => self.tick_n112_quote_teleport_in(state, ctx),
113 => self.tick_n113_professor_booster(state, ctx),
114 => self.tick_n114_press(state, ctx),
115 => self.tick_n115_ravil(state, ctx),
116 => self.tick_n116_red_petals(state, ctx),
117 => self.tick_n117_curly(state, ctx),
118 => self.tick_n118_curly_boss(state, ctx),
119 => self.tick_n119_table_chair(state, ctx),
120 => self.tick_n120_colon_a(state, ctx),
121 => self.tick_n121_colon_b(state, ctx),
122 => self.tick_n122_colon_enraged(state, ctx),
123 => self.tick_n123_curly_boss_bullet(state, ctx),
124 => self.tick_n124_sunstone(state, ctx),
125 => self.tick_n125_hidden_item(state, ctx),
126 => self.tick_n126_puppy_running(state, ctx),
127 => self.tick_n127_machine_gun_trail_l2(state, ctx),
128 => self.tick_n128_machine_gun_trail_l3(state, ctx),
129 => self.tick_n129_fireball_snake_trail(state, ctx),
130 => self.tick_n130_puppy_sitting(state, ctx),
131 => self.tick_n131_puppy_sleeping(state, ctx),
132 => self.tick_n132_puppy_barking(state, ctx),
133 => self.tick_n133_jenka(state, ctx),
134 => self.tick_n134_armadillo(state, ctx),
135 => self.tick_n135_skeleton(state, ctx),
136 => self.tick_n136_puppy_carried(state, ctx),
137 => self.tick_n137_large_door_frame(state, ctx),
138 => self.tick_n138_large_door(state, ctx),
139 => self.tick_n139_doctor(state, ctx),
140 => self.tick_n140_toroko_frenzied(state, ctx),
141 => self.tick_n141_toroko_block_projectile(state, ctx),
142 => self.tick_n142_flower_cub(state, ctx),
143 => self.tick_n143_jenka_collapsed(state, ctx),
144 => self.tick_n144_toroko_teleporting_in(state, ctx),
145 => self.tick_n145_king_sword(state, ctx),
146 => self.tick_n146_lightning(state, ctx),
147 => self.tick_n147_critter_purple(state, ctx),
148 => self.tick_n148_critter_purple_projectile(state, ctx),
149 => self.tick_n149_horizontal_moving_block(state, ctx),
150 => self.tick_n150_quote(state, ctx),
151 => self.tick_n151_blue_robot_standing(state, ctx),
152 => self.tick_n152_shutter_stuck(state, ctx),
153 => self.tick_n153_gaudi(state, ctx),
154 => self.tick_n154_gaudi_dead(state, ctx),
155 => self.tick_n155_gaudi_flying(state, ctx),
156 => self.tick_n156_gaudi_projectile(state, ctx),
157 => self.tick_n157_vertical_moving_block(state, ctx),
158 => self.tick_n158_fish_missile(state, ctx),
159 => self.tick_n159_monster_x_defeated(state, ctx),
160 => self.tick_n160_puu_black(state, ctx),
161 => self.tick_n161_puu_black_projectile(state, ctx),
162 => self.tick_n162_puu_black_dead(state, ctx),
163 => self.tick_n163_dr_gero(state, ctx),
164 => self.tick_n164_nurse_hasumi(state, ctx),
165 => self.tick_n165_curly_collapsed(state, ctx),
166 => self.tick_n166_chaba(state, ctx),
167 => self.tick_n167_booster_falling(state, ctx),
168 => self.tick_n168_boulder(state, ctx),
169 => self.tick_n169_balrog_shooting_missiles(state, ctx),
170 => self.tick_n170_balrog_missile(state, ctx),
171 => self.tick_n171_fire_whirrr(state, ctx),
172 => self.tick_n172_fire_whirrr_projectile(state, ctx),
173 => self.tick_n173_gaudi_armored(state, ctx),
174 => self.tick_n174_gaudi_armored_projectile(state, ctx),
175 => self.tick_n175_gaudi_egg(state, ctx),
176 => self.tick_n176_buyo_buyo_base(state, ctx),
177 => self.tick_n177_buyo_buyo(state, ctx),
178 => self.tick_n178_core_blade_projectile(state, ctx),
179 => self.tick_n179_core_wisp_projectile(state, ctx),
180 => self.tick_n180_curly_ai(state, ctx),
181 => self.tick_n181_curly_ai_machine_gun(state, ctx),
182 => self.tick_n182_curly_ai_polar_star(state, ctx),
183 => self.tick_n183_curly_air_tank_bubble(state, ctx),
184 => self.tick_n184_shutter(state, ctx),
185 => self.tick_n185_small_shutter(state, ctx),
186 => self.tick_n186_lift_block(state, ctx),
187 => self.tick_n187_fuzz_core(state, ctx),
188 => self.tick_n188_fuzz(state, ctx),
189 => self.tick_n189_homing_flame(state, ctx),
190 => self.tick_n190_broken_robot(state, ctx),
191 => self.tick_n191_water_level(state, ctx),
192 => self.tick_n192_scooter(state, ctx),
193 => self.tick_n193_broken_scooter(state, ctx),
194 => self.tick_n194_broken_blue_robot(state, ctx),
195 => self.tick_n195_background_grate(state, ctx),
196 => self.tick_n196_ironhead_wall(state, ctx),
197 => self.tick_n197_porcupine_fish(state, ctx),
198 => self.tick_n198_ironhead_projectile(state, ctx),
199 => self.tick_n199_wind_particles(state, ctx),
200 => self.tick_n200_zombie_dragon(state, ctx),
201 => self.tick_n201_zombie_dragon_dead(state, ctx),
202 => self.tick_n202_zombie_dragon_projectile(state, ctx),
203 => self.tick_n203_critter_destroyed_egg_corridor(state, ctx),
204 => self.tick_n204_small_falling_spike(state, ctx),
205 => self.tick_n205_large_falling_spike(state, ctx),
206 => self.tick_n206_counter_bomb(state, ctx),
207 => self.tick_n207_counter_bomb_countdown(state, ctx),
208 => self.tick_n208_basu_destroyed_egg_corridor(state, ctx),
209 => self.tick_n209_basu_projectile_destroyed_egg_corridor(state, ctx),
210 => self.tick_n210_beetle_destroyed_egg_corridor(state, ctx),
211 => self.tick_n211_small_spikes(state, ctx),
212 => self.tick_n212_sky_dragon(state, ctx),
213 => self.tick_n213_night_spirit(state, ctx),
214 => self.tick_n214_night_spirit_projectile(state, ctx),
215 => self.tick_n215_sandcroc_outer_wall(state, ctx),
216 => self.tick_n216_debug_cat(state, ctx),
217 => self.tick_n217_itoh(state, ctx),
218 => self.tick_n218_core_giant_ball(state, ctx),
219 => self.tick_n219_smoke_generator(state, ctx),
220 => self.tick_n220_shovel_brigade(state, ctx),
221 => self.tick_n221_shovel_brigade_walking(state, ctx),
222 => self.tick_n222_prison_bars(state, ctx),
223 => self.tick_n223_momorin(state, ctx),
224 => self.tick_n224_chie(state, ctx),
225 => self.tick_n225_megane(state, ctx),
226 => self.tick_n226_kanpachi_plantation(state, ctx),
227 => self.tick_n227_bucket(state, ctx),
228 => self.tick_n228_droll(state, ctx),
229 => self.tick_n229_red_flowers_sprouts(state, ctx),
230 => self.tick_n230_red_flowers_blooming(state, ctx),
231 => self.tick_n231_rocket(state, ctx),
232 => self.tick_n232_orangebell(state, ctx),
233 => self.tick_n233_orangebell_bat(state, ctx),
234 => self.tick_n234_red_flowers_picked(state, ctx),
235 => self.tick_n235_midorin(state, ctx),
236 => self.tick_n236_gunfish(state, ctx),
237 => self.tick_n237_gunfish_projectile(state, ctx),
238 => self.tick_n238_press_sideways(state, ctx),
239 => self.tick_n239_cage_bars(state, ctx),
240 => self.tick_n240_mimiga_jailed(state, ctx),
241 => self.tick_n241_critter_red(state, ctx),
242 => self.tick_n242_bat_last_cave(state, ctx),
243 => self.tick_n243_bat_generator(state, ctx),
244 => self.tick_n244_lava_drop(state, ctx),
245 => self.tick_n245_lava_drop_generator(state, ctx),
246 => self.tick_n246_press_proximity(state, ctx),
247 => self.tick_n247_misery_boss(state, ctx),
248 => self.tick_n248_misery_boss_vanishing(state, ctx),
249 => self.tick_n249_misery_boss_appearing(state, ctx),
250 => self.tick_n250_misery_boss_lightning_ball(state, ctx),
251 => self.tick_n251_misery_boss_lightning(state, ctx),
252 => self.tick_n252_misery_boss_bats(state, ctx),
253 => self.tick_n253_experience_capsule(state, ctx),
254 => self.tick_n254_helicopter(state, ctx),
255 => self.tick_n255_helicopter_blades(state, ctx),
256 => self.tick_n256_doctor_facing_away(state, ctx),
257 => self.tick_n257_red_crystal(state, ctx),
258 => self.tick_n258_mimiga_sleeping(state, ctx),
259 => self.tick_n259_curly_unconscious(state, ctx),
260 => self.tick_n260_shovel_brigade_caged(state, ctx),
261 => self.tick_n261_chie_caged(state, ctx),
262 => self.tick_n262_chaco_caged(state, ctx),
263 => self.tick_n263_doctor_boss(state, ctx),
264 => self.tick_n264_doctor_boss_red_projectile(state, ctx),
265 => self.tick_n265_doctor_boss_red_projectile_trail(state, ctx),
266 => self.tick_n266_doctor_boss_red_projectile_bouncing(state, ctx),
267 => self.tick_n267_muscle_doctor(state, ctx),
268 => self.tick_n268_igor_enemy(state, ctx),
269 => self.tick_n269_red_bat_bouncing(state, ctx),
270 => self.tick_n270_doctor_red_energy(state, ctx),
271 => self.tick_n271_ironhead_block(state, ctx),
272 => self.tick_n272_ironhead_block_generator(state, ctx),
273 => self.tick_n273_droll_projectile(state, ctx),
274 => self.tick_n274_droll(state, ctx),
275 => self.tick_n275_puppy_plantation(state, ctx),
276 => self.tick_n276_red_demon(state, ctx),
277 => self.tick_n277_red_demon_projectile(state, ctx),
278 => self.tick_n278_little_family(state, ctx),
279 => self.tick_n279_large_falling_block(state, ctx),
280 => self.tick_n280_sue_teleported(state, ctx),
281 => self.tick_n281_doctor_energy_form(state, ctx),
282 => self.tick_n282_mini_undead_core_active(state, ctx),
283 => self.tick_n283_misery_possessed(state, ctx),
284 => self.tick_n284_sue_possessed(state, ctx),
285 => self.tick_n285_undead_core_spiral_projectile(state, ctx),
286 => self.tick_n286_undead_core_spiral_projectile_trail(state, ctx),
287 => self.tick_n287_orange_smoke(state, ctx),
288 => self.tick_n288_undead_core_exploding_rock(state, ctx),
289 => self.tick_n289_critter_orange(state, ctx),
290 => self.tick_n290_bat_misery(state, ctx),
291 => self.tick_n291_mini_undead_core_inactive(state, ctx),
292 => self.tick_n292_quake(state, ctx),
293 => self.tick_n293_undead_core_energy_shot(state, ctx),
294 => self.tick_n294_quake_falling_block_generator(state, ctx),
295 => self.tick_n295_cloud(state, ctx),
296 => self.tick_n296_cloud_generator(state, ctx),
297 => self.tick_n297_sue_dragon_mouth(state, ctx),
298 => self.tick_n298_intro_doctor(state, ctx),
299 => self.tick_n299_intro_balrog_misery(state, ctx),
300 => self.tick_n300_intro_demon_crown(state, ctx),
301 => self.tick_n301_misery_fish_missile(state, ctx),
302 => self.tick_n302_camera_focus_marker(state, ctx),
303 => self.tick_n303_curly_machine_gun(state, ctx),
304 => self.tick_n304_gaudi_hospital(state, ctx),
305 => self.tick_n305_small_puppy(state, ctx),
306 => self.tick_n306_balrog_nurse(state, ctx),
307 => self.tick_n307_santa_caged(state, ctx),
308 => self.tick_n308_stumpy(state, ctx),
309 => self.tick_n309_bute(state, ctx),
310 => self.tick_n310_bute_sword(state, ctx),
311 => self.tick_n311_bute_archer(state, ctx),
312 => self.tick_n312_bute_arrow_projectile(state, ctx),
313 => self.tick_n313_ma_pignon(state, ctx),
314 => self.tick_n314_ma_pignon_rock(state, ctx),
315 => self.tick_n315_ma_pignon_clone(state, ctx),
316 => self.tick_n316_bute_dead(state, ctx),
317 => self.tick_n317_mesa(state, ctx),
318 => self.tick_n318_mesa_dead(state, ctx),
319 => self.tick_n319_mesa_block(state, ctx),
320 => self.tick_n320_curly_carried(state, ctx),
321 => self.tick_n321_curly_nemesis(state, ctx),
322 => self.tick_n322_deleet(state, ctx),
323 => self.tick_n323_bute_spinning(state, ctx),
324 => self.tick_n324_bute_generator(state, ctx),
325 => self.tick_n325_heavy_press_lightning(state, ctx),
326 => self.tick_n326_sue_itoh_human_transition(state, ctx),
327 => self.tick_n327_sneeze(state, ctx),
328 => self.tick_n328_human_transform_machine(state, ctx),
329 => self.tick_n329_laboratory_fan(state, ctx),
330 => self.tick_n330_rolling(state, ctx),
331 => self.tick_n331_ballos_bone_projectile(state, ctx),
332 => self.tick_n332_ballos_shockwave(state, ctx),
333 => self.tick_n333_ballos_lightning(state, ctx),
334 => self.tick_n334_sweat(state, ctx),
335 => self.tick_n335_ikachan(state, ctx),
336 => self.tick_n336_ikachan_generator(state, ctx),
337 => self.tick_n337_numahachi(state, ctx),
338 => self.tick_n338_green_devil(state, ctx),
339 => self.tick_n339_green_devil_generator(state, ctx),
340 => self.tick_n340_ballos(state, ctx),
341 => self.tick_n341_ballos_1_head(state, ctx),
342 => self.tick_n342_ballos_orbiting_eye(state, ctx),
343 => self.tick_n343_ballos_3_cutscene(state, ctx),
344 => self.tick_n344_ballos_3_eyes(state, ctx),
345 => self.tick_n345_ballos_skull_projectile(state, ctx),
346 => self.tick_n346_ballos_orbiting_platform(state, ctx),
347 => self.tick_n347_hoppy(state, ctx),
348 => self.tick_n348_ballos_4_spikes(state, ctx),
349 => self.tick_n349_statue(state, ctx),
350 => self.tick_n350_flying_bute_archer(state, ctx),
351 => self.tick_n351_statue_shootable(state, ctx),
352 => self.tick_n352_ending_characters(state, ctx),
353 => self.tick_n353_bute_sword_flying(state, ctx),
354 => self.tick_n354_invisible_deathtrap_wall(state, ctx),
355 => self.tick_n355_quote_and_curly_on_balrog(state, ctx),
356 => self.tick_n356_balrog_rescuing(state, ctx),
357 => self.tick_n357_puppy_ghost(state, ctx),
358 => self.tick_n358_misery_credits(state, ctx),
359 => self.tick_n359_water_droplet_generator(state, ctx),
360 => self.tick_n360_credits_thank_you(state, ctx),
361 => self.tick_n361_flying_gaudi(state, ctx),
362 => self.tick_n362_curly_clone(state, ctx),
363 => self.tick_n363_dead_curly_clone(state, ctx),
364 => self.tick_n364_fast_bullet(state, ctx),
365 => self.tick_n365_still_curly_clone(state, ctx),
366 => self.tick_n366_zombie_curly_clone(state, ctx),
367 => self.tick_n367_curly_clone_incubator(state, ctx),
368 => self.tick_n368_gclone(state, ctx),
369 => self.tick_n369_gclone_curly_clone(state, ctx),
370 => self.tick_n370_second_quote(state, ctx),
_ => Ok(()),
}?;
@ -645,6 +641,12 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu
}
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(());
}
@ -740,7 +742,7 @@ impl PhysicalEntity for NPC {
}
#[inline(always)]
fn hit_bounds(&self) -> &Rect<u32> {
fn hit_bounds(&self) -> &HitExtents {
&self.hit_bounds
}
@ -909,16 +911,16 @@ impl NPCTable {
}
}
pub fn get_hit_bounds(&self, npc_type: u16) -> Rect<u32> {
pub fn get_hit_bounds(&self, npc_type: u16) -> HitExtents {
if let Some(npc) = self.entries.get(npc_type as usize) {
Rect {
HitExtents {
left: npc.hit_bounds.left as u32 * 0x200,
top: npc.hit_bounds.top as u32 * 0x200,
right: npc.hit_bounds.right as u32 * 0x200,
bottom: npc.hit_bounds.bottom as u32 * 0x200,
}
} else {
Rect { left: 0, top: 0, right: 0, bottom: 0 }
HitExtents { left: 0, top: 0, right: 0, bottom: 0 }
}
}

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

@ -82,6 +82,33 @@ pub const OFFSETS: [(i32, i32); 64] = [
(4, 4),
];
/**
* Represents hitbox extents, with each value being the distance from the center of the entity.
*/
#[derive(Debug, Clone, Copy)]
pub struct HitExtents {
pub left: u32,
pub right: u32,
pub top: u32,
pub bottom: u32,
}
impl HitExtents {
/**
* Check if a point is within the hitbox extents of an entity on X axis (exclusively).
*/
pub const fn point_in_entity_x(&self, entity_x: i32, point_x: i32) -> bool {
point_x > entity_x - self.left as i32 && point_x < entity_x + self.right as i32
}
/**
* Check if a point is within the hitbox extents of an entity on Y axis (exclusively).
*/
pub const fn point_in_entity_y(&self, entity_y: i32, point_y: i32) -> bool {
point_y > entity_y - self.top as i32 && point_y < entity_y + self.bottom as i32
}
}
pub trait PhysicalEntity {
fn x(&self) -> i32;
fn y(&self) -> i32;
@ -96,7 +123,7 @@ pub trait PhysicalEntity {
0
}
fn hit_bounds(&self) -> &Rect<u32>;
fn hit_bounds(&self) -> &HitExtents;
fn display_bounds(&self) -> &Rect<u32>;
fn set_x(&mut self, x: i32);
@ -125,14 +152,23 @@ pub trait PhysicalEntity {
let bounds_bottom = if self.is_player() { 0x800 } else { 0x600 };
let half_tile_size = state.tile_size.as_int() * 0x100;
if (self.y() - self.hit_bounds().top as i32) < ((y * 2 + 1) * half_tile_size - bounds_top)
&& (self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size + bounds_bottom)
let hit_bounds = *self.hit_bounds();
let block_center_x = (x * 2) * half_tile_size;
let block_center_y = (y * 2) * half_tile_size;
let block_top = (y * 2 - 1) * half_tile_size;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if (self.y() - hit_bounds.top as i32) < (block_bottom - bounds_top)
&& (self.y() + hit_bounds.bottom as i32) > (block_top + bounds_bottom)
{
// left wall
if (self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size
&& (self.x() - self.hit_bounds().right as i32) > (x * 2) * half_tile_size
if (self.x() - hit_bounds.right as i32) < block_right
&& (self.x() - hit_bounds.right as i32) > block_center_x
{
self.set_x(((x * 2 + 1) * half_tile_size) + self.hit_bounds().right as i32);
self.set_x(block_right + hit_bounds.right as i32);
if self.is_player() {
if self.vel_x() < -0x180 {
@ -148,10 +184,10 @@ pub trait PhysicalEntity {
}
// right wall
if (self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size
&& (self.x() + self.hit_bounds().right as i32) < (x * 2) * half_tile_size
if (self.x() + hit_bounds.right as i32) > block_left
&& (self.x() + hit_bounds.right as i32) < block_center_x
{
self.set_x(((x * 2 - 1) * half_tile_size) - self.hit_bounds().right as i32);
self.set_x(block_left - hit_bounds.right as i32);
if self.is_player() {
if self.vel_x() > 0x180 {
@ -167,27 +203,26 @@ pub trait PhysicalEntity {
}
}
if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size - bounds_x)
&& ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size + bounds_x)
if ((self.x() - hit_bounds.right as i32) < block_right - bounds_x)
&& ((self.x() + hit_bounds.right as i32) > block_left + bounds_x)
{
// ceiling
if (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
&& (self.y() - self.hit_bounds().top as i32) > (y * 2) * half_tile_size
if (self.y() - hit_bounds.top as i32) < block_bottom && (self.y() - hit_bounds.top as i32) > block_center_y
{
self.set_y(((y * 2 + 1) * half_tile_size) + self.hit_bounds().top as i32);
self.set_y(block_bottom + hit_bounds.top as i32);
if self.is_player() {
if !self.cond().hidden() && self.vel_y() < -0x200 {
state.sound_manager.play_sfx(3);
state.create_caret(
self.x(),
self.y() - self.hit_bounds().top as i32,
self.y() - hit_bounds.top as i32,
CaretType::LittleParticles,
Direction::Left,
);
state.create_caret(
self.x(),
self.y() - self.hit_bounds().top as i32,
self.y() - hit_bounds.top as i32,
CaretType::LittleParticles,
Direction::Left,
);
@ -204,10 +239,10 @@ pub trait PhysicalEntity {
}
// floor
if ((self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size))
&& ((self.y() + self.hit_bounds().bottom as i32) < (y * 2) * half_tile_size)
if ((self.y() + hit_bounds.bottom as i32) > block_top)
&& ((self.y() + hit_bounds.bottom as i32) < block_center_y)
{
self.set_y(((y * 2 - 1) * half_tile_size) - self.hit_bounds().bottom as i32);
self.set_y(block_top - hit_bounds.bottom as i32);
if self.is_player() {
if self.vel_y() > 0x400 {
@ -228,13 +263,16 @@ pub trait PhysicalEntity {
fn test_platform_hit(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
let half_tile_size = state.tile_size.as_int() * 0x100;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if ((self.x() - self.hit_bounds().right as i32) < (x * 2 + 1) * half_tile_size)
&& ((self.x() + self.hit_bounds().right as i32) > (x * 2 - 1) * half_tile_size)
&& ((self.y() + self.hit_bounds().bottom as i32) > ((y * 2 - 1) * half_tile_size))
&& ((self.y() + self.hit_bounds().bottom as i32) < (y * 2 - 1) * half_tile_size + 0x400)
if ((self.x() - self.hit_bounds().right as i32) < block_right)
&& ((self.x() + self.hit_bounds().right as i32) > block_left)
&& ((self.y() + self.hit_bounds().bottom as i32) > block_top)
&& ((self.y() + self.hit_bounds().bottom as i32) < block_top + 0x400)
{
self.set_y(((y * 2 - 1) * half_tile_size) - self.hit_bounds().bottom as i32);
self.set_y(block_top - self.hit_bounds().bottom as i32);
if self.is_player() {
if self.vel_y() > 0x400 {
@ -257,12 +295,15 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (self.y() - self.hit_bounds().top as i32)
< (y * tile_size) - (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
< (y * tile_size) - (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y(
(y * tile_size) - ((self.x() - x * tile_size) / 2) + quarter_tile_size + self.hit_bounds().top as i32,
@ -298,12 +339,15 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (self.y() - self.hit_bounds().top as i32)
< (y * tile_size) - (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
< (y * tile_size) - (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y(
(y * tile_size) - ((self.x() - x * tile_size) / 2) - quarter_tile_size + self.hit_bounds().top as i32,
@ -339,12 +383,15 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (self.y() - self.hit_bounds().top as i32)
< (y * tile_size) + (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
< (y * tile_size) + (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y(
(y * tile_size) + ((self.x() - x * tile_size) / 2) - quarter_tile_size + self.hit_bounds().top as i32,
@ -380,12 +427,15 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (self.y() - self.hit_bounds().top as i32)
< (y * tile_size) + (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
< (y * tile_size) + (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y(
(y * tile_size) + ((self.x() - x * tile_size) / 2) + quarter_tile_size + self.hit_bounds().top as i32,
@ -421,14 +471,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_left_higher_half(true);
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (self.y() + self.hit_bounds().bottom as i32)
> (y * tile_size) + (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
> (y * tile_size) + (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) + ((self.x() - x * tile_size) / 2)
@ -454,14 +507,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_left_lower_half(true);
if (self.x() < (x * 2 + 1) * half_tile_size)
&& (self.x() > (x * 2 - 1) * half_tile_size)
if (self.x() < block_right)
&& (self.x() > block_left)
&& (self.y() + self.hit_bounds().bottom as i32)
> (y * tile_size) + (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
> (y * tile_size) + (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) + ((self.x() - x * tile_size) / 2) + quarter_tile_size
@ -486,14 +542,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_right_lower_half(true);
if (self.x() < (x * 2 + 1) * half_tile_size)
&& (self.x() > (x * 2 - 1) * half_tile_size)
if (self.x() < block_right)
&& (self.x() > block_left)
&& (self.y() + self.hit_bounds().bottom as i32)
> (y * tile_size) - (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
> (y * tile_size) - (self.x() - x * tile_size) / 2 + quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) - ((self.x() - x * tile_size) / 2) + quarter_tile_size
@ -518,14 +577,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_right_higher_half(true);
if (self.x() < (x * 2 + 1) * half_tile_size)
&& (self.x() > (x * 2 - 1) * half_tile_size)
if (self.x() < block_right)
&& (self.x() > block_left)
&& (self.y() + self.hit_bounds().bottom as i32)
> (y * tile_size) - (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
> (y * tile_size) - (self.x() - x * tile_size) / 2 - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size)
@ -551,11 +613,14 @@ pub trait PhysicalEntity {
fn test_hit_upper_left_slope(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (self.y() - self.hit_bounds().top as i32) < (y * tile_size) - (self.x() - x * tile_size)
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y((y * tile_size) - (self.x() - x * tile_size) + self.hit_bounds().top as i32);
@ -587,11 +652,14 @@ pub trait PhysicalEntity {
fn test_hit_upper_right_slope(&mut self, state: &mut SharedGameState, x: i32, y: i32) {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let block_top = (y * 2 - 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (self.y() - self.hit_bounds().top as i32) < (y * tile_size) + (self.x() - x * tile_size)
&& (self.y() + self.hit_bounds().bottom as i32) > (y * 2 - 1) * half_tile_size
&& (self.y() + self.hit_bounds().bottom as i32) > block_top
{
self.set_y((y * tile_size) + (self.x() - x * tile_size) + self.hit_bounds().top as i32);
@ -624,14 +692,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_left_higher_half(true);
if self.x() < (x * 2 + 1) * half_tile_size
&& self.x() > (x * 2 - 1) * half_tile_size
if self.x() < block_right
&& self.x() > block_left
&& (self.y() + self.hit_bounds().bottom as i32)
> (y * tile_size) + (self.x() - x * tile_size) - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
> (y * tile_size) + (self.x() - x * tile_size) - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) + (self.x() - x * tile_size) - quarter_tile_size - self.hit_bounds().bottom as i32,
@ -655,14 +726,17 @@ pub trait PhysicalEntity {
let tile_size = state.tile_size.as_int() * 0x200;
let half_tile_size = tile_size / 2;
let quarter_tile_size = half_tile_size / 2;
let block_bottom = (y * 2 + 1) * half_tile_size;
let block_left = (x * 2 - 1) * half_tile_size;
let block_right = (x * 2 + 1) * half_tile_size;
self.flags().set_hit_right_higher_half(true);
if (self.x() < (x * 2 + 1) * half_tile_size)
&& (self.x() > (x * 2 - 1) * half_tile_size)
if (self.x() < block_right)
&& (self.x() > block_left)
&& (self.y() + self.hit_bounds().bottom as i32)
> (y * tile_size) - (self.x() - x * tile_size) - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < (y * 2 + 1) * half_tile_size
> (y * tile_size) - (self.x() - x * tile_size) - quarter_tile_size
&& (self.y() - self.hit_bounds().top as i32) < block_bottom
{
self.set_y(
(y * tile_size) - (self.x() - x * tile_size) - quarter_tile_size - self.hit_bounds().bottom as i32,

View file

@ -19,6 +19,8 @@ use crate::input::dummy_player_controller::DummyPlayerController;
use crate::input::player_controller::PlayerController;
use crate::util::rng::RNG;
use super::physics::HitExtents;
mod player_hit;
pub mod skin;
@ -93,7 +95,7 @@ pub struct Player {
pub equip: Equipment,
pub direction: Direction,
pub display_bounds: Rect<u32>,
pub hit_bounds: Rect<u32>,
pub hit_bounds: HitExtents,
pub control_mode: ControlMode,
pub question: bool,
pub booster_fuel: u32,

View file

@ -6,9 +6,9 @@ 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::PhysicalEntity;
use crate::game::physics::{HitExtents, PhysicalEntity};
use crate::game::player::{ControlMode, Player, TargetPlayer};
use crate::game::shared_game_state::SharedGameState;
use crate::game::weapon::WeaponType;
@ -43,7 +43,7 @@ impl PhysicalEntity for Player {
}
#[inline(always)]
fn hit_bounds(&self) -> &Rect<u32> {
fn hit_bounds(&self) -> &HitExtents {
&self.hit_bounds
}
@ -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

@ -4,6 +4,7 @@ use crate::common::{Color, Direction, Rect};
use crate::framework::context::Context;
use crate::framework::filesystem;
use crate::framework::filesystem::File;
use crate::game::physics::HitExtents;
use crate::game::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin};
use crate::game::shared_game_state::SharedGameState;
@ -227,10 +228,10 @@ impl PlayerSkin for BasicPlayerSkin {
""
}
fn get_hit_bounds(&self) -> Rect<u32> {
fn get_hit_bounds(&self) -> HitExtents {
let ubox = &self.metadata.hit_box;
Rect {
HitExtents {
left: ubox.left as u32 * 0x200,
top: ubox.top as u32 * 0x200,
right: ubox.right as u32 * 0x200,

View file

@ -1,5 +1,6 @@
use crate::bitfield;
use crate::common::{Color, Direction, Rect};
use crate::game::physics::HitExtents;
use crate::game::shared_game_state::SharedGameState;
pub mod basic;
@ -83,7 +84,7 @@ pub trait PlayerSkin: PlayerSkinClone {
fn get_mask_texture_name(&self) -> &str;
/// Returns hit bounds of skin.
fn get_hit_bounds(&self) -> Rect<u32>;
fn get_hit_bounds(&self) -> HitExtents;
/// Returns display bounds of skin.
fn get_display_bounds(&self) -> Rect<u32>;

File diff suppressed because it is too large Load diff

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,8 +17,10 @@ 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;
use crate::game::npc::NPC;
use crate::game::player::{ControlMode, TargetPlayer};
use crate::game::scripting::tsc::bytecode_utils::read_cur_varint;
@ -899,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 {
@ -953,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;
@ -1094,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 {
@ -1105,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 {
@ -1482,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);
}
@ -1515,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;
@ -1529,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;
}
}
@ -1552,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;
@ -1568,7 +1573,7 @@ impl TextScriptVM {
npc.direction = direction;
}
}
}
});
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -1578,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);
@ -1625,19 +1630,21 @@ impl TextScriptVM {
npc.direction = direction;
}
npc.tick(
map_err_to_break(npc.tick(
state,
(
[&mut game_scene.player1, &mut game_scene.player2],
&game_scene.npc_list,
&mut game_scene.stage,
&mut game_scene.bullet_manager,
&mut game_scene.flash,
&mut game_scene.boss,
),
)?;
NPCContext {
players: [&mut game_scene.player1, &mut game_scene.player2],
npc_list: &game_scene.npc_list,
stage: &mut game_scene.stage,
bullet_manager: &mut game_scene.bullet_manager,
flash: &mut game_scene.flash,
boss: &mut game_scene.boss,
},
))?;
}
}
ControlFlow::Continue(())
})?;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -1649,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;
@ -1666,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

@ -4,6 +4,7 @@ use crate::framework::filesystem::{user_create, user_open};
use crate::framework::gamepad::{Axis, AxisDirection, Button, PlayerControllerInputType};
use crate::framework::graphics::VSyncMode;
use crate::framework::keyboard::ScanCode;
use crate::game::profile::SaveFormat;
use crate::game::player::TargetPlayer;
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, TimingMode, WindowMode};
use crate::input::combined_player_controller::CombinedPlayerController;
@ -87,6 +88,8 @@ pub struct Settings {
pub discord_rpc: bool,
#[serde(default = "default_true")]
pub allow_strafe: bool,
#[serde(default = "default_save_format")]
pub save_format: SaveFormat,
}
fn default_true() -> bool {
@ -95,7 +98,7 @@ fn default_true() -> bool {
#[inline(always)]
fn current_version() -> u32 {
25
26
}
#[inline(always)]
@ -171,6 +174,11 @@ fn default_cutscene_skip_mode() -> CutsceneSkipMode {
CutsceneSkipMode::Hold
}
#[inline(always)]
fn default_save_format() -> SaveFormat {
SaveFormat::Freeware
}
impl Settings {
pub fn load(ctx: &Context) -> GameResult<Settings> {
if let Ok(file) = user_open(ctx, "/settings.json") {
@ -359,6 +367,11 @@ impl Settings {
}
}
if self.version == 25 {
self.version = 26;
self.save_format = default_save_format();
}
if self.version != initial_version {
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
}
@ -467,6 +480,7 @@ impl Default for Settings {
cutscene_skip_mode: CutsceneSkipMode::Hold,
discord_rpc: true,
allow_strafe: true,
save_format: default_save_format()
}
}
}

View file

@ -17,7 +17,7 @@ use crate::framework::{filesystem, graphics};
use crate::game::caret::{Caret, CaretType};
use crate::game::npc::NPCTable;
use crate::game::player::TargetPlayer;
use crate::game::profile::GameProfile;
use crate::game::profile::{GameProfile, SaveContainer, SaveFormat, SaveParams, SaveSlot};
use crate::game::scripting::tsc::credit_script::{CreditScript, CreditScriptVM};
use crate::game::scripting::tsc::text_script::{
ScriptMode, TextScript, TextScriptEncoding, TextScriptExecutionState, TextScriptVM,
@ -678,48 +678,60 @@ impl SharedGameState {
ctx: &mut Context,
target_player: Option<TargetPlayer>,
) -> GameResult {
/*
if let Some(save_path) = self.get_save_filename(self.save_slot) {
if let Ok(data) = filesystem::open_options(ctx, save_path, OpenOptions::new().write(true).create(true)) {
let profile = GameProfile::dump(self, game_scene, target_player);
profile.write_save(data)?;
//profile.write_save(data)?;
let mut save_container = SaveContainer::load(ctx)?;
save_container.set_profile(None, self.save_slot, profile);
save_container.write_save(ctx, SaveFormat::Generic, None, None);
} else {
log::warn!("Cannot open save file.");
}
} else {
log::info!("Mod has saves disabled.");
}
*/
if let Some(slot) = self.get_save_slot(self.save_slot) {
let profile = GameProfile::dump(self, game_scene, target_player);
let mut save_container = SaveContainer::load(ctx, self)?;
save_container.set_profile(slot, profile);
save_container.save(ctx, self, SaveParams::default())?;
} else {
log::info!("Mod has saves disabled.");
}
Ok(())
}
pub fn load_or_start_game(&mut self, ctx: &mut Context) -> GameResult {
if let Some(save_path) = self.get_save_filename(self.save_slot) {
if let Ok(data) = filesystem::user_open(ctx, save_path) {
match GameProfile::load_from_save(data) {
Ok(profile) => {
self.reset();
let mut next_scene = GameScene::new(self, ctx, profile.current_map as usize)?;
if let Some(slot) = self.get_save_slot(self.save_slot) {
if let Ok(save) = SaveContainer::load(ctx, self) {
if let Some(profile) = save.get_profile(slot) {
self.reset();
let mut next_scene = GameScene::new(self, ctx, profile.current_map as usize)?;
profile.apply(self, &mut next_scene, ctx);
profile.apply(self, &mut next_scene, ctx);
#[cfg(feature = "discord-rpc")]
self.discord_rpc.update_difficulty(self.difficulty)?;
#[cfg(feature = "discord-rpc")]
self.discord_rpc.update_difficulty(self.difficulty)?;
self.next_scene = Some(Box::new(next_scene));
return Ok(());
}
Err(e) => {
log::warn!("Failed to load save game, starting new one: {}", e);
}
self.next_scene = Some(Box::new(next_scene));
return Ok(());
}
} else {
log::warn!("No save game found, starting new one...");
}
} else {
log::info!("Mod has saves disabled.");
log::info!("Mod has saves disabled, starting new game...");
}
self.start_new_game(ctx)
self.start_new_game(ctx)?;
Ok(())
}
pub fn reset(&mut self) {
@ -859,6 +871,30 @@ impl SharedGameState {
}
}
pub fn get_save_slot(&mut self, slot: usize) -> Option<SaveSlot> {
if let Some(mod_path) = &self.mod_path {
if let Some(mod_info) = self.mod_list.get_mod_info_from_path(mod_path.clone()) {
log::debug!("Mod info get save slot: {:?}", mod_info);
if mod_info.id.starts_with(&"csmod_".to_string()) {
if mod_info.save_slot > 0 {
return Some(SaveSlot::CSPMod(mod_info.save_slot.try_into().unwrap(), slot));
} else if mod_info.save_slot < 0 {
// Mods with a negative save set(slot) has saves disabled.
return None;
}
// If mod uses save set 0, saves are stored in the main game set
} else {
return Some(SaveSlot::Mod(mod_info.id.clone(), slot));
}
} else {
return None;
}
}
Some(SaveSlot::MainGame(slot))
}
pub fn get_rec_filename(&self) -> String {
if let Some(mod_path) = &self.mod_path {
let name = self.mod_list.get_name_from_path(mod_path.to_string());

View file

@ -5,11 +5,11 @@ use crate::engine_constants::{BulletData, EngineConstants};
use crate::game::caret::CaretType;
use crate::game::npc::list::NPCList;
use crate::game::npc::NPC;
use crate::game::physics::{OFFSETS, PhysicalEntity};
use crate::game::physics::{HitExtents, PhysicalEntity, OFFSETS};
use crate::game::player::{Player, TargetPlayer};
use crate::game::shared_game_state::{SharedGameState, TileSize};
use crate::game::stage::Stage;
use crate::util::rng::{RNG, Xoroshiro32PlusPlus, XorShift};
use crate::util::rng::{XorShift, Xoroshiro32PlusPlus, RNG};
pub struct BulletManager {
pub bullets: Vec<Bullet>,
@ -121,7 +121,7 @@ pub struct Bullet {
pub anim_counter: u16,
pub action_num: u16,
pub action_counter: u16,
pub hit_bounds: Rect<u32>,
pub hit_bounds: HitExtents,
pub display_bounds: Rect<u32>,
}
@ -180,12 +180,12 @@ impl Bullet {
bullet.display_bounds.right as u32 * 0x200,
bullet.display_bounds.bottom as u32 * 0x200,
),
hit_bounds: Rect::new(
bullet.block_hit_width as u32 * 0x200,
bullet.block_hit_height as u32 * 0x200,
bullet.block_hit_width as u32 * 0x200,
bullet.block_hit_height as u32 * 0x200,
),
hit_bounds: HitExtents {
left: bullet.block_hit_width as u32 * 0x200,
top: bullet.block_hit_height as u32 * 0x200,
right: bullet.block_hit_width as u32 * 0x200,
bottom: bullet.block_hit_height as u32 * 0x200,
},
}
}
@ -1844,7 +1844,7 @@ impl PhysicalEntity for Bullet {
}
#[inline(always)]
fn hit_bounds(&self) -> &Rect<u32> {
fn hit_bounds(&self) -> &HitExtents {
&self.hit_bounds
}
@ -1964,7 +1964,7 @@ impl PhysicalEntity for Bullet {
}
if let Some(tile) =
stage.map.tiles.get_mut(stage.map.width as usize * (y + oy) as usize + (x + ox) as usize)
stage.map.tiles.get_mut(stage.map.width as usize * (y + oy) as usize + (x + ox) as usize)
{
*tile = tile.wrapping_sub(1);
}

View file

@ -183,8 +183,6 @@ impl Weapon {
self.refire_timer = 4;
}
// todo lua hook
match self.wtype {
WeaponType::None => {}
WeaponType::Snake => self.tick_snake(player, player_id, bullet_manager, state),

View file

@ -86,8 +86,12 @@ impl Locale {
}
}
pub fn ts(&self, key: &str) -> String {
self.t(key).to_owned()
}
pub fn tt(&self, key: &str, args: &[(&str, &str)]) -> String {
let mut string = self.t(key).to_owned();
let mut string = self.ts(key);
for (key, value) in args.iter() {
string = string.replace(&format!("{{{}}}", key), &value);

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

@ -7,11 +7,6 @@ use clap::Parser;
fn main() {
let options = doukutsu_rs::game::LaunchOptions::parse();
if options.server_mode && options.editor {
eprintln!("Cannot run in server mode and editor mode at the same time.");
exit(1);
}
let result = doukutsu_rs::game::init(options);
#[cfg(target_os = "windows")]

View file

@ -1,4 +1,6 @@
use std::cell::Cell;
use std::collections::HashMap;
use std::path::PathBuf;
use crate::common::{Color, Rect};
use crate::components::draw_common::{draw_number, Alignment};
@ -9,6 +11,7 @@ use crate::game::shared_game_state::{GameDifficulty, MenuCharacter, SharedGameSt
use crate::graphics::font::Font;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::menu::save_select_menu::MenuSaveInfo;
use crate::util::file_picker::{open_file_picker, FilePickerParams};
pub mod controls_menu;
pub mod coop_menu;
@ -17,6 +20,7 @@ pub mod save_select_menu;
pub mod settings_menu;
const MENU_MIN_PADDING: f32 = 30.0;
const MENU_DISABLED_COLOR: (u8, u8, u8, u8) = (0xa0, 0xa0, 0xff, 0xff);
#[derive(Clone, Debug)]
pub enum ControlMenuData {
@ -43,6 +47,7 @@ pub enum MenuEntry {
PlayerSkin,
Control(String, ControlMenuData),
Spacer(f64),
FilePicker(String, bool, FilePickerParams, Option<Vec<PathBuf>>), // text, display selected file names, params, selected files/dirs
}
impl MenuEntry {
@ -64,6 +69,7 @@ impl MenuEntry {
MenuEntry::PlayerSkin => 24.0,
MenuEntry::Control(_, _) => 16.0,
MenuEntry::Spacer(height) => *height,
MenuEntry::FilePicker(_, _, _, _) => 16.0,
}
}
@ -85,6 +91,7 @@ impl MenuEntry {
MenuEntry::PlayerSkin => true,
MenuEntry::Control(_, _) => true,
MenuEntry::Spacer(_) => false,
MenuEntry::FilePicker(_, _, _, _) => true,
}
}
}
@ -97,6 +104,7 @@ pub enum MenuSelectionResult<'a, T: std::cmp::PartialEq> {
Right(T, &'a mut MenuEntry, i16),
}
#[derive(Clone)]
pub struct Menu<T: std::cmp::PartialEq> {
pub x: isize,
pub y: isize,
@ -154,6 +162,26 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
}
}
pub fn get_entry(&self, id: T) -> Option<&MenuEntry> {
for i in 0..self.entries.len() {
if self.entries[i].0 == id {
return Some(&self.entries[i].1);
}
}
None
}
pub fn get_entry_mut(&mut self, id: T) -> Option<&mut MenuEntry> {
for i in 0..self.entries.len() {
if self.entries[i].0 == id {
return Some(&mut self.entries[i].1);
}
}
None
}
pub fn update_width(&mut self, state: &SharedGameState) {
let mut width = self.width as f32;
@ -216,7 +244,25 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
MenuEntry::NewSave => {}
MenuEntry::PlayerSkin => {}
MenuEntry::Control(_, _) => {}
MenuEntry::Spacer(_) => {}
MenuEntry::Spacer(_) => {},
MenuEntry::FilePicker(entry, display, _, selection) => {
let mut entry_with_selection = entry.clone();
if *display {
entry_with_selection.push_str(" ");
let filename = selection.as_ref()
.and_then(|files| files.first())
.and_then(|file| file.file_name())
.and_then(|filename| filename.to_str())
.unwrap_or(state.loc.t("common.choose_file"));
entry_with_selection.push_str(filename);
}
let entry_width = state.font.builder().compute_width(&entry_with_selection) + 32.0;
width = width.max(entry_width);
},
}
}
@ -353,21 +399,22 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
batch.draw(ctx)?;
let options_x = if self.center_options {
let mut longest_option_width = 20.0;
let mut longest_entry_width = 20.0;
for (_, entry) in &self.entries {
match entry {
let text_width = match entry {
MenuEntry::Options(text, _, _) | MenuEntry::Active(text) => {
let text_width = state.font.builder().compute_width(text) + 32.0;
if text_width > longest_option_width {
longest_option_width = text_width;
}
state.font.builder().compute_width(text) + 32.0
}
_ => {}
_ => 0.0
};
if text_width > longest_entry_width {
longest_entry_width = text_width;
}
}
(state.canvas_size.0 / 2.0) - (longest_option_width / 2.0)
(state.canvas_size.0 / 2.0) - (longest_entry_width / 2.0)
} else {
self.x as f32
};
@ -471,7 +518,7 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
let mut builder = state.font.builder().position(x, local_y);
if !*is_white {
builder = builder.color((0xa0, 0xa0, 0xff, 0xff));
builder = builder.color(MENU_DISABLED_COLOR);
}
builder.draw(&line, ctx, &state.constants, &mut state.texture_set)?;
@ -482,7 +529,7 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
y += entry.height() as f32 * (lines.len() - 1) as f32;
}
MenuEntry::Disabled(name) => {
state.font.builder().position(self.x as f32 + 20.0, y).color((0xa0, 0xa0, 0xff, 0xff)).draw(
state.font.builder().position(self.x as f32 + 20.0, y).color(MENU_DISABLED_COLOR).draw(
name,
ctx,
&state.constants,
@ -548,7 +595,7 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
.font
.builder()
.position(self.x as f32 + 20.0, y + 16.0)
.color((0xc0, 0xc0, 0xff, 0xff))
.color(MENU_DISABLED_COLOR)
.draw(description_text, ctx, &state.constants, &mut state.texture_set)?;
}
MenuEntry::OptionsBar(name, percent) => {
@ -739,6 +786,42 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
}
}
}
MenuEntry::FilePicker(name, display, _, selection) => {
// TODO: fix text is rendered out of the text box bounds if the filename is too long.
let name_text_len = state.font.builder().compute_width(name);
let value_text = selection.as_ref()
.and_then(|files| files.first())
.and_then(|file| file.file_name())
.and_then(|filename| filename.to_str())
.unwrap_or(state.loc.t("common.choose_file"));
// Draw the entry as disabled on unsupported platforms
let text_color = if cfg!(not(any(target_os = "android", target_os = "horizon"))) {
state.font.builder().get_color()
} else {
MENU_DISABLED_COLOR
};
state.font.builder().position(self.x as f32 + 20.0, y)
.draw(
name,
ctx,
&state.constants,
&mut state.texture_set,
)?;
if *display {
state.font.builder()
.position(self.x as f32 + 25.0 + name_text_len, y)
.draw(
value_text,
ctx,
&state.constants,
&mut state.texture_set,
)?;
}
}
_ => {}
}
@ -882,6 +965,18 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
return MenuSelectionResult::Selected(idx, entry);
}
}
MenuEntry::FilePicker(_, _, params, selection) => {
if self.selected == idx && controller.trigger_ok()
|| state.touch_controls.consume_click_in(entry_bounds)
{
state.sound_manager.play_sfx(18);
self.selected = idx.clone();
*selection = open_file_picker(params);
return MenuSelectionResult::Selected(idx, entry);
}
}
_ => {}
}
}

View file

@ -1,12 +1,15 @@
use pelite::pe::imports::Import;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::game::profile::GameProfile;
use crate::game::profile::{GameProfile, SaveContainer, SaveFormat, SaveParams, SaveSlot};
use crate::game::shared_game_state::{GameDifficulty, SharedGameState};
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::menu::coop_menu::PlayerCountMenu;
use crate::menu::MenuEntry;
use crate::menu::{Menu, MenuSelectionResult};
use crate::util::file_picker::{open_file_picker, FilePickerParams};
#[derive(Clone, Copy)]
pub struct MenuSaveInfo {
@ -33,12 +36,14 @@ pub enum CurrentMenu {
PlayerCountMenu,
DeleteConfirm,
LoadConfirm,
ImportExport,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SaveMenuEntry {
Load(usize),
New(usize),
ImportExport,
Back,
}
@ -87,8 +92,111 @@ impl Default for LoadConfirmMenuEntry {
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ImportExportMenuEntry {
Format,
Import,
Export,
Back,
}
#[derive(Clone, Copy)]
enum ImportExportLocation {
Filesystem,
// TODO: add some Switch emulators
}
impl Default for ImportExportLocation {
fn default() -> Self {
Self::Filesystem
}
}
#[derive(Clone)]
pub struct MenuExportInfo {
pub location: ImportExportLocation,
pub format: Option<SaveFormat>,
pub picker_params: FilePickerParams,
pub save_params: SaveParams,
}
impl Default for MenuExportInfo {
fn default() -> Self {
Self {
location: ImportExportLocation::Filesystem,
format: None,
picker_params: FilePickerParams::new(),
save_params: SaveParams::default()
}
}
}
impl MenuExportInfo {
fn fpicker_from_format(state: &SharedGameState, mut format: Option<SaveFormat>, is_export: bool) -> FilePickerParams {
if is_export && format.is_none() {
format = Some(state.settings.save_format);
}
let mut params = match format {
Some(SaveFormat::Freeware) => {
FilePickerParams::new()
.pick_dirs(true)
}
Some(SaveFormat::Plus) | Some(SaveFormat::Switch) => {
let filename = SaveContainer::get_save_filename(&format.unwrap(), None).split_off(1);
FilePickerParams::new()
.file_name(Some(filename))
.filter(state.loc.ts("menus.save_manage_menu.file_filters.plus"), vec![
"dat".to_owned()
])
}
Some(SaveFormat::Generic) => {
let filename = SaveContainer::get_save_filename(&format.unwrap(), None).split_off(1);
FilePickerParams::new()
.file_name(Some(filename))
.filter(state.loc.ts("menus.save_manage_menu.file_filters.generic"), vec![
"json".to_owned()
])
}
None => FilePickerParams::new()
};
if is_export && format != Some(SaveFormat::Freeware) {
params = params.save(true);
}
if let Some(fs_container) = &state.fs_container {
params = params.starting_dir(Some(fs_container.user_path.clone()));
}
params
}
}
impl ImportExportMenuEntry {
fn format_from_value(val: usize) -> Option<SaveFormat> {
match val {
0 => None, // Auto
1 => Some(SaveFormat::Freeware),
2 => Some(SaveFormat::Plus),
3 => Some(SaveFormat::Switch),
_ => unreachable!()
}
}
}
impl Default for ImportExportMenuEntry {
fn default() -> Self {
ImportExportMenuEntry::Format
}
}
pub struct SaveSelectMenu {
pub saves: [MenuSaveInfo; 3],
pub export_info: MenuExportInfo,
current_menu: CurrentMenu,
save_menu: Menu<SaveMenuEntry>,
save_detailed: Menu<usize>,
@ -97,12 +205,14 @@ pub struct SaveSelectMenu {
delete_confirm: Menu<DeleteConfirmMenuEntry>,
load_confirm: Menu<LoadConfirmMenuEntry>,
skip_difficulty_menu: bool,
import_export_menu: Menu<ImportExportMenuEntry>,
}
impl SaveSelectMenu {
pub fn new() -> SaveSelectMenu {
SaveSelectMenu {
saves: [MenuSaveInfo::default(); 3],
export_info: MenuExportInfo::default(),
current_menu: CurrentMenu::SaveMenu,
save_menu: Menu::new(0, 0, 230, 0),
coop_menu: PlayerCountMenu::new(),
@ -111,10 +221,11 @@ impl SaveSelectMenu {
delete_confirm: Menu::new(0, 0, 75, 0),
load_confirm: Menu::new(0, 0, 75, 0),
skip_difficulty_menu: false,
import_export_menu: Menu::new(0, 0, 75, 0),
}
}
pub fn init(&mut self, state: &mut SharedGameState, ctx: &Context) -> GameResult {
pub fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
self.save_menu = Menu::new(0, 0, 230, 0);
self.save_detailed = Menu::new(0, 0, 230, 0);
self.coop_menu.on_title = true;
@ -122,26 +233,36 @@ impl SaveSelectMenu {
self.difficulty_menu = Menu::new(0, 0, 130, 0);
self.delete_confirm = Menu::new(0, 0, 75, 0);
self.load_confirm = Menu::new(0, 0, 75, 0);
self.import_export_menu = Menu::new(0, 0, 75, 0);
self.skip_difficulty_menu = false;
let mut should_mutate_selection = true;
let save_container = SaveContainer::load(ctx, state)?;
for (iter, save) in self.saves.iter_mut().enumerate() {
if let Ok(data) = filesystem::user_open(ctx, state.get_save_filename(iter + 1).unwrap_or(String::new())) {
let loaded_save = GameProfile::load_from_save(data)?;
if let Some(slot) = state.get_save_slot(iter + 1) {
if let Some(loaded_profile) = save_container.get_profile(slot) {
log::trace!("Loading save select menu. Iter - {}. {}", iter, loaded_profile.is_empty());
save.current_map = loaded_profile.current_map;
save.max_life = loaded_profile.max_life;
save.life = loaded_profile.life;
save.weapon_count = loaded_profile.weapon_data.iter().filter(|weapon| weapon.weapon_id != 0).count();
save.weapon_id = loaded_profile.weapon_data.map(|weapon| weapon.weapon_id);
save.difficulty = loaded_profile.difficulty;
save.current_map = loaded_save.current_map;
save.max_life = loaded_save.max_life;
save.life = loaded_save.life;
save.weapon_count = loaded_save.weapon_data.iter().filter(|weapon| weapon.weapon_id != 0).count();
save.weapon_id = loaded_save.weapon_data.map(|weapon| weapon.weapon_id);
save.difficulty = loaded_save.difficulty;
self.save_menu.push_entry(SaveMenuEntry::Load(iter), MenuEntry::SaveData(*save));
self.save_menu.push_entry(SaveMenuEntry::Load(iter), MenuEntry::SaveData(*save));
if should_mutate_selection {
should_mutate_selection = false;
self.save_menu.selected = SaveMenuEntry::Load(iter);
}
} else {
self.save_menu.push_entry(SaveMenuEntry::New(iter), MenuEntry::NewSave);
if should_mutate_selection {
should_mutate_selection = false;
self.save_menu.selected = SaveMenuEntry::Load(iter);
if should_mutate_selection {
should_mutate_selection = false;
self.save_menu.selected = SaveMenuEntry::New(iter);
}
}
} else {
self.save_menu.push_entry(SaveMenuEntry::New(iter), MenuEntry::NewSave);
@ -153,6 +274,7 @@ impl SaveSelectMenu {
}
}
self.save_menu.push_entry(SaveMenuEntry::ImportExport, MenuEntry::Active(state.loc.ts("menus.save_manage_menu.import_export_save")));
self.save_menu.push_entry(SaveMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.difficulty_menu.push_entry(
@ -176,8 +298,6 @@ impl SaveSelectMenu {
self.difficulty_menu.selected = DifficultyMenuEntry::Difficulty(GameDifficulty::Normal);
//self.coop_menu.init(state, ctx);
self.delete_confirm.push_entry(
DeleteConfirmMenuEntry::Title,
MenuEntry::Disabled(state.loc.t("menus.save_menu.delete_confirm").to_owned()),
@ -206,6 +326,31 @@ impl SaveSelectMenu {
self.save_detailed.push_entry(0, MenuEntry::SaveDataSingle(save));
}
self.export_info.format = None;
self.import_export_menu.push_entry(
ImportExportMenuEntry::Format,
MenuEntry::Options(
state.loc.ts("menus.save_manage_menu.save_format.entry"),
0,
vec![
state.loc.ts("menus.save_manage_menu.save_format.auto"),
state.loc.ts("menus.save_manage_menu.save_format.freeware"),
state.loc.ts("menus.save_manage_menu.save_format.plus"),
state.loc.ts("menus.save_manage_menu.save_format.switch"),
]
)
);
self.import_export_menu.push_entry(
ImportExportMenuEntry::Import,
MenuEntry::Active(state.loc.ts("menus.save_manage_menu.action_type.import"))
);
self.import_export_menu.push_entry(
ImportExportMenuEntry::Export,
MenuEntry::Active(state.loc.ts("menus.save_manage_menu.action_type.export"))
);
self.import_export_menu.push_entry(ImportExportMenuEntry::Back, MenuEntry::Active(state.loc.ts("common.back")));
self.update_sizes(state);
Ok(())
@ -241,6 +386,11 @@ impl SaveSelectMenu {
self.save_detailed.update_height(state);
self.save_detailed.x = ((state.canvas_size.0 - self.save_detailed.width as f32) / 2.0).floor() as isize;
self.save_detailed.y = -40 + ((state.canvas_size.1 - self.save_detailed.height as f32) / 2.0).floor() as isize;
self.import_export_menu.update_width(state);
self.import_export_menu.update_height(state);
self.import_export_menu.x = ((state.canvas_size.0 - self.import_export_menu.width as f32) / 2.0).floor() as isize;
self.import_export_menu.y = ((state.canvas_size.1 - self.import_export_menu.height as f32) / 2.0).floor() as isize;
}
pub fn tick(
@ -267,17 +417,17 @@ impl SaveSelectMenu {
MenuSelectionResult::Selected(SaveMenuEntry::Load(slot), _) => {
state.save_slot = slot + 1;
if let Ok(_) =
filesystem::user_open(ctx, state.get_save_filename(state.save_slot).unwrap_or(String::new()))
{
if let (_, MenuEntry::SaveData(save)) = self.save_menu.entries[slot] {
self.save_detailed.entries.clear();
self.save_detailed.push_entry(0, MenuEntry::SaveDataSingle(save));
}
self.current_menu = CurrentMenu::LoadConfirm;
self.load_confirm.selected = LoadConfirmMenuEntry::Start;
if let (_, MenuEntry::SaveData(save)) = self.save_menu.entries[slot] {
self.save_detailed.entries.clear();
self.save_detailed.push_entry(0, MenuEntry::SaveDataSingle(save));
}
self.current_menu = CurrentMenu::LoadConfirm;
self.load_confirm.selected = LoadConfirmMenuEntry::Start;
}
MenuSelectionResult::Selected(SaveMenuEntry::ImportExport, _) => {
self.current_menu = CurrentMenu::ImportExport;
self.import_export_menu.selected = ImportExportMenuEntry::Back;
}
_ => (),
},
@ -308,7 +458,9 @@ impl SaveSelectMenu {
match self.save_menu.selected {
SaveMenuEntry::Load(slot) => {
state.sound_manager.play_sfx(17); // Player Death sfx
filesystem::user_delete(ctx, state.get_save_filename(slot + 1).unwrap_or(String::new()))?;
let mut save = SaveContainer::load(ctx, state)?;
save.delete_profile(&ctx, state.get_save_slot(slot + 1).unwrap());
save.save(ctx, state, SaveParams::default())?;
}
_ => (),
}
@ -340,6 +492,69 @@ impl SaveSelectMenu {
}
_ => (),
},
CurrentMenu::ImportExport => match self.import_export_menu.tick(controller, state) {
MenuSelectionResult::Selected(ImportExportMenuEntry::Format, toggle)
| MenuSelectionResult::Right(ImportExportMenuEntry::Format, toggle, _) => {
if let MenuEntry::Options(_, value, _) = toggle {
*value = match *value {
0..3 => *value + 1,
3 => 0,
_ => unreachable!(),
};
self.export_info.format = ImportExportMenuEntry::format_from_value(*value);
}
}
MenuSelectionResult::Left(ImportExportMenuEntry::Format, toggle, _) => {
if let MenuEntry::Options(_, value, _) = toggle {
*value = match *value {
1..=3 => *value - 1,
0 => 3,
_ => unreachable!(),
};
self.export_info.format = ImportExportMenuEntry::format_from_value(*value);
}
}
MenuSelectionResult::Selected(ImportExportMenuEntry::Import, _) => {
let picker_params = MenuExportInfo::fpicker_from_format(state, self.export_info.format, false);
let selection = open_file_picker(&picker_params);
let out_path = selection.and_then(|location| location.first().cloned());
if out_path.is_none() {
// Export path is not selected, so we break export operation
return Ok(());
}
log::trace!("{:?}", out_path);
let mut save_container = SaveContainer::load(ctx, state)?;
save_container.import(state, ctx, self.export_info.format, self.export_info.save_params.clone(), out_path.unwrap().clone())?;
save_container.save(ctx, state, self.export_info.save_params.clone())?;
}
MenuSelectionResult::Selected(ImportExportMenuEntry::Export, _) => {
let picker_params = MenuExportInfo::fpicker_from_format(state, self.export_info.format, true);
let selection = open_file_picker(&picker_params);
let out_path = selection.and_then(|location| location.first().cloned());
if out_path.is_none() {
// Export path is not selected, so we break export operation
return Ok(());
}
log::trace!("{:?}", out_path);
let format = self.export_info.format.unwrap_or(state.settings.save_format);
let mut save_container = SaveContainer::load(ctx, state)?;
save_container.export(state, ctx, format, self.export_info.save_params.clone(), out_path.unwrap().clone())?;
}
MenuSelectionResult::Selected(ImportExportMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
self.current_menu = CurrentMenu::SaveMenu;
}
_ => (),
},
}
Ok(())
@ -364,6 +579,9 @@ impl SaveSelectMenu {
self.save_detailed.draw(state, ctx)?;
self.load_confirm.draw(state, ctx)?;
}
CurrentMenu::ImportExport => {
self.import_export_menu.draw(state, ctx)?;
}
}
Ok(())
}

View file

@ -4,6 +4,7 @@ use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::VSyncMode;
use crate::framework::{filesystem, graphics};
use crate::game::profile::{SaveFormat};
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, SharedGameState, TimingMode, WindowMode};
use crate::graphics::font::Font;
use crate::input::combined_menu_controller::CombinedMenuController;
@ -118,6 +119,7 @@ enum BehaviorMenuEntry {
CutsceneSkipMode,
#[cfg(feature = "discord-rpc")]
DiscordRPC,
SaveFormat,
Back,
}
@ -595,6 +597,19 @@ impl SettingsMenu {
),
);
self.behavior.push_entry(
BehaviorMenuEntry::SaveFormat,
MenuEntry::Options(
state.loc.t("menus.options_menu.behavior_menu.save_format.entry").to_owned(),
state.settings.save_format as usize,
vec![
state.loc.t("menus.options_menu.behavior_menu.save_format.freeware").to_owned(),
state.loc.t("menus.options_menu.behavior_menu.save_format.plus").to_owned(),
state.loc.t("menus.options_menu.behavior_menu.save_format.switch").to_owned(),
],
),
);
self.behavior.push_entry(BehaviorMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.links.push_entry(LinksMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
@ -1056,6 +1071,35 @@ impl SettingsMenu {
}
}
}
MenuSelectionResult::Selected(BehaviorMenuEntry::SaveFormat, toggle)
| MenuSelectionResult::Right(BehaviorMenuEntry::SaveFormat, toggle, _) => {
if let MenuEntry::Options(_, value, _) = toggle {
let (new_mode, new_value) = match state.settings.save_format {
SaveFormat::Freeware => (SaveFormat::Plus, 1),
SaveFormat::Plus => (SaveFormat::Switch, 2),
SaveFormat::Switch => (SaveFormat::Freeware, 0),
_ => unreachable!()
};
state.settings.save_format = new_mode;
*value = new_value;
let _ = state.settings.save(ctx);
}
}
MenuSelectionResult::Left(BehaviorMenuEntry::SaveFormat, toggle, _) => {
if let MenuEntry::Options(_, value, _) = toggle {
let (new_mode, new_value) = match state.settings.save_format {
SaveFormat::Freeware => (SaveFormat::Switch, 2),
SaveFormat::Plus => (SaveFormat::Freeware, 0),
SaveFormat::Switch => (SaveFormat::Plus, 1),
_ => unreachable!()
};
state.settings.save_format = new_mode;
*value = new_value;
let _ = state.settings.save(ctx);
}
}
MenuSelectionResult::Selected(BehaviorMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
self.current = CurrentMenu::MainMenu;
}

View file

@ -8,7 +8,7 @@ use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::mod_requirements::ModRequirements;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ModInfo {
pub id: String,
pub requirement: Requirement,
@ -173,6 +173,7 @@ impl ModList {
description = "mod.txt not found".to_string();
}
log::debug!("CSP Mod Loaded: {:?}", ModInfo { id: id.clone(), requirement, priority, save_slot, path: path.clone(), name: name.clone(), description: description.clone(), valid });
mods.push(ModInfo { id, requirement, priority, save_slot, path, name, description, valid })
}
}
@ -182,8 +183,16 @@ impl ModList {
Ok(ModList { mods })
}
pub fn get_mod_info_from_path(&self, mod_path: String) -> Option<ModInfo> {
if let Some(mod_sel) = self.mods.iter().find(|x| x.path == mod_path).cloned() {
Some(mod_sel)
} else {
None
}
}
pub fn get_save_from_path(&self, mod_path: String) -> i32 {
if let Some(mod_sel) = self.mods.iter().find(|x| x.path == mod_path) {
if let Some(mod_sel) = self.get_mod_info_from_path(mod_path) {
mod_sel.save_slot
} else {
-1
@ -197,4 +206,8 @@ impl ModList {
"NoName"
}
}
pub fn get_mod_info_from_id(&self, mod_id: String) -> Option<ModInfo> {
self.mods.iter().find(|x| x.id == mod_id).cloned()
}
}

View file

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::rc::Rc;
use downcast::Downcast;
use imgui::{Condition, MenuItem, TabItem, TabItemFlags, Window};
use imgui::{Condition, TabItem, TabItemFlags, Window};
use crate::editor::{CurrentTool, EditorInstance};
use crate::framework::context::Context;
@ -121,7 +121,7 @@ trait ExtraWidgetsExt {
fn tool_button(&self, label: impl AsRef<str>, active: bool) -> bool;
}
impl ExtraWidgetsExt for imgui::Ui<'_> {
impl ExtraWidgetsExt for imgui::Ui {
fn tool_button(&self, label: impl AsRef<str>, active: bool) -> bool {
if active {
let color = self.style_color(imgui::StyleColor::ButtonActive);
@ -222,13 +222,13 @@ impl Scene for EditorScene {
menu_bar_size = (menu_bar_w, menu_bar_h);
if let Some(menu) = ui.begin_menu("File") {
if MenuItem::new("Open stage").shortcut("Ctrl+O").build(ui) {
if ui.menu_item_config("Open stage").shortcut("Ctrl+O").build() {
self.stage_list.show();
}
ui.separator();
if MenuItem::new("Exit editor").build(ui) {
if ui.menu_item("Exit editor") {
self.exit_editor(state);
}
@ -333,7 +333,7 @@ impl StageListWindow {
}
ui.push_item_width(-1.0);
ui.list_box("", &mut self.selected_stage, &stages, 14);
ui.list_box("##", &mut self.selected_stage, &stages, 14);
ui.disabled(self.selected_stage < 0, || {
if ui.button("Open") {
@ -341,7 +341,7 @@ impl StageListWindow {
}
ui.same_line();
if ui.button("Edit table entry") {}
//if ui.button("Edit table entry") {}
});
ui.same_line();

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;
@ -34,9 +34,9 @@ use crate::game::caret::CaretType;
use crate::game::frame::{Frame, UpdateTarget};
use crate::game::inventory::{Inventory, TakeExperienceResult};
use crate::game::map::WaterParams;
use crate::game::npc::boss::BossNPC;
use crate::game::npc::list::NPCList;
use crate::game::npc::{NPCLayer, NPC};
use crate::game::npc::boss::{BossNPC, BossNPCContext};
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};
use crate::game::scripting::tsc::credit_script::CreditScriptVM;
@ -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() {
@ -1120,8 +1124,7 @@ impl GameScene {
let mut droplet = NPC::create(73, &state.npc_table);
droplet.cond.set_alive(true);
droplet.y = npc.y;
droplet.direction =
if npc.flags.bloody_droplets() { Direction::Right } else { Direction::Left };
droplet.direction = if npc.flags.bloody_droplets() { Direction::Right } else { Direction::Left };
for _ in 0..7 {
droplet.x = npc.x + (npc.rng.range(-8..8) * 0x200) as i32;
@ -1145,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() {
@ -1229,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;
@ -1376,29 +1383,34 @@ 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,
(
[&mut self.player1, &mut self.player2],
&self.npc_list,
&mut self.stage,
&mut self.bullet_manager,
&mut self.flash,
&mut self.boss,
),
)?;
}
NPCContext {
players: [&mut self.player1, &mut self.player2],
npc_list: &self.npc_list,
stage: &mut self.stage,
bullet_manager: &mut self.bullet_manager,
flash: &mut self.flash,
boss: &mut self.boss,
},
))?;
ControlFlow::Continue(())
})?;
self.boss.tick(
state,
(
[&mut self.player1, &mut self.player2],
&self.npc_list,
&mut self.stage,
&self.bullet_manager,
&mut self.flash,
),
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);
@ -1408,6 +1420,7 @@ impl GameScene {
TargetPlayer::Player1,
state,
&self.npc_list,
&mut self.npc_token,
&mut self.boss,
&mut self.inventory_player1,
);
@ -1415,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);
@ -1501,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;
@ -1524,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() {
@ -1542,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;
@ -1625,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()) {
@ -1928,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() {
@ -2181,7 +2198,9 @@ impl Scene for GameScene {
self.falling_island.draw(state, ctx, &self.frame)?;
self.text_boxes.draw(state, ctx, &self.frame)?;
if (self.skip_counter > 1 || state.tutorial_counter > 0) && (state.settings.cutscene_skip_mode != CutsceneSkipMode::Auto) {
if (self.skip_counter > 1 || state.tutorial_counter > 0)
&& (state.settings.cutscene_skip_mode != CutsceneSkipMode::Auto)
{
let key = {
if state.settings.touch_controls {
">>".to_owned()

86
src/util/file_picker.rs Normal file
View file

@ -0,0 +1,86 @@
use std::collections::HashMap;
use std::path::PathBuf;
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
use rfd::FileDialog;
#[derive(Clone, Debug, Default)]
pub struct FilePickerParams {
pub save: bool, /// Opens save file dialog
pub multiple: bool,
pub pick_dirs: bool,
pub file_name: Option<String>, /// Starting file name.
pub starting_dir: Option<PathBuf>,
pub filters: HashMap<String, Vec<String>>, // filter name -> file extensions
}
impl FilePickerParams {
pub fn new() -> Self {
Self::default()
}
pub fn save(mut self, save: bool) -> Self {
self.save = save;
self
}
pub fn multiple(mut self, multiple: bool) -> Self {
self.multiple = multiple;
self
}
pub fn pick_dirs(mut self, pick_dirs: bool) -> Self {
self.pick_dirs = pick_dirs;
self
}
pub fn file_name(mut self, file_name: Option<String>) -> Self {
self.file_name = file_name;
self
}
pub fn starting_dir(mut self, starting_dir: Option<PathBuf>) -> Self {
self.starting_dir = starting_dir;
self
}
pub fn filter(mut self, name: String, ext: Vec<String>) -> Self {
self.filters.insert(name, ext);
self
}
}
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
pub fn open_file_picker(params: &FilePickerParams) -> Option<Vec<PathBuf>> {
log::trace!("Call a file picker dialog with params: {:?}", params.clone());
let mut dialog = FileDialog::new();
if let Some(filename) = params.file_name.clone() {
dialog = dialog.set_file_name(filename);
}
if let Some(dir) = params.starting_dir.clone() {
dialog = dialog.set_directory(dir);
}
for filter in params.filters.iter() {
dialog = dialog.add_filter(filter.0, filter.1);
}
let selected_files = match (params.pick_dirs, params.multiple, params.save) {
(_, _, true) => dialog.save_file().map(|path| vec![path]),
(true, false, _) => dialog.pick_folder().map(|path| vec![path]),
(true, true, _) => dialog.pick_folders(),
(false, false, _) => dialog.pick_file().map(|path| vec![path]),
(false, true, _) => dialog.pick_files(),
};
log::trace!("Selected file entries: {:?}", selected_files.clone());
return selected_files;
}
#[cfg(any(target_os = "android", target_os = "horizon"))]
pub fn open_file_picker(params: &FileChooserParams) -> Option<Vec<PathBuf>> {
None
}

View file

@ -1,3 +1,4 @@
pub mod bitvec;
pub mod browser;
pub mod file_picker;
pub mod rng;