1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-11-30 08:08:18 +00:00

normal ending...

This commit is contained in:
Alula 2021-12-02 06:57:44 +01:00
parent 1b424f0b80
commit eaaa11d4f6
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
39 changed files with 6930 additions and 557 deletions

View file

@ -196,11 +196,11 @@ impl Caret {
}
match self.direction {
Direction::Left => self.x -= 0x400, // 2.0fix9
Direction::Left => self.x -= 0x400,
Direction::Up => self.y -= 0x400,
Direction::Right => self.x += 0x400,
Direction::Bottom => self.y += 0x400,
Direction::FacingPlayer => {}
Direction::FacingPlayer => (),
}
}
CaretType::DrownedQuote => {
@ -302,8 +302,8 @@ impl Caret {
if self.anim_num == 0 {
match self.direction {
Direction::Left => {
self.vel_x = rng.range(-0x600..0x600) as i32; // -3.0fix9..3.0fix9
self.vel_y = rng.range(-0x200..0x200) as i32; // -1.0fix9..1.0fix9
self.vel_x = rng.range(-0x600..0x600) as i32;
self.vel_y = rng.range(-0x200..0x200) as i32;
}
Direction::Up => {
self.vel_y = rng.range(-3..-1) as i32 * 0x200;

View file

@ -530,3 +530,29 @@ impl From<Color> for [f32; 4] {
[color.r, color.g, color.b, color.a]
}
}
pub trait SliceExt {
type Item;
fn get_two_mut(&mut self, a: usize, b: usize) -> Option<(&mut Self::Item, &mut Self::Item)>;
}
impl<T> SliceExt for [T] {
type Item = T;
fn get_two_mut(&mut self, a: usize, b: usize) -> Option<(&mut Self::Item, &mut Self::Item)> {
if a == b {
None
} else {
if a >= self.len() || b >= self.len() {
None
} else {
unsafe {
let ar = &mut *(self.get_unchecked_mut(a) as *mut _);
let br = &mut *(self.get_unchecked_mut(b) as *mut _);
Some((ar, br))
}
}
}
}
}

View file

@ -0,0 +1,56 @@
use crate::common::{Color, Rect};
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::graphics;
use crate::scripting::tsc::text_script::TextScriptExecutionState;
use crate::shared_game_state::SharedGameState;
pub struct FallingIsland {}
impl FallingIsland {
pub fn new() -> FallingIsland {
FallingIsland {}
}
}
impl GameEntity<()> for FallingIsland {
fn tick(&mut self, state: &mut SharedGameState, custom: ()) -> GameResult {
Ok(())
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
let (pos_x, pos_y) =
if let TextScriptExecutionState::FallingIsland(_, _, pos_x, pos_y, _, _) = state.textscript_vm.state {
(pos_x, pos_y)
} else {
return Ok(());
};
let off_x = (state.canvas_size.0 - 320.0) * 0.5;
let clip_rect: Rect = Rect::new_size(
((off_x + 80.0) * state.scale) as _,
(80.0 * state.scale) as _,
(160.0 * state.scale) as _,
(80.0 * state.scale) as _,
);
graphics::clear(ctx, Color::from_rgb(0, 0, 32));
graphics::set_clip_rect(ctx, Some(clip_rect))?;
static RECT_BG: Rect<u16> = Rect { left: 0, top: 0, right: 160, bottom: 80 };
static RECT_ISLAND: Rect<u16> = Rect { left: 160, top: 0, right: 200, bottom: 24 };
static RECT_TERRAIN: Rect<u16> = Rect { left: 160, top: 48, right: 320, bottom: 80 };
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, &state.npc_table.tex_npc1_name)?;
batch.add_rect(off_x + 80.0, 80.0, &RECT_BG);
batch.add_rect(off_x + (pos_x as f32 / 512.0) - 20.0, (pos_y as f32 / 512.0) - 12.0, &RECT_ISLAND);
batch.add_rect(off_x + 80.0, 128.0, &RECT_TERRAIN);
batch.draw(ctx)?;
graphics::set_clip_rect(ctx, None)?;
Ok(())
}
}

View file

@ -1,6 +1,7 @@
pub mod boss_life_bar;
pub mod credits;
pub mod draw_common;
pub mod falling_island;
pub mod flash;
pub mod hud;
pub mod inventory;

View file

@ -5,7 +5,6 @@ use crate::common::Rect;
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct NPCConsts {
// pub n000_null: () // Defined in code
#[serde(default = "default_n001_experience")]
pub n001_experience: [Rect<u16>; 6],
@ -13,7 +12,6 @@ pub struct NPCConsts {
pub n002_behemoth: [Rect<u16>; 14],
// pub n003_dead_enemy: () // Defined in code
#[serde(default = "default_n004_smoke")]
pub n004_smoke: [Rect<u16>; 16],
@ -141,7 +139,6 @@ pub struct NPCConsts {
pub n045_baby: [Rect<u16>; 3],
// pub n046_hv_trigger: () // Defined in code
#[serde(default = "default_n047_sandcroc")]
pub n047_sandcroc: [Rect<u16>; 5],
@ -230,7 +227,6 @@ pub struct NPCConsts {
pub n075_kanpachi: [Rect<u16>; 2],
// pub n076_flowers: () // Defined in code
#[serde(default = "default_n077_yamashita")]
pub n077_yamashita: [Rect<u16>; 3],
@ -319,7 +315,6 @@ pub struct NPCConsts {
pub n105_hey_bubble_low: [Rect<u16>; 2],
// pub n106_hey_bubble_high: () // Defined in code
#[serde(default = "default_n107_malco_broken")]
pub n107_malco_broken: [Rect<u16>; 10],
@ -456,7 +451,6 @@ pub struct NPCConsts {
pub n151_blue_robot_standing: [Rect<u16>; 4],
// pub n152_shutter_stuck: () // Defined in code
#[serde(default = "default_n153_gaudi")]
pub n153_gaudi: [Rect<u16>; 14],
@ -572,7 +566,6 @@ pub struct NPCConsts {
pub n190_broken_robot: [Rect<u16>; 2],
// pub n191_water_level: () // Defined in code
#[serde(default = "default_n192_scooter")]
pub n192_scooter: [Rect<u16>; 4],
@ -655,7 +648,6 @@ pub struct NPCConsts {
pub n218_core_giant_ball: [Rect<u16>; 2],
// pub n219_smoke_generator: () // Defined in code
#[serde(default = "default_n220_shovel_brigade")]
pub n220_shovel_brigade: [Rect<u16>; 4],
@ -695,8 +687,8 @@ pub struct NPCConsts {
#[serde(default = "default_n232_orangebell")]
pub n232_orangebell: [Rect<u16>; 6],
#[serde(default = "default_n233_orangebell_hat")]
pub n233_orangebell_hat: [Rect<u16>; 8],
#[serde(default = "default_n233_orangebell_bat")]
pub n233_orangebell_bat: [Rect<u16>; 8],
#[serde(default = "default_n234_red_flowers_picked")]
pub n234_red_flowers_picked: [Rect<u16>; 2],
@ -726,7 +718,6 @@ pub struct NPCConsts {
pub n242_bat_last_cave: [Rect<u16>; 8],
// pub n243_bat_generator: () // Defined in code
#[serde(default = "default_n244_lava_drop")]
pub n244_lava_drop: Rect<u16>,
@ -742,8 +733,8 @@ pub struct NPCConsts {
#[serde(default = "default_n248_misery_boss_vanishing")]
pub n248_misery_boss_vanishing: [Rect<u16>; 3],
#[serde(default = "default_n249_misery_boss_energy_shot")]
pub n249_misery_boss_energy_shot: [Rect<u16>; 2],
#[serde(default = "default_n249_misery_boss_appearing")]
pub n249_misery_boss_appearing: [Rect<u16>; 2],
#[serde(default = "default_n250_misery_boss_lighting_ball")]
pub n250_misery_boss_lighting_ball: [Rect<u16>; 3],
@ -787,14 +778,14 @@ pub struct NPCConsts {
#[serde(default = "default_n263_doctor_boss")]
pub n263_doctor_boss: [Rect<u16>; 18],
#[serde(default = "default_n264_doctor_boss_red_wave_projectile")]
pub n264_doctor_boss_red_wave_projectile: Rect<u16>,
#[serde(default = "default_n264_doctor_boss_red_projectile")]
pub n264_doctor_boss_red_projectile: Rect<u16>,
#[serde(default = "default_n265_doctor_boss_red_ball_projectile")]
pub n265_doctor_boss_red_ball_projectile: [Rect<u16>; 3],
#[serde(default = "default_n265_doctor_boss_red_projectile_trail")]
pub n265_doctor_boss_red_projectile_trail: [Rect<u16>; 3],
#[serde(default = "default_n266_doctor_boss_red_ball_projectile_bouncing")]
pub n266_doctor_boss_red_ball_projectile_bouncing: [Rect<u16>; 2],
#[serde(default = "default_n266_doctor_boss_red_projectile_bouncing")]
pub n266_doctor_boss_red_projectile_bouncing: [Rect<u16>; 2],
#[serde(default = "default_n267_muscle_doctor")]
pub n267_muscle_doctor: [Rect<u16>; 20],
@ -811,7 +802,6 @@ pub struct NPCConsts {
// pub n271_ironhead_block: () // Defined in code
// pub n272_ironhead_block_generator: () // Defined in code
#[serde(default = "default_n273_droll_projectile")]
pub n273_droll_projectile: [Rect<u16>; 3],
@ -837,7 +827,6 @@ pub struct NPCConsts {
pub n280_sue_teleported: [Rect<u16>; 4],
// pub n281_doctor_energy_form: () // Defined in code
#[serde(default = "default_n282_mini_undead_core_active")]
pub n282_mini_undead_core_active: [Rect<u16>; 3],
@ -869,17 +858,14 @@ pub struct NPCConsts {
pub n291_mini_undead_core_inactive: [Rect<u16>; 2],
// pub n292_quake: () // Defined in code
#[serde(default = "default_n293_undead_core_energy_shot")]
pub n293_undead_core_energy_shot: [Rect<u16>; 2],
// pub n294_quake_falling_block_generator: () // Defined in code
#[serde(default = "default_n295_cloud")]
pub n295_cloud: [Rect<u16>; 4],
// pub n296_cloud_generator: () // Defined in code
#[serde(default = "default_n297_sue_dragon_mouth")]
pub n297_sue_dragon_mouth: Rect<u16>,
@ -896,7 +882,6 @@ pub struct NPCConsts {
pub n301_misery_fish_missile: [Rect<u16>; 8],
// pub n302_camera_focus_marker: () // Defined in code
#[serde(default = "default_n303_curly_machine_gun")]
pub n303_curly_machine_gun: [Rect<u16>; 4],
@ -961,7 +946,6 @@ pub struct NPCConsts {
pub n323_bute_spinning: [Rect<u16>; 4],
// pub n324_bute_generator: () // Defined in code
#[serde(default = "default_n325_heavy_press_lighting")]
pub n325_heavy_press_lighting: [Rect<u16>; 7],
@ -996,7 +980,6 @@ pub struct NPCConsts {
pub n335_ikachan: [Rect<u16>; 3],
// pub n336_ikachan_generator: () // Defined in code
#[serde(default = "default_n337_numahachi")]
pub n337_numahachi: [Rect<u16>; 2],
@ -1004,7 +987,6 @@ pub struct NPCConsts {
pub n338_green_devil: [Rect<u16>; 4],
// pub n339_green_devil_generator: () // Defined in code
#[serde(default = "default_n340_ballos")]
pub n340_ballos: [Rect<u16>; 22],
@ -1048,7 +1030,6 @@ pub struct NPCConsts {
pub n353_bute_sword_flying: [Rect<u16>; 8],
// pub n354_invisible_deathtrap_wall: () // Defined in code
#[serde(default = "default_n355_quote_and_curly_on_balrog")]
pub n355_quote_and_curly_on_balrog: [Rect<u16>; 4],
@ -1062,7 +1043,6 @@ pub struct NPCConsts {
pub n358_misery_credits: [Rect<u16>; 5],
// pub n359_water_droplet_generator: () // Defined in code
#[serde(default = "default_n360_credits_thank_you")]
pub n360_credits_thank_you: Rect<u16>,
@ -1077,6 +1057,15 @@ pub struct NPCConsts {
#[serde(default = "default_b04_core")]
pub b04_core: [Rect<u16>; 10],
#[serde(default = "default_b05_ironhead")]
pub b05_ironhead: [Rect<u16>; 18],
#[serde(default = "default_b06_sisters")]
pub b06_sisters: [Rect<u16>; 14],
#[serde(default = "default_b07_undead_core")]
pub b07_undead_core: [Rect<u16>; 15],
}
fn default_n001_experience() -> [Rect<u16>; 6] {
@ -1280,17 +1269,11 @@ fn default_n016_save_point() -> [Rect<u16>; 8] {
}
fn default_n017_health_refill() -> [Rect<u16>; 2] {
[
Rect { left: 288, top: 0, right: 304, bottom: 16 },
Rect { left: 304, top: 0, right: 320, bottom: 16 },
]
[Rect { left: 288, top: 0, right: 304, bottom: 16 }, Rect { left: 304, top: 0, right: 320, bottom: 16 }]
}
fn default_n018_door() -> [Rect<u16>; 2] {
[
Rect { left: 224, top: 16, right: 240, bottom: 40 },
Rect { left: 192, top: 112, right: 208, bottom: 136 },
]
[Rect { left: 224, top: 16, right: 240, bottom: 40 }, Rect { left: 192, top: 112, right: 208, bottom: 136 }]
}
fn default_n019_balrog_bust_in() -> [Rect<u16>; 8] {
@ -1320,10 +1303,7 @@ fn default_n021_chest_open() -> Rect<u16> {
}
fn default_n022_teleporter() -> [Rect<u16>; 2] {
[
Rect { left: 240, top: 16, right: 264, bottom: 48 },
Rect { left: 248, top: 152, right: 272, bottom: 184 },
]
[Rect { left: 240, top: 16, right: 264, bottom: 48 }, Rect { left: 248, top: 152, right: 272, bottom: 184 }]
}
fn default_n023_teleporter_lights() -> [Rect<u16>; 8] {
@ -1357,10 +1337,7 @@ fn default_n024_power_critter() -> [Rect<u16>; 12] {
}
fn default_n025_lift() -> [Rect<u16>; 2] {
[
Rect { left: 256, top: 64, right: 288, bottom: 80 },
Rect { left: 256, top: 80, right: 288, bottom: 96 },
]
[Rect { left: 256, top: 64, right: 288, bottom: 80 }, Rect { left: 256, top: 80, right: 288, bottom: 96 }]
}
fn default_n026_bat_flying() -> [Rect<u16>; 8] {
@ -1430,24 +1407,15 @@ fn default_n031_bat_hanging() -> [Rect<u16>; 10] {
}
fn default_n032_life_capsule() -> [Rect<u16>; 2] {
[
Rect { left: 32, top: 96, right: 48, bottom: 112 },
Rect { left: 48, top: 96, right: 64, bottom: 112 },
]
[Rect { left: 32, top: 96, right: 48, bottom: 112 }, Rect { left: 48, top: 96, right: 64, bottom: 112 }]
}
fn default_n033_balrog_bouncing_projectile() -> [Rect<u16>; 2] {
[
Rect { left: 240, top: 64, right: 256, bottom: 80 },
Rect { left: 240, top: 80, right: 256, bottom: 96 },
]
[Rect { left: 240, top: 64, right: 256, bottom: 80 }, Rect { left: 240, top: 80, right: 256, bottom: 96 }]
}
fn default_n034_bed() -> [Rect<u16>; 2] {
[
Rect { left: 192, top: 48, right: 224, bottom: 64 },
Rect { left: 192, top: 184, right: 224, bottom: 200 },
]
[Rect { left: 192, top: 48, right: 224, bottom: 64 }, Rect { left: 192, top: 184, right: 224, bottom: 200 }]
}
fn default_n035_mannan() -> [Rect<u16>; 8] {
@ -1481,10 +1449,7 @@ fn default_n036_balrog_hover() -> [Rect<u16>; 12] {
}
fn default_n037_sign() -> [Rect<u16>; 2] {
[
Rect { left: 192, top: 64, right: 208, bottom: 80 },
Rect { left: 208, top: 64, right: 224, bottom: 80 },
]
[Rect { left: 192, top: 64, right: 208, bottom: 80 }, Rect { left: 208, top: 64, right: 224, bottom: 80 }]
}
fn default_n038_fireplace() -> [Rect<u16>; 4] {
@ -1497,10 +1462,7 @@ fn default_n038_fireplace() -> [Rect<u16>; 4] {
}
fn default_n039_save_sign() -> [Rect<u16>; 2] {
[
Rect { left: 224, top: 64, right: 240, bottom: 80 },
Rect { left: 240, top: 64, right: 256, bottom: 80 },
]
[Rect { left: 224, top: 64, right: 240, bottom: 80 }, Rect { left: 240, top: 64, right: 256, bottom: 80 }]
}
fn default_n040_santa() -> [Rect<u16>; 14] {
@ -1558,10 +1520,7 @@ fn default_n042_sue() -> [Rect<u16>; 26] {
}
fn default_n043_chalkboard() -> [Rect<u16>; 2] {
[
Rect { left: 128, top: 80, right: 168, bottom: 112 },
Rect { left: 168, top: 80, right: 208, bottom: 112 },
]
[Rect { left: 128, top: 80, right: 168, bottom: 112 }, Rect { left: 168, top: 80, right: 208, bottom: 112 }]
}
fn default_n044_polish() -> [Rect<u16>; 6] {
@ -1912,10 +1871,7 @@ fn default_n071_chinfish() -> [Rect<u16>; 6] {
}
fn default_n072_sprinkler() -> [Rect<u16>; 2] {
[
Rect { left: 224, top: 48, right: 240, bottom: 64 },
Rect { left: 240, top: 48, right: 256, bottom: 64 },
]
[Rect { left: 224, top: 48, right: 240, bottom: 64 }, Rect { left: 240, top: 48, right: 256, bottom: 64 }]
}
fn default_n073_water_droplet() -> [Rect<u16>; 5] {
@ -1946,10 +1902,7 @@ fn default_n074_jack() -> [Rect<u16>; 12] {
}
fn default_n075_kanpachi() -> [Rect<u16>; 2] {
[
Rect { left: 272, top: 32, right: 296, bottom: 56 },
Rect { left: 296, top: 32, right: 320, bottom: 56 },
]
[Rect { left: 272, top: 32, right: 296, bottom: 56 }, Rect { left: 296, top: 32, right: 320, bottom: 56 }]
}
fn default_n077_yamashita() -> [Rect<u16>; 3] {
@ -1961,10 +1914,7 @@ fn default_n077_yamashita() -> [Rect<u16>; 3] {
}
fn default_n078_pot() -> [Rect<u16>; 2] {
[
Rect { left: 160, top: 48, right: 176, bottom: 64 },
Rect { left: 176, top: 48, right: 192, bottom: 64 },
]
[Rect { left: 160, top: 48, right: 176, bottom: 64 }, Rect { left: 176, top: 48, right: 192, bottom: 64 }]
}
fn default_n079_mahin() -> [Rect<u16>; 6] {
@ -2231,10 +2181,7 @@ fn default_n099_fan_down() -> [Rect<u16>; 3] {
}
fn default_n100_grate() -> [Rect<u16>; 2] {
[
Rect { left: 272, top: 48, right: 288, bottom: 64 },
Rect { left: 272, top: 48, right: 288, bottom: 64 },
]
[Rect { left: 272, top: 48, right: 288, bottom: 64 }, Rect { left: 272, top: 48, right: 288, bottom: 64 }]
}
fn default_n101_malco_screen() -> [Rect<u16>; 3] {
@ -2277,10 +2224,7 @@ fn default_n104_frog() -> [Rect<u16>; 6] {
}
fn default_n105_hey_bubble_low() -> [Rect<u16>; 2] {
[
Rect { left: 128, top: 32, right: 144, bottom: 48 },
Rect { left: 128, top: 32, right: 128, bottom: 32 },
]
[Rect { left: 128, top: 32, right: 144, bottom: 48 }, Rect { left: 128, top: 32, right: 128, bottom: 32 }]
}
fn default_n107_malco_broken() -> [Rect<u16>; 10] {
@ -2445,10 +2389,7 @@ fn default_n119_table_chair() -> Rect<u16> {
}
fn default_n120_colon_a() -> [Rect<u16>; 2] {
[
Rect { left: 64, top: 0, right: 80, bottom: 16 },
Rect { left: 64, top: 16, right: 80, bottom: 32 },
]
[Rect { left: 64, top: 0, right: 80, bottom: 16 }, Rect { left: 64, top: 16, right: 80, bottom: 32 }]
}
fn default_n121_colon_b() -> [Rect<u16>; 3] {
@ -2494,17 +2435,11 @@ fn default_n123_curly_boss_bullet() -> [Rect<u16>; 4] {
}
fn default_n124_sunstone() -> [Rect<u16>; 2] {
[
Rect { left: 160, top: 0, right: 192, bottom: 32 },
Rect { left: 192, top: 0, right: 224, bottom: 32 },
]
[Rect { left: 160, top: 0, right: 192, bottom: 32 }, Rect { left: 192, top: 0, right: 224, bottom: 32 }]
}
fn default_n125_hidden_item() -> [Rect<u16>; 2] {
[
Rect { left: 0, top: 96, right: 16, bottom: 112 },
Rect { left: 16, top: 96, right: 32, bottom: 112 },
]
[Rect { left: 0, top: 96, right: 16, bottom: 112 }, Rect { left: 16, top: 96, right: 32, bottom: 112 }]
}
fn default_n126_puppy_running() -> [Rect<u16>; 12] {
@ -2597,10 +2532,7 @@ fn default_n130_puppy_sitting() -> [Rect<u16>; 8] {
}
fn default_n131_puppy_sleeping() -> [Rect<u16>; 2] {
[
Rect { left: 144, top: 144, right: 160, bottom: 160 },
Rect { left: 144, top: 160, right: 160, bottom: 176 },
]
[Rect { left: 144, top: 144, right: 160, bottom: 160 }, Rect { left: 144, top: 160, right: 160, bottom: 176 }]
}
fn default_n132_puppy_barking() -> [Rect<u16>; 10] {
@ -2661,10 +2593,7 @@ fn default_n137_large_door_frame() -> Rect<u16> {
}
fn default_n138_large_door() -> [Rect<u16>; 2] {
[
Rect { left: 96, top: 112, right: 112, bottom: 136 },
Rect { left: 112, top: 112, right: 128, bottom: 136 },
]
[Rect { left: 96, top: 112, right: 112, bottom: 136 }, Rect { left: 112, top: 112, right: 128, bottom: 136 }]
}
fn default_n139_doctor() -> [Rect<u16>; 6] {
@ -2712,10 +2641,7 @@ fn default_n140_toroko_frenzied() -> [Rect<u16>; 28] {
}
fn default_n141_toroko_block_projectile() -> [Rect<u16>; 2] {
[
Rect { left: 288, top: 32, right: 304, bottom: 48 },
Rect { left: 304, top: 32, right: 320, bottom: 48 },
]
[Rect { left: 288, top: 32, right: 304, bottom: 48 }, Rect { left: 304, top: 32, right: 320, bottom: 48 }]
}
fn default_n142_flower_cub() -> [Rect<u16>; 5] {
@ -2729,10 +2655,7 @@ fn default_n142_flower_cub() -> [Rect<u16>; 5] {
}
fn default_n143_jenka_collapsed() -> [Rect<u16>; 2] {
[
Rect { left: 208, top: 32, right: 224, bottom: 48 },
Rect { left: 208, top: 48, right: 224, bottom: 64 },
]
[Rect { left: 208, top: 32, right: 224, bottom: 48 }, Rect { left: 208, top: 48, right: 224, bottom: 64 }]
}
fn default_n144_toroko_teleporting_in() -> [Rect<u16>; 10] {
@ -2751,10 +2674,7 @@ fn default_n144_toroko_teleporting_in() -> [Rect<u16>; 10] {
}
fn default_n145_king_sword() -> [Rect<u16>; 2] {
[
Rect { left: 96, top: 32, right: 112, bottom: 48 },
Rect { left: 112, top: 32, right: 128, bottom: 48 },
]
[Rect { left: 96, top: 32, right: 112, bottom: 48 }, Rect { left: 112, top: 32, right: 128, bottom: 48 }]
}
fn default_n146_lighting() -> [Rect<u16>; 5] {
@ -2785,10 +2705,7 @@ fn default_n147_critter_purple() -> [Rect<u16>; 12] {
}
fn default_n148_critter_purple_projectile() -> [Rect<u16>; 2] {
[
Rect { left: 96, top: 96, right: 104, bottom: 104 },
Rect { left: 104, top: 96, right: 112, bottom: 104 },
]
[Rect { left: 96, top: 96, right: 104, bottom: 104 }, Rect { left: 104, top: 96, right: 112, bottom: 104 }]
}
fn default_n149_horizontal_moving_block() -> Rect<u16> {
@ -2829,19 +2746,19 @@ fn default_n151_blue_robot_standing() -> [Rect<u16>; 4] {
fn default_n153_gaudi() -> [Rect<u16>; 14] {
[
Rect { left: 0, top: 0, right: 24, bottom: 24 }, // 0 0 // left
Rect { left: 24, top: 0, right: 48, bottom: 24 }, // 1 1
Rect { left: 48, top: 0, right: 72, bottom: 24 }, // 2 2
Rect { left: 0, top: 0, right: 24, bottom: 24 }, // 3 3
Rect { left: 72, top: 0, right: 96, bottom: 24 }, // 4 4
Rect { left: 0, top: 0, right: 24, bottom: 24 }, // 5 5
Rect { left: 0, top: 0, right: 24, bottom: 24 }, // 0 0 // left
Rect { left: 24, top: 0, right: 48, bottom: 24 }, // 1 1
Rect { left: 48, top: 0, right: 72, bottom: 24 }, // 2 2
Rect { left: 0, top: 0, right: 24, bottom: 24 }, // 3 3
Rect { left: 72, top: 0, right: 96, bottom: 24 }, // 4 4
Rect { left: 0, top: 0, right: 24, bottom: 24 }, // 5 5
Rect { left: 96, top: 48, right: 120, bottom: 72 }, // 6 20
Rect { left: 0, top: 24, right: 24, bottom: 48 }, // 0 0 // right
Rect { left: 24, top: 24, right: 48, bottom: 48 }, // 1 1
Rect { left: 48, top: 24, right: 72, bottom: 48 }, // 2 2
Rect { left: 0, top: 24, right: 24, bottom: 48 }, // 3 3
Rect { left: 72, top: 24, right: 96, bottom: 48 }, // 4 4
Rect { left: 0, top: 24, right: 24, bottom: 48 }, // 5 5
Rect { left: 0, top: 24, right: 24, bottom: 48 }, // 0 0 // right
Rect { left: 24, top: 24, right: 48, bottom: 48 }, // 1 1
Rect { left: 48, top: 24, right: 72, bottom: 48 }, // 2 2
Rect { left: 0, top: 24, right: 24, bottom: 48 }, // 3 3
Rect { left: 72, top: 24, right: 96, bottom: 48 }, // 4 4
Rect { left: 0, top: 24, right: 24, bottom: 48 }, // 5 5
Rect { left: 96, top: 72, right: 120, bottom: 96 }, // 6 20
]
}
@ -2859,14 +2776,14 @@ fn default_n154_gaudi_dead() -> [Rect<u16>; 6] {
fn default_n155_gaudi_flying() -> [Rect<u16>; 8] {
[
Rect { left: 0, top: 48, right: 24, bottom: 72 }, // 0 14 // left
Rect { left: 24, top: 48, right: 48, bottom: 72 }, // 1 15
Rect { left: 288, top: 0, right: 312, bottom: 24 }, // 2 18
Rect { left: 24, top: 48, right: 48, bottom: 72 }, // 3 19
Rect { left: 0, top: 72, right: 24, bottom: 96 }, // 0 14 // right
Rect { left: 24, top: 72, right: 48, bottom: 96 }, // 1 15
Rect { left: 0, top: 48, right: 24, bottom: 72 }, // 0 14 // left
Rect { left: 24, top: 48, right: 48, bottom: 72 }, // 1 15
Rect { left: 288, top: 0, right: 312, bottom: 24 }, // 2 18
Rect { left: 24, top: 48, right: 48, bottom: 72 }, // 3 19
Rect { left: 0, top: 72, right: 24, bottom: 96 }, // 0 14 // right
Rect { left: 24, top: 72, right: 48, bottom: 96 }, // 1 15
Rect { left: 288, top: 24, right: 312, bottom: 48 }, // 2 18
Rect { left: 24, top: 72, right: 48, bottom: 96 }, // 3 19
Rect { left: 24, top: 72, right: 48, bottom: 96 }, // 3 19
]
}
@ -2955,10 +2872,7 @@ fn default_n165_curly_collapsed() -> [Rect<u16>; 3] {
}
fn default_n166_chaba() -> [Rect<u16>; 2] {
[
Rect { left: 144, top: 104, right: 184, bottom: 128 },
Rect { left: 184, top: 104, right: 224, bottom: 128 },
]
[Rect { left: 144, top: 104, right: 184, bottom: 128 }, Rect { left: 184, top: 104, right: 224, bottom: 128 }]
}
fn default_n167_booster_falling() -> [Rect<u16>; 3] {
@ -3064,10 +2978,7 @@ fn default_n176_buyo_buyo_base() -> [Rect<u16>; 6] {
}
fn default_n177_buyo_buyo() -> [Rect<u16>; 2] {
[
Rect { left: 192, top: 128, right: 208, bottom: 144 },
Rect { left: 208, top: 128, right: 224, bottom: 144 },
]
[Rect { left: 192, top: 128, right: 208, bottom: 144 }, Rect { left: 208, top: 128, right: 224, bottom: 144 }]
}
fn default_n178_core_blade_projectile() -> [Rect<u16>; 3] {
@ -3132,10 +3043,7 @@ fn default_n182_curly_ai_polar_star() -> [Rect<u16>; 4] {
}
fn default_n183_curly_air_tank_bubble() -> [Rect<u16>; 2] {
[
Rect { left: 56, top: 96, right: 80, bottom: 120 },
Rect { left: 80, top: 96, right: 104, bottom: 120 },
]
[Rect { left: 56, top: 96, right: 80, bottom: 120 }, Rect { left: 80, top: 96, right: 104, bottom: 120 }]
}
fn default_n184_shutter() -> [Rect<u16>; 4] {
@ -3187,10 +3095,7 @@ fn default_n189_homing_flame() -> [Rect<u16>; 3] {
}
fn default_n190_broken_robot() -> [Rect<u16>; 2] {
[
Rect { left: 192, top: 32, right: 208, bottom: 48 },
Rect { left: 208, top: 32, right: 224, bottom: 48 },
]
[Rect { left: 192, top: 32, right: 208, bottom: 48 }, Rect { left: 208, top: 32, right: 224, bottom: 48 }]
}
fn default_n192_scooter() -> [Rect<u16>; 4] {
@ -3215,10 +3120,7 @@ fn default_n195_background_grate() -> Rect<u16> {
}
fn default_n196_ironhead_wall() -> [Rect<u16>; 2] {
[
Rect { left: 112, top: 64, right: 144, bottom: 80 },
Rect { left: 112, top: 80, right: 144, bottom: 96 },
]
[Rect { left: 112, top: 64, right: 144, bottom: 80 }, Rect { left: 112, top: 80, right: 144, bottom: 96 }]
}
fn default_n197_porcupine_fish() -> [Rect<u16>; 4] {
@ -3266,10 +3168,7 @@ fn default_n200_zombie_dragon() -> [Rect<u16>; 12] {
}
fn default_n201_zombie_dragon_dead() -> [Rect<u16>; 2] {
[
Rect { left: 200, top: 0, right: 240, bottom: 40 },
Rect { left: 200, top: 40, right: 240, bottom: 80 },
]
[Rect { left: 200, top: 0, right: 240, bottom: 40 }, Rect { left: 200, top: 40, right: 240, bottom: 80 }]
}
fn default_n202_zombie_dragon_projectile() -> [Rect<u16>; 3] {
@ -3292,17 +3191,11 @@ fn default_n203_critter_destroyed_egg_corridor() -> [Rect<u16>; 6] {
}
fn default_n204_small_falling_spike() -> [Rect<u16>; 2] {
[
Rect { left: 240, top: 80, right: 256, bottom: 96 },
Rect { left: 240, top: 144, right: 256, bottom: 160 },
]
[Rect { left: 240, top: 80, right: 256, bottom: 96 }, Rect { left: 240, top: 144, right: 256, bottom: 160 }]
}
fn default_n205_large_falling_spike() -> [Rect<u16>; 2] {
[
Rect { left: 112, top: 80, right: 128, bottom: 112 },
Rect { left: 128, top: 80, right: 144, bottom: 112 },
]
[Rect { left: 112, top: 80, right: 128, bottom: 112 }, Rect { left: 128, top: 80, right: 144, bottom: 112 }]
}
fn default_n206_counter_bomb() -> [Rect<u16>; 3] {
@ -3421,10 +3314,7 @@ fn default_n217_itoh() -> [Rect<u16>; 8] {
}
fn default_n218_core_giant_ball() -> [Rect<u16>; 2] {
[
Rect { left: 256, top: 120, right: 288, bottom: 152 },
Rect { left: 288, top: 120, right: 320, bottom: 152 },
]
[Rect { left: 256, top: 120, right: 288, bottom: 152 }, Rect { left: 288, top: 120, right: 320, bottom: 152 }]
}
fn default_n220_shovel_brigade() -> [Rect<u16>; 4] {
@ -3516,24 +3406,15 @@ fn default_n228_droll() -> [Rect<u16>; 8] {
}
fn default_n229_red_flowers_sprouts() -> [Rect<u16>; 2] {
[
Rect { left: 0, top: 96, right: 48, bottom: 112 },
Rect { left: 0, top: 112, right: 48, bottom: 128 },
]
[Rect { left: 0, top: 96, right: 48, bottom: 112 }, Rect { left: 0, top: 112, right: 48, bottom: 128 }]
}
fn default_n230_red_flowers_blooming() -> [Rect<u16>; 2] {
[
Rect { left: 48, top: 96, right: 96, bottom: 128 },
Rect { left: 96, top: 96, right: 144, bottom: 128 },
]
[Rect { left: 48, top: 96, right: 96, bottom: 128 }, Rect { left: 96, top: 96, right: 144, bottom: 128 }]
}
fn default_n231_rocket() -> [Rect<u16>; 2] {
[
Rect { left: 176, top: 32, right: 208, bottom: 48 },
Rect { left: 176, top: 48, right: 208, bottom: 64 },
]
[Rect { left: 176, top: 32, right: 208, bottom: 48 }, Rect { left: 176, top: 48, right: 208, bottom: 64 }]
}
fn default_n232_orangebell() -> [Rect<u16>; 6] {
@ -3547,7 +3428,7 @@ fn default_n232_orangebell() -> [Rect<u16>; 6] {
]
}
fn default_n233_orangebell_hat() -> [Rect<u16>; 8] {
fn default_n233_orangebell_bat() -> [Rect<u16>; 8] {
[
Rect { left: 256, top: 0, right: 272, bottom: 16 },
Rect { left: 272, top: 0, right: 288, bottom: 16 },
@ -3561,10 +3442,7 @@ fn default_n233_orangebell_hat() -> [Rect<u16>; 8] {
}
fn default_n234_red_flowers_picked() -> [Rect<u16>; 2] {
[
Rect { left: 144, top: 96, right: 192, bottom: 112 },
Rect { left: 144, top: 112, right: 192, bottom: 128 },
]
[Rect { left: 144, top: 96, right: 192, bottom: 112 }, Rect { left: 144, top: 112, right: 192, bottom: 128 }]
}
fn default_n235_midorin() -> [Rect<u16>; 8] {
@ -3610,10 +3488,7 @@ fn default_n238_press_sideways() -> [Rect<u16>; 3] {
}
fn default_n239_cage_bars() -> [Rect<u16>; 2] {
[
Rect { left: 192, top: 48, right: 256, bottom: 80 },
Rect { left: 96, top: 112, right: 144, bottom: 144 },
]
[Rect { left: 192, top: 48, right: 256, bottom: 80 }, Rect { left: 96, top: 112, right: 144, bottom: 144 }]
}
fn default_n240_mimiga_jailed() -> [Rect<u16>; 12] {
@ -3709,11 +3584,8 @@ fn default_n248_misery_boss_vanishing() -> [Rect<u16>; 3] {
]
}
fn default_n249_misery_boss_energy_shot() -> [Rect<u16>; 2] {
[
Rect { left: 48, top: 48, right: 64, bottom: 64 },
Rect { left: 64, top: 48, right: 80, bottom: 64 },
]
fn default_n249_misery_boss_appearing() -> [Rect<u16>; 2] {
[Rect { left: 48, top: 48, right: 64, bottom: 64 }, Rect { left: 64, top: 48, right: 80, bottom: 64 }]
}
fn default_n250_misery_boss_lighting_ball() -> [Rect<u16>; 3] {
@ -3725,10 +3597,7 @@ fn default_n250_misery_boss_lighting_ball() -> [Rect<u16>; 3] {
}
fn default_n251_misery_boss_lighting() -> [Rect<u16>; 2] {
[
Rect { left: 80, top: 32, right: 96, bottom: 64 },
Rect { left: 96, top: 32, right: 112, bottom: 64 },
]
[Rect { left: 80, top: 32, right: 96, bottom: 64 }, Rect { left: 96, top: 32, right: 112, bottom: 64 }]
}
fn default_n252_misery_boss_bats() -> [Rect<u16>; 8] {
@ -3745,17 +3614,11 @@ fn default_n252_misery_boss_bats() -> [Rect<u16>; 8] {
}
fn default_n253_experience_capsule() -> [Rect<u16>; 2] {
[
Rect { left: 0, top: 64, right: 16, bottom: 80 },
Rect { left: 16, top: 64, right: 32, bottom: 80 },
]
[Rect { left: 0, top: 64, right: 16, bottom: 80 }, Rect { left: 16, top: 64, right: 32, bottom: 80 }]
}
fn default_n254_helicopter() -> [Rect<u16>; 2] {
[
Rect { left: 0, top: 0, right: 128, bottom: 64 },
Rect { left: 0, top: 64, right: 128, bottom: 128 },
]
[Rect { left: 0, top: 0, right: 128, bottom: 64 }, Rect { left: 0, top: 64, right: 128, bottom: 128 }]
}
fn default_n255_helicopter_blades() -> [Rect<u16>; 8] {
@ -3795,10 +3658,7 @@ fn default_n258_mimiga_sleeping() -> Rect<u16> {
}
fn default_n259_curly_unconscious() -> [Rect<u16>; 2] {
[
Rect { left: 224, top: 96, right: 240, bottom: 112 },
Rect { left: 224, top: 112, right: 240, bottom: 128 },
]
[Rect { left: 224, top: 96, right: 240, bottom: 112 }, Rect { left: 224, top: 112, right: 240, bottom: 128 }]
}
fn default_n260_shovel_brigade_caged() -> [Rect<u16>; 6] {
@ -3853,11 +3713,11 @@ fn default_n263_doctor_boss() -> [Rect<u16>; 18] {
]
}
fn default_n264_doctor_boss_red_wave_projectile() -> Rect<u16> {
fn default_n264_doctor_boss_red_projectile() -> Rect<u16> {
Rect { left: 288, top: 0, right: 304, bottom: 16 }
}
fn default_n265_doctor_boss_red_ball_projectile() -> [Rect<u16>; 3] {
fn default_n265_doctor_boss_red_projectile_trail() -> [Rect<u16>; 3] {
[
Rect { left: 288, top: 16, right: 304, bottom: 32 },
Rect { left: 288, top: 32, right: 304, bottom: 48 },
@ -3865,11 +3725,8 @@ fn default_n265_doctor_boss_red_ball_projectile() -> [Rect<u16>; 3] {
]
}
fn default_n266_doctor_boss_red_ball_projectile_bouncing() -> [Rect<u16>; 2] {
[
Rect { left: 304, top: 16, right: 320, bottom: 32 },
Rect { left: 304, top: 32, right: 320, bottom: 48 },
]
fn default_n266_doctor_boss_red_projectile_bouncing() -> [Rect<u16>; 2] {
[Rect { left: 304, top: 16, right: 320, bottom: 32 }, Rect { left: 304, top: 32, right: 320, bottom: 48 }]
}
fn default_n267_muscle_doctor() -> [Rect<u16>; 20] {
@ -3934,10 +3791,7 @@ fn default_n269_red_bat_bouncing() -> [Rect<u16>; 6] {
}
fn default_n270_doctor_red_energy() -> [Rect<u16>; 2] {
[
Rect { left: 170, top: 34, right: 174, bottom: 38 },
Rect { left: 170, top: 42, right: 174, bottom: 46 },
]
[Rect { left: 170, top: 34, right: 174, bottom: 38 }, Rect { left: 170, top: 42, right: 174, bottom: 46 }]
}
fn default_n273_droll_projectile() -> [Rect<u16>; 3] {
@ -4017,10 +3871,7 @@ fn default_n278_little_family() -> [Rect<u16>; 6] {
}
fn default_n279_large_falling_block() -> [Rect<u16>; 2] {
[
Rect { left: 0, top: 16, right: 32, bottom: 48 },
Rect { left: 16, top: 0, right: 32, bottom: 16 },
]
[Rect { left: 0, top: 16, right: 32, bottom: 48 }, Rect { left: 16, top: 0, right: 32, bottom: 16 }]
}
fn default_n280_sue_teleported() -> [Rect<u16>; 4] {
@ -4155,17 +4006,11 @@ fn default_n290_bat_misery() -> [Rect<u16>; 6] {
}
fn default_n291_mini_undead_core_inactive() -> [Rect<u16>; 2] {
[
Rect { left: 256, top: 80, right: 320, bottom: 120 },
Rect { left: 256, top: 0, right: 320, bottom: 40 },
]
[Rect { left: 256, top: 80, right: 320, bottom: 120 }, Rect { left: 256, top: 0, right: 320, bottom: 40 }]
}
fn default_n293_undead_core_energy_shot() -> [Rect<u16>; 2] {
[
Rect { left: 240, top: 200, right: 280, bottom: 240 },
Rect { left: 280, top: 200, right: 320, bottom: 240 },
]
[Rect { left: 240, top: 200, right: 280, bottom: 240 }, Rect { left: 280, top: 200, right: 320, bottom: 240 }]
}
fn default_n295_cloud() -> [Rect<u16>; 4] {
@ -4195,10 +4040,7 @@ fn default_n298_intro_doctor() -> [Rect<u16>; 8] {
}
fn default_n299_intro_balrog_misery() -> [Rect<u16>; 2] {
[
Rect { left: 0, top: 0, right: 48, bottom: 48 },
Rect { left: 48, top: 0, right: 96, bottom: 48 },
]
[Rect { left: 0, top: 0, right: 48, bottom: 48 }, Rect { left: 48, top: 0, right: 96, bottom: 48 }]
}
fn default_n300_intro_demon_crown() -> Rect<u16> {
@ -4500,10 +4342,7 @@ fn default_n326_sue_itoh_human_transition() -> [Rect<u16>; 16] {
}
fn default_n327_sneeze() -> [Rect<u16>; 2] {
[
Rect { left: 240, top: 80, right: 256, bottom: 96 },
Rect { left: 256, top: 80, right: 272, bottom: 96 },
]
[Rect { left: 240, top: 80, right: 256, bottom: 96 }, Rect { left: 256, top: 80, right: 272, bottom: 96 }]
}
fn default_n328_human_transform_machine() -> Rect<u16> {
@ -4511,10 +4350,7 @@ fn default_n328_human_transform_machine() -> Rect<u16> {
}
fn default_n329_laboratory_fan() -> [Rect<u16>; 2] {
[
Rect { left: 48, top: 0, right: 64, bottom: 16 },
Rect { left: 64, top: 0, right: 80, bottom: 16 },
]
[Rect { left: 48, top: 0, right: 64, bottom: 16 }, Rect { left: 64, top: 0, right: 80, bottom: 16 }]
}
fn default_n330_rolling() -> [Rect<u16>; 3] {
@ -4543,10 +4379,7 @@ fn default_n332_ballos_shockwave() -> [Rect<u16>; 3] {
}
fn default_n333_ballos_lighting() -> [Rect<u16>; 2] {
[
Rect { left: 80, top: 120, right: 104, bottom: 144 },
Rect { left: 104, top: 120, right: 128, bottom: 144 },
]
[Rect { left: 80, top: 120, right: 104, bottom: 144 }, Rect { left: 104, top: 120, right: 128, bottom: 144 }]
}
fn default_n334_sweat() -> [Rect<u16>; 4] {
@ -4567,10 +4400,7 @@ fn default_n335_ikachan() -> [Rect<u16>; 3] {
}
fn default_n337_numahachi() -> [Rect<u16>; 2] {
[
Rect { left: 256, top: 112, right: 288, bottom: 152 },
Rect { left: 288, top: 112, right: 320, bottom: 152 },
]
[Rect { left: 256, top: 112, right: 288, bottom: 152 }, Rect { left: 288, top: 112, right: 320, bottom: 152 }]
}
fn default_n338_green_devil() -> [Rect<u16>; 4] {
@ -4630,10 +4460,7 @@ fn default_n343_ballos_2_cutscene() -> Rect<u16> {
}
fn default_n344_ballos_2_eyes() -> [Rect<u16>; 2] {
[
Rect { left: 272, top: 0, right: 296, bottom: 16 },
Rect { left: 296, top: 0, right: 320, bottom: 16 },
]
[Rect { left: 272, top: 0, right: 296, bottom: 16 }, Rect { left: 296, top: 0, right: 320, bottom: 16 }]
}
fn default_n345_ballos_skull_projectile() -> [Rect<u16>; 4] {
@ -4659,10 +4486,7 @@ fn default_n347_hoppy() -> [Rect<u16>; 4] {
}
fn default_n348_ballos_4_spikes() -> [Rect<u16>; 2] {
[
Rect { left: 128, top: 152, right: 160, bottom: 176 },
Rect { left: 160, top: 152, right: 192, bottom: 176 },
]
[Rect { left: 128, top: 152, right: 160, bottom: 176 }, Rect { left: 160, top: 152, right: 192, bottom: 176 }]
}
fn default_n349_statue() -> Rect<u16> {
@ -4758,10 +4582,7 @@ fn default_n355_quote_and_curly_on_balrog() -> [Rect<u16>; 4] {
}
fn default_n356_balrog_rescuing() -> [Rect<u16>; 2] {
[
Rect { left: 240, top: 128, right: 280, bottom: 152 },
Rect { left: 240, top: 152, right: 280, bottom: 176 },
]
[Rect { left: 240, top: 128, right: 280, bottom: 152 }, Rect { left: 240, top: 152, right: 280, bottom: 176 }]
}
fn default_n357_puppy_ghost() -> Rect<u16> {
@ -4822,28 +4643,35 @@ fn default_b02_balfrog() -> [Rect<u16>; 18] {
fn default_b03_monster_x() -> [Rect<u16>; 29] {
[
Rect { left: 216, top: 0, right: 320, bottom: 48 }, // face
// face
Rect { left: 216, top: 0, right: 320, bottom: 48 },
Rect { left: 216, top: 48, right: 320, bottom: 96 },
Rect { left: 216, top: 144, right: 320, bottom: 192 },
Rect { left: 0, top: 0, right: 72, bottom: 32 }, // tracks up
// tracks up
Rect { left: 0, top: 0, right: 72, bottom: 32 },
Rect { left: 0, top: 32, right: 72, bottom: 64 },
Rect { left: 72, top: 0, right: 144, bottom: 32 },
Rect { left: 144, top: 0, right: 216, bottom: 32 },
Rect { left: 72, top: 32, right: 144, bottom: 64 },
Rect { left: 144, top: 32, right: 216, bottom: 64 },
Rect { left: 0, top: 64, right: 72, bottom: 96 }, // tracks down
// tracks down
Rect { left: 0, top: 64, right: 72, bottom: 96 },
Rect { left: 0, top: 96, right: 72, bottom: 128 },
Rect { left: 72, top: 64, right: 144, bottom: 96 },
Rect { left: 144, top: 64, right: 216, bottom: 96 },
Rect { left: 72, top: 96, right: 144, bottom: 128 },
Rect { left: 144, top: 96, right: 216, bottom: 128 },
Rect { left: 0, top: 128, right: 72, bottom: 160 }, // frame
// frame
Rect { left: 0, top: 128, right: 72, bottom: 160 },
Rect { left: 72, top: 128, right: 144, bottom: 160 },
Rect { left: 0, top: 160, right: 72, bottom: 192 },
Rect { left: 72, top: 160, right: 144, bottom: 192 },
Rect { left: 216, top: 96, right: 264, bottom: 144 }, // shield left
Rect { left: 264, top: 96, right: 312, bottom: 144 }, // shield right
Rect { left: 0, top: 192, right: 16, bottom: 208 }, // part 4
// shield left
Rect { left: 216, top: 96, right: 264, bottom: 144 },
// shield right
Rect { left: 264, top: 96, right: 312, bottom: 144 },
// part 4
Rect { left: 0, top: 192, right: 16, bottom: 208 },
Rect { left: 16, top: 192, right: 32, bottom: 208 },
Rect { left: 32, top: 192, right: 48, bottom: 208 },
Rect { left: 48, top: 192, right: 64, bottom: 208 },
@ -4856,16 +4684,87 @@ fn default_b03_monster_x() -> [Rect<u16>; 29] {
fn default_b04_core() -> [Rect<u16>; 10] {
[
Rect { left: 0, top: 0, right: 72, bottom: 112 }, // face
// face
Rect { left: 0, top: 0, right: 72, bottom: 112 },
Rect { left: 0, top: 112, right: 72, bottom: 224 },
Rect { left: 160, top: 0, right: 232, bottom: 112 },
Rect { left: 0, top: 0, right: 0, bottom: 0 },
Rect { left: 72, top: 0, right: 160, bottom: 112 }, // tail
// tail
Rect { left: 72, top: 0, right: 160, bottom: 112 },
Rect { left: 72, top: 112, right: 160, bottom: 224 },
Rect { left: 0, top: 0, right: 0, bottom: 0 },
Rect { left: 256, top: 0, right: 320, bottom: 40 }, // small head
// small head
Rect { left: 256, top: 0, right: 320, bottom: 40 },
Rect { left: 256, top: 40, right: 320, bottom: 80 },
Rect { left: 256, top: 80, right: 320, bottom: 120 },
]
}
fn default_b05_ironhead() -> [Rect<u16>; 18] {
[
// set 1
Rect { left: 0, top: 0, right: 64, bottom: 24 },
Rect { left: 64, top: 0, right: 128, bottom: 24 },
Rect { left: 128, top: 0, right: 192, bottom: 24 },
Rect { left: 64, top: 0, right: 128, bottom: 24 },
Rect { left: 0, top: 0, right: 64, bottom: 24 },
Rect { left: 192, top: 0, right: 256, bottom: 24 },
Rect { left: 256, top: 0, right: 320, bottom: 24 },
Rect { left: 192, top: 0, right: 256, bottom: 24 },
Rect { left: 256, top: 48, right: 320, bottom: 72 },
// set 2
Rect { left: 0, top: 24, right: 64, bottom: 48 },
Rect { left: 64, top: 24, right: 128, bottom: 48 },
Rect { left: 128, top: 24, right: 192, bottom: 48 },
Rect { left: 64, top: 24, right: 128, bottom: 48 },
Rect { left: 0, top: 24, right: 64, bottom: 48 },
Rect { left: 192, top: 24, right: 256, bottom: 48 },
Rect { left: 256, top: 24, right: 320, bottom: 48 },
Rect { left: 192, top: 24, right: 256, bottom: 48 },
Rect { left: 256, top: 48, right: 320, bottom: 72 },
]
}
fn default_b06_sisters() -> [Rect<u16>; 14] {
[
// head
Rect { left: 0, top: 80, right: 40, bottom: 112 },
Rect { left: 40, top: 80, right: 80, bottom: 112 },
Rect { left: 80, top: 80, right: 120, bottom: 112 },
Rect { left: 120, top: 80, right: 160, bottom: 112 },
Rect { left: 0, top: 112, right: 40, bottom: 144 },
Rect { left: 40, top: 112, right: 80, bottom: 144 },
Rect { left: 80, top: 112, right: 120, bottom: 144 },
Rect { left: 120, top: 112, right: 160, bottom: 144 },
// body
Rect { left: 0, top: 0, right: 40, bottom: 40 },
Rect { left: 40, top: 0, right: 80, bottom: 40 },
Rect { left: 80, top: 0, right: 120, bottom: 40 },
Rect { left: 0, top: 40, right: 40, bottom: 80 },
Rect { left: 40, top: 40, right: 80, bottom: 80 },
Rect { left: 80, top: 40, right: 120, bottom: 80 },
]
}
fn default_b07_undead_core() -> [Rect<u16>; 15] {
[
// face
Rect { left: 0, top: 0, right: 0, bottom: 0 },
Rect { left: 160, top: 112, right: 232, bottom: 152 },
Rect { left: 160, top: 152, right: 232, bottom: 192 },
Rect { left: 160, top: 192, right: 232, bottom: 232 },
Rect { left: 248, top: 160, right: 320, bottom: 200 },
// head
Rect { left: 0, top: 0, right: 72, bottom: 112 },
Rect { left: 0, top: 112, right: 72, bottom: 224 },
Rect { left: 160, top: 0, right: 232, bottom: 112 },
Rect { left: 0, top: 0, right: 0, bottom: 0 },
// tail
Rect { left: 72, top: 0, right: 160, bottom: 112 },
Rect { left: 72, top: 112, right: 160, bottom: 224 },
Rect { left: 0, top: 0, right: 0, bottom: 0 },
// small head
Rect { left: 256, top: 0, right: 320, bottom: 40 },
Rect { left: 256, top: 40, right: 320, bottom: 80 },
Rect { left: 256, top: 80, right: 320, bottom: 120 },
]

View file

@ -53,6 +53,8 @@ pub trait BackendRenderer {
fn draw_outline_rect(&mut self, rect: Rect, line_width: usize, color: Color) -> GameResult;
fn set_clip_rect(&mut self, rect: Option<Rect>) -> GameResult;
fn imgui(&self) -> GameResult<&mut imgui::Context>;
fn render_imgui(&mut self, draw_data: &DrawData) -> GameResult;

View file

@ -118,6 +118,10 @@ impl BackendRenderer for NullRenderer {
Ok(())
}
fn set_clip_rect(&mut self, rect: Option<Rect>) -> GameResult {
Ok(())
}
fn imgui(&self) -> GameResult<&mut imgui::Context> {
unsafe { Ok(&mut *self.0.as_ptr()) }
}

View file

@ -5,8 +5,9 @@ use std::ffi::c_void;
use std::rc::Rc;
use std::time::Duration;
use imgui::internal::RawWrapper;
use imgui::{ConfigFlags, DrawCmd, DrawData, ImString, Key, MouseCursor, TextureId};
use imgui::internal::RawWrapper;
use sdl2::{EventPump, keyboard, pixels, Sdl, VideoSubsystem};
use sdl2::event::{Event, WindowEvent};
use sdl2::keyboard::Scancode;
use sdl2::mouse::{Cursor, SystemCursor};
@ -14,7 +15,6 @@ use sdl2::pixels::PixelFormatEnum;
use sdl2::render::{Texture, TextureCreator, WindowCanvas};
use sdl2::video::GLProfile;
use sdl2::video::WindowContext;
use sdl2::{keyboard, pixels, EventPump, Sdl, VideoSubsystem};
use crate::common::{Color, Rect};
use crate::framework::backend::{
@ -570,6 +570,23 @@ impl BackendRenderer for SDL2Renderer {
Ok(())
}
fn set_clip_rect(&mut self, rect: Option<Rect>) -> GameResult {
let mut refs = self.refs.borrow_mut();
if let Some(rect) = &rect {
refs.canvas.set_clip_rect(Some(sdl2::rect::Rect::new(
rect.left as i32,
rect.top as i32,
rect.width() as u32,
rect.height() as u32,
)));
} else {
refs.canvas.set_clip_rect(None);
}
Ok(())
}
fn imgui(&self) -> GameResult<&mut imgui::Context> {
unsafe { Ok(&mut *self.imgui.as_ptr()) }
}

View file

@ -103,6 +103,15 @@ pub fn draw_outline_rect(ctx: &mut Context, rect: Rect, line_width: usize, color
Ok(())
}
pub fn set_clip_rect(ctx: &mut Context, rect: Option<Rect>) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
return renderer.set_clip_rect(rect);
}
Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string()))
}
pub fn imgui_context(ctx: &Context) -> GameResult<&mut imgui::Context> {
if let Some(renderer) = ctx.renderer.as_ref() {
return renderer.imgui();

View file

@ -1159,6 +1159,23 @@ impl BackendRenderer for OpenGLRenderer {
fn supports_vertex_draw(&self) -> bool {
true
}
fn set_clip_rect(&mut self, rect: Option<Rect>) -> GameResult {
if let Some((_, gl)) = self.get_context() {
unsafe {
if let Some(rect) = &rect {
gl.gl.Enable(gl::SCISSOR_TEST);
gl.gl.Scissor(rect.left as GLint, rect.top as GLint, rect.width() as GLint, rect.height() as GLint);
} else {
gl.gl.Disable(gl::SCISSOR_TEST);
}
}
Ok(())
} else {
Err(RenderError("No OpenGL context available!".to_string()))
}
}
}
impl OpenGLRenderer {

View file

@ -22,6 +22,7 @@ use crate::framework::ui::UI;
use crate::framework::vfs::PhysicalFS;
use crate::scene::loading_scene::LoadingScene;
use crate::scene::Scene;
use crate::scripting::tsc::text_script::ScriptMode;
use crate::shared_game_state::{SharedGameState, TimingMode};
use crate::texture_set::{G_MAG, I_MAG};
@ -99,16 +100,22 @@ impl Game {
if let Some(scene) = self.scene.as_mut() {
let state_ref = unsafe { &mut *self.state.get() };
let speed = if state_ref.textscript_vm.mode == ScriptMode::Map && state_ref.textscript_vm.flags.cutscene_skip() {
4.0 * state_ref.settings.speed
} else {
1.0 * state_ref.settings.speed
};
match state_ref.settings.timing_mode {
TimingMode::_50Hz | TimingMode::_60Hz => {
let last_tick = self.next_tick;
while self.start_time.elapsed().as_nanos() >= self.next_tick && self.loops < 10 {
if (state_ref.settings.speed - 1.0).abs() < 0.01 {
if (speed - 1.0).abs() < 0.01 {
self.next_tick += state_ref.settings.timing_mode.get_delta() as u128;
} else {
self.next_tick +=
(state_ref.settings.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128;
(state_ref.settings.timing_mode.get_delta() as f64 / speed) as u128;
}
self.loops += 1;
}
@ -117,7 +124,7 @@ impl Game {
log::warn!("Frame skip is way too high, a long system lag occurred?");
self.last_tick = self.start_time.elapsed().as_nanos();
self.next_tick = self.last_tick
+ (state_ref.settings.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128;
+ (state_ref.settings.timing_mode.get_delta() as f64 / speed) as u128;
self.loops = 0;
}

252
src/npc/ai/balcony.rs Normal file
View file

@ -0,0 +1,252 @@
use crate::common::Direction;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::player::Player;
use crate::rng::RNG;
use crate::{GameResult, SharedGameState};
impl NPC {
pub(crate) fn tick_n254_helicopter(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
match self.action_num {
0 => {
self.action_num = 1;
// blades
let mut npc = NPC::create(255, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x + 0x2400;
npc.y = self.y - 0x7200;
npc.parent_id = self.id;
let _ = npc_list.spawn(0x100, npc.clone());
npc.x = self.x - 0x4000;
npc.y = self.y - 0x6800;
npc.direction = Direction::Right;
let _ = npc_list.spawn(0x100, npc);
}
20 => {
self.action_num = 21;
self.action_counter = 0;
self.action_counter2 = 60;
}
30 => {
self.action_num = 21;
// momorin
let mut npc = NPC::create(223, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x - 0x1600;
npc.y = self.y - 0x1c00;
let _ = npc_list.spawn(0x100, npc);
}
40 => {
self.action_num = 21;
// momorin
let mut npc = NPC::create(223, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x - 0x1200;
npc.y = self.y - 0x1c00;
let _ = npc_list.spawn(0x100, npc);
// santa
let mut npc = NPC::create(40, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x - 0x2c00;
npc.y = self.y - 0x1c00;
let _ = npc_list.spawn(0x100, npc);
// chaco
let mut npc = NPC::create(223, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x - 0x4600;
npc.y = self.y - 0x1c00;
let _ = npc_list.spawn(0x100, npc);
}
_ => (),
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 1 };
self.anim_rect = state.constants.npc.n254_helicopter[dir_offset];
Ok(())
}
pub(crate) fn tick_n255_helicopter_blades(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
if self.direction == Direction::Left {
self.display_bounds.left = 0x7000;
self.display_bounds.right = 0x7000;
} else {
self.display_bounds.left = 0x5000;
self.display_bounds.right = 0x5000;
}
}
if let Some(parent) = self.get_parent_ref_mut(npc_list) {
if parent.action_num >= 20 {
self.action_num = 10;
}
}
}
10 | 11 => {
self.action_num = 11;
self.anim_num += 1;
if self.anim_num > 3 {
self.anim_num = 0;
}
}
_ => (),
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
self.anim_rect = state.constants.npc.n255_helicopter_blades[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n260_shovel_brigade_caged(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.x += 0x200;
self.y -= 0x400;
self.action_num = 1;
self.anim_num = 0;
self.anim_counter = 0;
}
if self.rng.range(0..160) == 1 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 12 {
self.action_num = 1;
self.anim_num = 0;
}
}
10 => {
self.action_num = 11;
self.anim_num = 2;
// create heart
let mut npc = NPC::create(87, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y - 0x2000;
let _ = npc_list.spawn(0x100, npc);
}
_ => (),
}
let player = self.get_closest_player_ref(&players);
self.direction = if player.x < self.x { Direction::Left } else { Direction::Right };
let dir_offset = if self.direction == Direction::Left { 0 } else { 3 };
self.anim_rect = state.constants.npc.n260_shovel_brigade_caged[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n261_chie_caged(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.x -= 0x200;
self.y -= 0x400;
self.action_num = 1;
self.action_counter = 0;
self.anim_num = 0;
}
if self.rng.range(0..160) == 1 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 12 {
self.action_num = 1;
self.anim_num = 0;
}
}
_ => (),
}
let player = self.get_closest_player_ref(&players);
self.direction = if player.x < self.x { Direction::Left } else { Direction::Right };
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
self.anim_rect = state.constants.npc.n261_chie_caged[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n262_chaco_caged(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.x -= 0x200;
self.y -= 0x400;
self.action_num = 1;
self.action_counter = 0;
self.anim_num = 0;
}
if self.rng.range(0..160) == 1 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 12 {
self.action_num = 1;
self.anim_num = 0;
}
}
_ => (),
}
let player = self.get_closest_player_ref(&players);
self.direction = if player.x < self.x { Direction::Left } else { Direction::Right };
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
self.anim_rect = state.constants.npc.n262_chaco_caged[self.anim_num as usize + dir_offset];
Ok(())
}
}

View file

@ -1212,7 +1212,6 @@ impl NPC {
self.anim_rect = state.constants.npc.n169_balrog_shooting_missiles[self.anim_num as usize + dir_offset];
Ok(())
}
@ -1280,4 +1279,37 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n306_balrog_nurse(&mut self, state: &mut SharedGameState) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter = 0;
self.anim_num = 0;
self.y += 0x800;
}
if self.rng.range(0..120) == 10 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 8 {
self.action_num = 1;
self.anim_num = 0;
}
}
_ => (),
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
self.anim_rect = state.constants.npc.n306_balrog_nurse[self.anim_num as usize + dir_offset];
Ok(())
}
}

View file

@ -566,4 +566,248 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n278_little_family(&mut self, state: &mut SharedGameState) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 0;
self.anim_counter = 0;
self.vel_x = 0;
}
if self.rng.range(0..60) == 1 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
if self.rng.range(0..60) == 1 {
self.action_num = 10;
self.action_counter = 0;
self.anim_num = 1;
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 8 {
self.action_num = 1;
self.anim_num = 0;
}
}
10 | 11 => {
if self.action_counter == 10 {
self.action_num = 11;
self.action_counter = self.rng.range(0..16) as u16;
self.anim_num = 0;
self.direction = if self.rng.range(0..9) % 2 != 0 { Direction::Left } else { Direction::Right };
}
if self.direction == Direction::Left && self.flags.hit_left_wall() {
self.direction = Direction::Right;
}
if self.direction == Direction::Right && self.flags.hit_right_wall() {
self.direction = Direction::Left;
}
self.vel_x = self.direction.vector_x() * 0x100;
self.animate(4, 0, 1);
self.action_counter += 1;
if self.action_counter > 32 {
self.action_num = 0;
}
}
_ => (),
}
self.vel_y += 0x20;
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
self.x += self.vel_x;
self.y += self.vel_y;
let offset = match self.event_num {
200 => 0,
210 => 2,
_ => 4,
};
self.anim_rect = state.constants.npc.n278_little_family[self.anim_num as usize + offset];
Ok(())
}
pub(crate) fn tick_n305_small_puppy(&mut self, state: &mut SharedGameState) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x2000;
self.anim_counter = self.rng.range(0..6) as u16;
}
if self.action_num == 1 {
self.animate(6, 0, 1);
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
self.anim_rect = state.constants.npc.n305_small_puppy[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n326_sue_itoh_human_transition(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 0;
self.x += 0x2000;
self.y -= 0x1000;
}
self.action_counter += 1;
if self.action_counter > 80 {
self.action_num = 10;
self.action_counter = 0;
} else {
if self.direction != Direction::Left {
if self.action_counter == 50 {
self.anim_num = 1;
}
if self.action_counter == 60 {
self.anim_num = 0;
}
} else {
if self.action_counter == 30 {
self.anim_num = 1;
}
if self.action_counter == 40 {
self.anim_num = 0;
}
}
}
}
10 => {
self.action_counter += 1;
if self.action_counter <= 50 {
self.anim_num = if self.action_counter & 2 != 0 { 2 } else { 3 };
} else {
self.action_num = 15;
self.anim_num = 4;
let actr: &mut i16 = unsafe { std::mem::transmute(&mut self.action_counter) };
if self.direction == Direction::Left {
*actr = -20;
} else {
*actr = 0;
}
}
}
15 => {
let actr: &mut i16 = unsafe { std::mem::transmute(&mut self.action_counter) };
*actr += 1;
if *actr > 40 {
*actr = 0;
self.action_num = 20;
}
}
20 => {
self.vel_y += 0x40;
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
self.action_counter += 1;
if self.action_counter > 50 {
self.action_num = 30;
self.action_counter = 0;
self.anim_num = 6;
let mut npc = NPC::create(327, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = if self.direction == Direction::Left { self.y - 0x1000 } else { self.y - 0x2000 };
npc.parent_id = self.id;
let _ = npc_list.spawn(0x100, npc);
}
}
30 => {
self.action_counter += 1;
if self.action_counter == 30 {
self.anim_num = 7;
}
if self.action_counter == 40 {
self.action_num = 40;
}
}
40 | 41 => {
if self.action_num == 40 {
self.action_num = 41;
self.action_counter = 0;
self.anim_num = 0;
}
self.action_counter += 1;
if self.action_counter == 30 {
self.anim_num = 1;
}
if self.action_counter == 40 {
self.anim_num = 0;
}
}
_ => (),
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 8 };
self.anim_rect = state.constants.npc.n326_sue_itoh_human_transition[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n327_sneeze(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
self.action_counter += 1;
if self.action_num == 0 {
if self.action_counter < 4 {
self.y -= 0x400;
}
if let Some(parent) = self.get_parent_ref_mut(npc_list) {
if parent.anim_num == 7 {
self.action_num = 1;
self.anim_num = 1;
self.target_x = self.x;
self.target_y = self.y;
}
}
} else if self.action_num == 1 {
if self.action_counter >= 48 {
self.x = self.target_x;
self.y = self.target_y;
} else {
self.x += self.target_x + self.rng.range(-1..1) * 0x200;
self.y += self.target_y + self.rng.range(-1..1) * 0x200;
}
}
if self.action_counter > 70 {
self.cond.set_alive(false);
}
self.anim_rect = state.constants.npc.n327_sneeze[self.anim_num as usize];
Ok(())
}
}

View file

@ -786,7 +786,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n259_curly_unconcious(
pub(crate) fn tick_n259_curly_unconscious(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],

File diff suppressed because it is too large Load diff

75
src/npc/ai/hell.rs Normal file
View file

@ -0,0 +1,75 @@
use crate::{GameResult, SharedGameState};
use crate::caret::CaretType;
use crate::common::Direction;
use crate::npc::NPC;
use crate::rng::RNG;
impl NPC {
pub(crate) fn tick_n337_numahachi(&mut self, state: &mut SharedGameState) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x1000;
}
if self.action_num == 1 {
self.action_num = 2;
self.anim_num = 0;
self.vel_x = 0;
}
if self.action_num == 2{
self.animate(50, 0, 1);
}
self.vel_y += 0x40;
if self.vel_y > 0x5ff {
self.vel_y = 0x5ff;
}
self.x += self.vel_x;
self.y += self.vel_y;
self.anim_rect = state.constants.npc.n337_numahachi[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n357_puppy_ghost(&mut self, state: &mut SharedGameState) -> GameResult {
match self.action_num {
0 => {
self.anim_rect = state.constants.npc.n357_puppy_ghost;
self.action_counter += 1;
}
10 | 11 => {
if self.action_num == 10 {
self.action_num = 11;
self.action_counter = 0;
state.sound_manager.play_sfx(29);
}
self.action_counter += 1;
self.anim_rect = state.constants.npc.n357_puppy_ghost;
if self.action_counter & 2 != 0 {
self.anim_rect.right = self.anim_rect.left;
}
if self.action_counter > 50 {
self.cond.set_alive(false);
}
}
_ => (),
}
if self.action_counter & 8 == 1 {
state.create_caret(
self.x + self.rng.range(-8..8) * 0x200,
self.y + 0x1000,
CaretType::LittleParticles,
Direction::Up,
);
}
Ok(())
}
}

View file

@ -1,4 +1,4 @@
use crate::common::{CDEG_RAD, Direction};
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::npc::list::NPCList;
use crate::npc::NPC;
@ -88,7 +88,12 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n088_igor_boss(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n088_igor_boss(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -98,14 +103,7 @@ impl NPC {
self.vel_x = 0;
}
self.anim_counter += 1;
if self.anim_counter > 5 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num > 1 {
self.anim_num = 0;
}
}
self.animate(5, 0, 1);
self.action_counter += 1;
if self.action_counter > 50 {
@ -131,14 +129,7 @@ impl NPC {
}
self.action_counter += 1;
self.anim_counter += 1;
if self.anim_counter > 3 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num > 5 {
self.anim_num = 2;
}
}
self.animate(3, 2, 5);
self.vel_x = self.direction.vector_x() * 0x200;
@ -155,8 +146,9 @@ impl NPC {
self.damage = 2;
self.vel_x = (self.vel_x * 3) / 2;
self.vel_y = -0x400;
} else if (self.direction == Direction::Left && self.x - 24 * 0x200 < player.x)
|| (self.direction == Direction::Right && self.x + 24 * 0x200 > player.x) {
} else if (self.direction == Direction::Left && self.x - 0x3000 < player.x)
|| (self.direction == Direction::Right && self.x + 0x3000 > player.x)
{
self.action_num = 4;
}
}
@ -174,7 +166,7 @@ impl NPC {
self.action_counter = 0;
self.anim_num = 7;
self.damage = 5;
self.hit_bounds.left = 24 * 0x200;
self.hit_bounds.left = 0x3000;
self.hit_bounds.top = 1;
state.sound_manager.play_sfx(70);
@ -227,13 +219,15 @@ impl NPC {
self.action_num = 10;
self.action_counter = 0;
let player = self.get_closest_player_mut(players);
let player = self.get_closest_player_ref(&players);
self.direction = if player.x < self.x { Direction::Left } else { Direction::Right };
}
self.action_counter += 1;
if self.action_counter > 100 && self.action_counter % 6 == 1 {
let deg = (if self.direction == Direction::Left { 0x88 } else { 0xf8 } + self.rng.range(-16..16)) as f64 * CDEG_RAD;
let deg = (if self.direction == Direction::Left { 0x88 } else { 0xf8 } + self.rng.range(-16..16))
as f64
* CDEG_RAD;
let vel_x = (deg.cos() * 1536.0) as i32;
let vel_y = (deg.sin() * 1536.0) as i32;
@ -270,12 +264,18 @@ impl NPC {
self.y += self.vel_y;
let dir_offset = if self.direction == Direction::Left { 0 } else { 12 };
self.anim_rect = state.constants.npc.n088_igor_boss[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n089_igor_dead(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
pub(crate) fn tick_n089_igor_dead(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -327,13 +327,13 @@ impl NPC {
self.action_counter += 1;
if (self.action_counter & 0x02) != 0 && self.action_counter < 100 {
self.anim_num = 0;
self.display_bounds.left = 20 * 0x200;
self.display_bounds.right = 20 * 0x200;
self.display_bounds.top = 20 * 0x200;
self.display_bounds.left = 0x2800;
self.display_bounds.right = 0x2800;
self.display_bounds.top = 0x2800;
} else {
self.anim_num = 1;
self.display_bounds.left = 12 * 0x200;
self.display_bounds.right = 12 * 0x200;
self.display_bounds.left = 0x1800;
self.display_bounds.right = 0x1800;
self.display_bounds.top = 0x1000;
}
@ -378,4 +378,158 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n268_igor_enemy(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
let player = self.get_closest_player_ref(&players);
if self.x < player.x - 0x28000
|| self.x > player.x + 0x28000
|| self.y < player.y - 0x1E000
|| self.y > player.y + 0x1E000
{
self.action_num = 1;
}
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.y += 0x1000;
}
self.animate(20, 0, 1);
if self.x < player.x + 0xE000
&& self.x > player.x - 0xE000
&& self.x < player.x + 0x6000
&& self.x > player.x - 0xE000
{
self.action_num = 10;
}
if self.shock != 0 {
self.action_num = 10;
}
}
10 | 11 => {
if self.action_num == 10 {
self.action_num = 11;
self.action_counter = 0;
self.anim_num = 0;
self.action_counter = 0;
self.direction = if self.x < player.x { Direction::Right } else { Direction::Left };
}
self.vel_x = self.direction.vector_x() * 0x200;
if self.x < player.x + 0x8000 && self.x > player.x - 0x8000 {
self.action_num = 20;
self.action_counter = 0;
}
if self.vel_x < 0 && self.flags.hit_left_wall() {
self.action_num = 20;
self.action_counter = 0;
}
if self.vel_x > 0 && self.flags.hit_right_wall() {
self.action_num = 20;
self.action_counter = 0;
}
self.animate(4, 2, 5);
}
20 => {
self.vel_x = 0;
self.anim_num = 6;
self.action_counter += 1;
if self.action_counter > 10 {
self.action_num = 30;
self.vel_x = self.direction.vector_x() * 0x200;
self.vel_y = -0x5FF;
state.sound_manager.play_sfx(108);
}
}
30 => {
self.anim_num = 7;
if self.flags.hit_bottom_wall() {
self.action_num = 40;
self.action_counter = 0;
state.quake_counter = 20;
state.sound_manager.play_sfx(26);
}
}
40 => {
self.vel_x = 0;
self.anim_num = 6;
self.action_counter += 1;
if self.action_counter > 30 {
self.action_num = 50;
}
}
50 | 51 => {
if self.action_num == 50 {
self.action_num = 51;
self.action_counter = 0;
let player = self.get_closest_player_ref(&players);
self.direction = if player.x < self.x { Direction::Left } else { Direction::Right };
}
self.action_counter += 1;
if self.action_counter > 30 && self.action_counter % 4 == 1 {
let deg = (if self.direction == Direction::Left { 0x88 } else { 0xf8 } + self.rng.range(-16..16))
as f64
* CDEG_RAD;
let vel_x = (deg.cos() * 1536.0) as i32;
let vel_y = (deg.sin() * 1536.0) as i32;
let mut npc = NPC::create(11, &state.npc_table);
npc.cond.set_alive(true);
npc.direction = Direction::Left;
npc.x = self.x;
npc.y = self.y + 0x800;
npc.vel_x = vel_x;
npc.vel_y = vel_y;
let _ = npc_list.spawn(0x100, npc);
state.sound_manager.play_sfx(12);
}
if self.action_counter < 50 && self.action_counter & 2 != 0 {
self.anim_num = 9;
} else {
self.anim_num = 8;
}
if self.action_counter > 82 {
self.action_num = 10;
self.direction = if player.x < self.x { Direction::Left } else { Direction::Right };
}
}
_ => (),
}
self.vel_y += 0x33;
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
self.x += self.vel_x;
self.y += self.vel_y;
let dir_offset = if self.direction == Direction::Left { 0 } else { 10 };
self.anim_rect = state.constants.npc.n268_igor_enemy[self.anim_num as usize + dir_offset];
Ok(())
}
}

View file

@ -1,7 +1,10 @@
use crate::caret::CaretType;
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::player::Player;
use crate::rng::RNG;
use crate::shared_game_state::SharedGameState;
impl NPC {
@ -18,7 +21,7 @@ impl NPC {
self.anim_num = 0;
}
let player = self.get_closest_player_mut(players);
let player = self.get_closest_player_ref(&players);
if self.x > player.x {
self.direction = Direction::Left;
} else {
@ -106,4 +109,371 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n242_bat_last_cave(&mut self, state: &mut SharedGameState) -> GameResult {
loop {
match self.action_num {
0 => {
self.action_num = 1;
self.target_x = self.x;
self.target_y = self.y;
self.action_counter = self.rng.range(0..50) as u16;
continue;
}
1 if self.action_counter > 0 => {
self.action_counter -= 1;
}
1 => {
self.action_num = 2;
self.vel_y = 0x400;
continue;
}
2 => {
self.vel_x = self.direction.vector_x() * 0x100;
self.vel_y += (self.target_y - self.y).signum() * 0x10;
self.vel_y = self.vel_y.clamp(-0x300, 0x300);
}
_ => (),
}
break;
}
self.x += self.vel_x;
self.y += self.vel_y;
self.animate(1, 0, 2);
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
self.anim_rect = state.constants.npc.n242_bat_last_cave[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n243_bat_generator(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter = self.rng.range(0..500) as u16;
}
if self.action_counter > 0 {
self.action_counter -= 1;
} else {
self.action_num = 0;
let mut npc = NPC::create(242, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y + self.rng.range(-32..32) * 0x200;
npc.direction = self.direction;
let _ = npc_list.spawn(0x100, npc);
}
}
_ => return Ok(()),
}
Ok(())
}
pub(crate) fn tick_n244_lava_drop(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
self.vel_y += 0x40;
// idfk why was that there in original code but I'll leave it there in case
let mut hit = self.flags.hit_anything();
if self.action_counter > 10 && self.flags.in_water() {
hit = true;
}
if hit {
for _ in 0..3 {
state.create_caret(self.x, self.y, CaretType::Bubble, Direction::Right);
}
let player = self.get_closest_player_ref(&players);
if self.x > player.x - 0x20000
&& self.x < player.x + 0x20000
&& self.y > player.y - 0x14000
&& self.y < player.y + 0x14000
{
state.sound_manager.play_sfx(21);
}
self.cond.set_alive(false);
return Ok(());
}
if self.vel_y > 0x5ff {
self.vel_y = 0x5ff;
}
self.y += self.vel_y;
self.anim_rect = state.constants.npc.n244_lava_drop;
Ok(())
}
pub(crate) fn tick_n245_lava_drop_generator(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.target_x = self.x;
self.action_counter = self.event_num;
}
self.anim_num = 0;
if self.anim_counter > 0 {
self.anim_counter -= 1;
return Ok(());
}
self.action_num = 10;
self.anim_counter = 0;
}
10 => {
self.anim_counter += 1;
if self.anim_counter > 10 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num > 3 {
self.anim_num = 0;
self.action_num = 1;
self.action_counter = self.flag_num;
let mut npc = NPC::create(244, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y;
let _ = npc_list.spawn(0x100, npc);
}
}
}
_ => (),
}
self.x = if (self.anim_counter & 2) != 0 { self.target_x } else { self.target_x + 0x200 };
self.anim_rect = state.constants.npc.n245_lava_drop_generator[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n276_red_demon(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 | 2 => {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x1000;
}
if self.action_num == 1 {
self.vel_x = 0;
self.action_num = 2;
self.anim_num = 0;
}
let player = self.get_closest_player_ref(&players);
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
self.animate(20, 0, 1);
if self.shock > 0 {
self.action_num = 10;
}
}
10 | 11 => {
if self.action_num == 10 {
self.action_num = 11;
self.anim_num = 2;
self.action_counter = 0;
self.npc_flags.set_shootable(true);
}
self.action_counter += 1;
match self.action_counter {
30 | 40 | 50 => {
let player = self.get_closest_player_ref(&players);
let mut npc = NPC::create(277, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y;
let angle = f64::atan2((player.y - 0x1400 - self.y) as f64, (player.x - self.x) as f64);
npc.vel_x = (2048.0 * angle.cos()) as i32;
npc.vel_y = (2048.0 * angle.sin()) as i32;
let _ = npc_list.spawn(0x100, npc);
state.sound_manager.play_sfx(39);
}
34 | 44 | 54 => {
self.anim_num = 3;
}
_ => (),
}
if self.action_counter > 60 {
self.action_num = 20;
self.action_counter = 0;
self.anim_num = 2;
}
}
20 => {
self.action_counter += 1;
if self.action_counter > 20 {
self.action_num = 21;
self.action_counter = 0;
self.anim_num = 5;
self.vel_y = -0x5ff;
let player = self.get_closest_player_ref(&players);
self.vel_x = if self.x < player.x { 0x100 } else { -0x100 };
}
}
21 => {
self.action_counter += 1;
match self.action_counter {
30 | 40 | 50 => {
let player = self.get_closest_player_ref(&players);
let mut npc = NPC::create(277, &state.npc_table);
npc.cond.set_alive(true);
let angle = f64::atan2((player.y - 0x1400 - self.y) as f64, (player.x - self.x) as f64);
npc.vel_x = (2048.0 * angle.cos()) as i32;
npc.vel_y = (2048.0 * angle.sin()) as i32;
let _ = npc_list.spawn(0x100, npc);
state.sound_manager.play_sfx(39);
}
34 | 44 => {
self.anim_num = 5;
}
_ => (),
}
if self.action_counter > 53 {
self.anim_num = 7;
}
if self.flags.hit_bottom_wall() {
self.action_num = 22;
self.action_counter = 0;
self.anim_num = 2;
state.quake_counter = 10;
state.sound_manager.play_sfx(26);
}
}
22 => {
self.vel_x /= 2;
self.action_counter += 1;
if self.action_counter > 22 {
self.action_num = 10;
}
}
50 => {
self.npc_flags.set_shootable(false);
self.damage = 0;
if self.flags.hit_bottom_wall() {
self.action_num = 51;
self.action_counter = 0;
self.anim_num = 2;
state.quake_counter = 10;
state.sound_manager.play_sfx(72);
npc_list.create_death_smoke(
self.x,
self.y,
self.display_bounds.right as usize,
8,
state,
&self.rng,
);
}
}
51 => {
self.vel_x = 7 * self.vel_x / 8;
self.anim_num = 8;
}
_ => (),
}
self.vel_y += 0x20;
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
self.x += self.vel_x;
self.y += self.vel_y;
if self.action_num < 50 {
let player = self.get_closest_player_ref(&players);
self.direction = if self.x < player.x { Direction::Right } else { Direction::Left };
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 9 };
self.anim_rect = state.constants.npc.n276_red_demon[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n277_red_demon_projectile(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
}
if self.action_num == 1 {
self.x += self.vel_x;
self.y += self.vel_y;
if self.flags.any_flag() {
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y;
for _ in 0..3 {
let _ = npc_list.spawn(0x100, npc.clone());
}
self.vanish(state);
return Ok(());
}
}
self.action_counter += 1;
if self.action_counter % 5 == 0 {
state.sound_manager.play_sfx(110);
}
self.anim_num += 1;
if self.anim_num > 2 {
self.anim_num = 0;
}
self.anim_rect = state.constants.npc.n277_red_demon_projectile[self.anim_num as usize];
Ok(())
}
}

View file

@ -1596,4 +1596,33 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n304_gaudi_hospital(&mut self, state: &mut SharedGameState) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.y += 0x1400;
}
self.anim_num = 0;
}
10 => {
self.anim_num = 1;
}
20 | 21 => {
if self.action_num == 20 {
self.action_num = 21;
self.anim_num = 2;
}
self.animate(10, 2, 3);
}
_ => (),
}
self.anim_rect = state.constants.npc.n304_gaudi_hospital[self.anim_num as usize];
Ok(())
}
}

View file

@ -1,11 +1,11 @@
use num_traits::{abs, clamp};
use std::hint::unreachable_unchecked;
use crate::caret::CaretType;
use crate::common::{Direction, Rect};
use crate::components::flash::Flash;
use crate::framework::error::GameResult;
use crate::npc::{NPC, NPCLayer};
use crate::npc::list::NPCList;
use crate::npc::{NPCLayer, NPC};
use crate::player::Player;
use crate::rng::RNG;
use crate::shared_game_state::SharedGameState;
@ -610,7 +610,7 @@ impl NPC {
self.cond.set_alive(false);
}
if self.y > stage.map.height as i32 * 0x2000 {
if self.y > stage.map.height as i32 * state.tile_size.as_int() * 0x200 {
// out of map
self.cond.set_alive(false);
}
@ -651,7 +651,7 @@ impl NPC {
self.anim_num = 0;
let player = self.get_closest_player_mut(players);
if abs(player.x - self.x) < 0x1000 && player.y < self.y + 0x1000 && player.y > self.y - 0x2000 {
if (player.x - self.x).abs() < 0x1000 && player.y < self.y + 0x1000 && player.y > self.y - 0x2000 {
state.sound_manager.play_sfx(43);
self.action_num = 1;
}
@ -666,6 +666,7 @@ impl NPC {
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 3 };
self.anim_rect = state.constants.npc.n085_terminal[self.anim_num as usize + dir_offset];
Ok(())
@ -706,8 +707,8 @@ impl NPC {
{
let i = self.get_closest_player_idx_mut(&players);
if abs(players[i].x - self.x) < 480 * 0x200
&& abs(players[i].y - self.y) < 240 * 0x200
if (players[i].x - self.x).abs() < 480 * 0x200
&& (players[i].y - self.y).abs() < 240 * 0x200
&& self.rng.range(0..5) == 1
{
let mut particle = NPC::create(199, &state.npc_table);
@ -724,7 +725,7 @@ impl NPC {
continue;
}
if abs(player.y - self.y) < 0x1000 && player.x < self.x && player.x > self.x - 96 * 0x200 {
if (player.y - self.y).abs() < 0x1000 && player.x < self.x && player.x > self.x - 96 * 0x200 {
player.vel_x -= 0x88;
player.cond.set_increase_acceleration(true);
}
@ -766,8 +767,8 @@ impl NPC {
{
let i = self.get_closest_player_idx_mut(&players);
if abs(players[i].x - self.x) < 480 * 0x200
&& abs(players[i].y - self.y) < 240 * 0x200
if (players[i].x - self.x).abs() < 480 * 0x200
&& (players[i].y - self.y).abs() < 240 * 0x200
&& self.rng.range(0..5) == 1
{
let mut particle = NPC::create(199, &state.npc_table);
@ -784,7 +785,7 @@ impl NPC {
continue;
}
if abs(player.x - self.x) < 0x1000 && player.y < self.y && player.y > self.y - 96 * 0x200 {
if (player.x - self.x).abs() < 0x1000 && player.y < self.y && player.y > self.y - 96 * 0x200 {
player.vel_y -= 0x88;
}
}
@ -825,8 +826,8 @@ impl NPC {
{
let i = self.get_closest_player_idx_mut(&players);
if abs(players[i].x - self.x) < 480 * 0x200
&& abs(players[i].y - self.y) < 240 * 0x200
if (players[i].x - self.x).abs() < 480 * 0x200
&& (players[i].y - self.y).abs() < 240 * 0x200
&& self.rng.range(0..5) == 1
{
let mut particle = NPC::create(199, &state.npc_table);
@ -839,7 +840,7 @@ impl NPC {
}
for player in players.iter_mut() {
if abs(player.y - self.y) < 0x1000 && player.x > self.x && player.x < self.x + 96 * 0x200 {
if (player.y - self.y).abs() < 0x1000 && player.x > self.x && player.x < self.x + 96 * 0x200 {
player.vel_x += 0x88;
player.cond.set_increase_acceleration(true);
}
@ -881,8 +882,8 @@ impl NPC {
{
let i = self.get_closest_player_idx_mut(&players);
if abs(players[i].x - self.x) < 480 * 0x200
&& abs(players[i].y - self.y) < 240 * 0x200
if (players[i].x - self.x).abs() < 480 * 0x200
&& (players[i].y - self.y).abs() < 240 * 0x200
&& self.rng.range(0..5) == 1
{
let mut particle = NPC::create(199, &state.npc_table);
@ -895,7 +896,7 @@ impl NPC {
}
for player in players.iter_mut() {
if abs(player.x - self.x) < 0x1000 && player.y > self.y && player.y < self.y + 96 * 0x200 {
if (player.x - self.x).abs() < 0x1000 && player.y > self.y && player.y < self.y + 96 * 0x200 {
player.vel_y += 0x88;
}
}
@ -959,14 +960,7 @@ impl NPC {
}
}
10 => {
self.anim_counter += 1;
if self.anim_counter > 2 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num > 2 {
self.anim_num = 2;
}
}
self.animate(2, 2, 2);
for player in players.iter() {
if player.y > self.y {
@ -1246,7 +1240,7 @@ impl NPC {
_ => (),
}
self.vel_x = clamp(self.vel_x, -0x200, 0x200);
self.vel_x = self.vel_x.clamp(-0x200, 0x200);
self.x += self.vel_x;
if self.anim_num != 149 {
@ -1390,7 +1384,7 @@ impl NPC {
_ => (),
}
self.vel_y = clamp(self.vel_y, -0x200, 0x200);
self.vel_y = self.vel_y.clamp(-0x200, 0x200);
self.y += self.vel_y;
if self.anim_num != 149 {
@ -1798,6 +1792,91 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n246_press_proximity(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x800;
}
for player in players.iter() {
if player.x < self.x + 0x1000
&& player.x > self.x - 0x1000
&& player.y > self.y + 0x1000
&& player.y < self.y + 0x10000
{
self.action_num = 5;
}
}
}
5 => {
if self.flags.hit_bottom_wall() {
self.action_num = 10;
self.anim_counter = 0;
self.anim_num = 1;
}
}
10 => {
self.animate(2, 2, 2);
for player in players.iter() {
if player.y > self.y {
self.npc_flags.set_solid_hard(false);
self.damage = 127;
} else {
self.npc_flags.set_solid_hard(true);
self.damage = 0;
}
}
if self.flags.hit_bottom_wall() {
if self.anim_num > 1 {
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y;
for _ in 0..4 {
npc.vel_x = self.rng.range(-0x155..0x155) as i32;
npc.vel_y = self.rng.range(-0x600..0) as i32;
let _ = npc_list.spawn(0x100, npc.clone());
}
state.quake_counter = 10;
state.sound_manager.play_sfx(26);
}
self.action_num = 1;
self.anim_num = 0;
self.damage = 0;
self.npc_flags.set_solid_hard(true);
}
}
_ => (),
}
if self.action_num >= 5 {
self.vel_y += 0x80;
if self.vel_y > 0x5ff {
self.vel_y = 0x5ff;
}
self.y += self.vel_y;
}
self.anim_rect = state.constants.npc.n246_press_proximity[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n253_experience_capsule(
&mut self,
state: &mut SharedGameState,
@ -1832,12 +1911,368 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n271_ironhead_block(&mut self, state: &mut SharedGameState, stage: &mut Stage) -> 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
{
self.cond.set_alive(false);
return Ok(());
}
if self.action_num == 0 {
self.action_num = 1;
if self.rng.range(0..9) == 9 {
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);
} else {
self.anim_rect =
Rect::new_size(16 * (self.rng.range(0..3) as u16), 16 * (self.rng.range(0..3) as u16 / 3), 16, 16);
}
self.vel_x = self.direction.vector_x() * 2 * self.rng.range(256..512);
self.vel_y = self.rng.range(-512..512);
}
if self.vel_y < 0 && self.y - (self.hit_bounds.top as i32) < 0x1000 {
self.vel_y = -self.vel_y;
state.create_caret(self.x, self.y - 0x1000, CaretType::LittleParticles, Direction::Left);
state.create_caret(self.x, self.y - 0x1000, CaretType::LittleParticles, Direction::Left);
}
if self.vel_y > 0 && (self.hit_bounds.bottom as i32) + self.y > 0x1D000 {
self.vel_y = -self.vel_y;
state.create_caret(self.x, self.y + 0x1000, CaretType::LittleParticles, Direction::Left);
state.create_caret(self.x, self.y + 0x1000, CaretType::LittleParticles, Direction::Left);
}
self.x += self.vel_x;
self.y += self.vel_y;
Ok(())
}
pub(crate) fn tick_n272_ironhead_block_generator(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter = self.rng.range(0..200) as u16;
}
if self.action_num == 1 {
if self.action_counter > 0 {
self.action_counter -= 1;
} else {
self.action_num = 0;
let mut npc = NPC::create(271, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y + self.rng.range(-32..32) * 0x200;
npc.direction = self.direction;
let _ = npc_list.spawn(0x100, npc);
}
}
Ok(())
}
pub(crate) fn tick_n279_large_falling_block(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
) -> GameResult {
match self.action_num {
0 => {
if self.action_num == 0 {
match self.direction {
Direction::Left => {
self.action_num = 100;
self.npc_flags.set_invulnerable(true);
self.anim_num = 0;
}
Direction::Up => {
self.anim_num = 0;
self.action_num = 10;
}
Direction::Right => {
self.action_num = 100;
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);
}
_ => (),
}
}
if self.direction == Direction::Up {
self.action_num = 11;
self.action_counter = 16;
if self.action_counter > 0 {
self.action_counter = self.action_counter.saturating_sub(2);
} else {
self.action_num = 100;
self.npc_flags.set_invulnerable(true);
}
}
}
10 | 11 => {
if self.action_num == 10 {
self.action_num = 11;
self.action_counter = 16;
}
if self.action_counter > 0 {
self.action_counter = self.action_counter.saturating_sub(2);
} else {
self.action_num = 100;
self.npc_flags.set_invulnerable(true);
}
}
100 => {
self.vel_y += 0x40;
if self.vel_y > 0x700 {
self.vel_y = 0x700;
}
if self.y > 0x10000 {
self.npc_flags.set_ignore_solidity(false);
}
if self.flags.hit_bottom_wall() {
self.vel_y = -0x200;
self.action_num = 110;
self.npc_flags.set_ignore_solidity(true);
state.sound_manager.play_sfx(26);
state.quake_counter = 10;
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
for _ in 0..4 {
npc.x = self.x + self.rng.range(-12..12) * 0x200;
npc.y = self.y + 0x2000;
npc.vel_x = self.rng.range(-0x155..0x155);
npc.vel_y = self.rng.range(-0x600..0);
let _ = npc_list.spawn(0x100, npc.clone());
}
}
}
110 => {
self.vel_y += 0x40;
if self.y > stage.map.height as i32 * state.tile_size.as_int() * 0x200 + 0x4000 {
self.cond.set_alive(false);
return Ok(());
}
}
_ => (),
}
let player = self.get_closest_player_ref(&players);
self.damage = if player.y > self.y { 10 } else { 0 };
self.y += self.vel_y;
self.anim_rect = state.constants.npc.n279_large_falling_block[self.anim_num as usize];
if self.action_num == 11 {
self.anim_rect.top += self.action_counter;
self.anim_rect.bottom += self.action_counter;
self.display_bounds.top = (16u32).saturating_sub(self.action_counter as u32) * 0x200;
}
Ok(())
}
pub(crate) fn tick_n292_quake(&mut self, state: &mut SharedGameState) -> GameResult {
state.quake_counter = 10;
Ok(())
}
pub(crate) fn tick_n294_quake_falling_block_generator(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
stage: &mut Stage,
) -> GameResult {
match self.action_num {
0 => {
if self.x < (stage.map.width as i32 - 6) * state.tile_size.as_int() * 0x200 {
self.action_num = 1;
self.action_counter = 0;
}
}
1 => {
self.action_counter += 1;
let player = &players[0];
if player.equip.has_booster_2_0() {
self.x = player.x + 0x8000;
if self.x < 0x34000 {
self.x = 0x34000;
}
} else {
self.x = player.x + 0xC000;
if self.x < 0x2E000 {
self.x = 0x2E000;
}
}
let map_start = (stage.map.width as i32 - 10) * state.tile_size.as_int() * 0x200;
if self.x > map_start {
self.x = map_start;
}
if self.action_counter > 24 {
let mut npc = NPC::create(279, &state.npc_table);
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;
npc.y = player.y - 0x1C000;
npc.direction = if self.rng.range(0..10) & 1 != 0 { Direction::Left } else { Direction::Right };
let _ = npc_list.spawn(0x100, npc);
self.action_counter = self.rng.range(0..15) as u16;
}
}
_ => (),
}
Ok(())
}
pub(crate) fn tick_n295_cloud(&mut self, state: &mut SharedGameState) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = self.tsc_direction % 4;
match self.tsc_direction {
0 => {
self.vel_y = -0x1000;
self.display_bounds.right = 0xD000;
self.display_bounds.left = 0xD000;
}
1 => {
self.vel_y = -0x800;
self.display_bounds.right = 0x7000;
self.display_bounds.left = 0x7000;
}
2 => {
self.vel_y = -0x400;
self.display_bounds.right = 0x4000;
self.display_bounds.left = 0x4000;
}
3 => {
self.vel_y = -0x200;
self.display_bounds.right = 0x2800;
self.display_bounds.left = 0x2800;
}
4 => {
self.vel_x = -0x400;
self.display_bounds.right = 0xD000;
self.display_bounds.left = 0xD000;
}
5 => {
self.vel_x = -0x200;
self.display_bounds.right = 0x7000;
self.display_bounds.left = 0x7000;
}
6 => {
self.vel_x = -0x100;
self.display_bounds.right = 0x4000;
self.display_bounds.left = 0x4000;
}
7 => {
self.vel_x = -0x80;
self.display_bounds.right = 0x2800;
self.display_bounds.left = 0x2800;
}
_ => (),
}
}
if self.action_num == 1 {
self.x += self.vel_x;
self.y += self.vel_y;
if self.x < -0x8000 {
self.cond.set_alive(false);
}
if self.y < -0x4000 {
self.cond.set_alive(false);
}
}
self.anim_rect = state.constants.npc.n295_cloud[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n296_cloud_generator(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
self.action_counter += 1;
if self.action_counter <= 16 {
return Ok(());
}
self.action_counter = self.rng.range(0..16) as u16;
let dir = self.rng.range(0..100) as u16 & 3;
let mut npc = NPC::create(295, &state.npc_table);
npc.cond.set_alive(true);
if self.direction != Direction::Left {
let layer = match dir {
0 => 128,
1 => 85,
2 => 64,
3 => 0,
_ => unsafe {
unreachable_unchecked();
},
};
npc.x = self.x;
npc.y = self.y + self.rng.range(-7..7) * state.tile_size.as_int() * 0x200;
npc.tsc_direction = dir + 4;
let _ = npc_list.spawn(layer, npc);
} else {
let layer = match dir {
0 => 384,
1 => 128,
2 => 64,
3 => 0,
_ => unsafe { unreachable_unchecked() },
};
npc.x = self.x + self.rng.range(-10..10) * state.tile_size.as_int() * 0x200;
npc.y = self.y;
npc.tsc_direction = dir;
let _ = npc_list.spawn(layer, 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) {
self.x = npc.x + 0x2000;
@ -2102,7 +2537,7 @@ impl NPC {
npc_list: &NPCList,
) -> GameResult {
let player = self.get_closest_player_mut(players);
if abs(player.x - self.x) < 320 * 0x200
if (player.x - self.x).abs() < 320 * 0x200
&& player.y < self.y + 320 * 0x200
&& player.y > self.y - 160 * 0x200
&& self.rng.range(0..100) == 2
@ -2117,4 +2552,16 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n360_credits_thank_you(&mut self, state: &mut SharedGameState) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
self.x -= 0x1000;
self.y -= 0x1000;
}
self.anim_rect = state.constants.npc.n360_credits_thank_you;
Ok(())
}
}

View file

@ -6,11 +6,14 @@ use crate::caret::CaretType;
use crate::common::{Direction, CDEG_RAD};
use crate::components::flash::Flash;
use crate::framework::error::GameResult;
use crate::npc::boss::BossNPC;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::physics::PhysicalEntity;
use crate::player::Player;
use crate::rng::RNG;
use crate::shared_game_state::SharedGameState;
use crate::stage::Stage;
impl NPC {
pub(crate) fn tick_n066_misery_bubble(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
@ -573,7 +576,7 @@ impl NPC {
npc.y = self.target_y;
let _ = npc_list.spawn(0x100, npc.clone());
npc.x = self.x - 0x2000;
npc.x = self.target_x - 0x2000;
npc.direction = Direction::Right;
let _ = npc_list.spawn(0x100, npc);
}
@ -716,7 +719,7 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n249_misery_boss_energy_shot(&mut self, state: &mut SharedGameState) -> GameResult {
pub(crate) fn tick_n249_misery_boss_appearing(&mut self, state: &mut SharedGameState) -> GameResult {
self.action_counter2 += 1;
if self.action_counter2 > 8 {
self.cond.set_alive(false);
@ -724,12 +727,616 @@ impl NPC {
if self.direction == Direction::Left {
self.x -= 0x400;
self.anim_rect = state.constants.npc.n249_misery_boss_energy_shot[0];
self.anim_rect = state.constants.npc.n249_misery_boss_appearing[0];
} else {
self.x += 0x400;
self.anim_rect = state.constants.npc.n249_misery_boss_energy_shot[1];
self.anim_rect = state.constants.npc.n249_misery_boss_appearing[1];
}
Ok(())
}
pub(crate) fn tick_n250_misery_boss_lighting_ball(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.target_y = self.y;
self.vel_x = 0;
self.vel_y = -0x200;
}
let player = self.get_closest_player_ref(&players);
self.vel_x += if self.x < player.x { 0x10 } else { -0x10 };
self.vel_y += if self.y < self.target_y { 0x20 } else { -0x20 };
self.vel_x = self.vel_x.clamp(-0x200, 0x200);
self.vel_y = self.vel_y.clamp(-0x200, 0x200);
self.x += self.vel_x;
self.y += self.vel_y;
self.animate(2, 0, 1);
if player.x > self.x - 0x1000 && player.x < self.x + 0x1000 && player.y > self.y {
self.action_num = 10;
}
}
10 | 11 => {
if self.action_num == 10 {
self.action_num = 11;
self.action_counter = 0;
}
self.action_counter += 1;
if self.action_counter > 10 {
let mut npc = NPC::create(251, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y;
let _ = npc_list.spawn(0x100, npc);
state.sound_manager.play_sfx(101);
self.cond.set_alive(false);
return Ok(());
}
self.anim_num = if (self.action_counter & 2) != 0 { 2 } else { 1 };
}
_ => (),
}
self.anim_rect = state.constants.npc.n250_misery_boss_lighting_ball[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n251_misery_boss_lighting(
&mut self,
state: &mut SharedGameState,
npc_list: &NPCList,
) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
}
if self.action_num == 1 {
self.anim_num = (self.anim_num + 1) & 1;
self.y += 0x1000;
if self.flags.any_flag() {
npc_list.create_death_smoke(self.x, self.y, self.display_bounds.right as usize, 3, state, &self.rng);
self.cond.set_alive(false);
}
}
self.anim_rect = state.constants.npc.n251_misery_boss_lighting[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n252_misery_boss_bats(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter = 0;
self.action_counter2 = self.tsc_direction;
}
self.action_counter2 += 2;
self.action_counter2 &= 0xff;
if self.action_counter < 192 {
self.action_counter += 1;
}
if let Some(parent) = self.get_parent_ref_mut(npc_list) {
self.x = parent.x
+ self.action_counter as i32 * ((self.action_counter2 as f64 * CDEG_RAD).cos() * -512.0) as i32
/ 4;
self.y = parent.y
+ self.action_counter as i32 * ((self.action_counter2 as f64 * CDEG_RAD).sin() * -512.0) as i32
/ 4;
if parent.action_num == 151 {
self.action_num = 10;
self.anim_num = 0;
}
}
}
10 | 11 => {
if self.action_num == 10 {
self.action_num = 11;
self.npc_flags.set_shootable(true);
self.npc_flags.set_invulnerable(false);
self.npc_flags.set_ignore_solidity(false);
let player = self.get_closest_player_ref(&players);
let deg = f64::atan2((self.y - player.y) as f64, (self.x - player.x) as f64);
let deg = deg + self.rng.range(-3..3) as f64 * CDEG_RAD;
self.vel_x = (deg.cos() * -512.0) as i32;
self.vel_y = (deg.sin() * -512.0) as i32;
self.anim_num = 1;
self.anim_counter = 0;
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
}
self.x += self.vel_x;
self.y += self.vel_y;
if self.flags.hit_anything() {
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y;
let _ = npc_list.spawn(0x100, npc.clone());
let _ = npc_list.spawn(0x100, npc.clone());
let _ = npc_list.spawn(0x100, npc);
self.cond.set_alive(false);
}
self.animate(4, 1, 3);
}
_ => (),
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
self.anim_rect = state.constants.npc.n252_misery_boss_bats[self.anim_num as usize + dir_offset];
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 {
if self.action_num < 100 && (!boss.parts[0].cond.alive() || self.life < 400) {
self.action_num = 100;
}
match self.action_num {
0 | 1 => {
if self.action_num == 9 {
self.action_num = 1;
self.y -= 0x1000;
state.sound_manager.play_sfx(29);
}
self.action_counter += 1;
self.anim_num = if self.action_counter & 2 != 0 { 9 } else { 0 };
}
10 => {
self.action_num = 11;
self.anim_num = 9;
}
20 | 21 => {
if self.action_num == 21 {
self.action_num = 21;
self.action_counter = 0;
self.anim_num = 0;
self.anim_counter = 0;
state.npc_super_pos.0 = 0;
}
self.vel_x = 7 * self.vel_x / 8;
self.vel_y = 7 * self.vel_y / 8;
self.animate(20, 0, 1);
self.action_counter += 1;
if self.action_counter > 100 {
self.action_num = 30;
}
let player = self.get_closest_player_ref(&players);
self.direction = if player.x > self.x { Direction::Left } else { Direction::Right };
}
30 | 31 => {
if self.action_num == 31 {
self.action_num = 31;
self.action_counter = 0;
self.anim_num = 2;
self.action_counter3 = self.life;
}
self.animate(1, 2, 3);
if self.flags.hit_bottom_wall() {
self.vel_y = -0x200;
}
let player = self.get_closest_player_ref(&players);
self.vel_x += if self.x > boss.parts[0].x { -0x20 } else { 0x20 };
self.vel_y += if self.y > player.y { -0x10 } else { 0x10 };
self.vel_x = self.vel_x.clamp(-0x200, 0x200);
self.vel_y = self.vel_y.clamp(-0x200, 0x200);
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
self.action_counter += 1;
if self.action_counter > 150 && (self.life + 20 < self.action_counter3 || state.npc_super_pos.0 != 0) {
state.npc_super_pos.0 = 0;
self.action_num = 40;
}
if boss.parts[0].anim_num != 0 && self.action_counter > 250 {
self.action_num = 50;
}
}
40 | 41 => {
if self.action_num == 40 {
self.action_num = 41;
self.action_counter = 0;
self.vel_x = 0;
self.vel_y = 0;
let player = self.get_closest_player_ref(&players);
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
self.action_counter3 = if player.y >= 0x14000 { 289 } else { 290 };
}
self.action_counter += 1;
self.anim_num = if self.action_counter & 2 != 0 { 4 } else { 5 };
if self.action_counter % 6 == 1 {
state.sound_manager.play_sfx(39);
let mut npc = NPC::create(self.action_counter3, &state.npc_table);
npc.cond.set_alive(true);
if self.action_counter3 == 289 {
npc.x = self.x + self.rng.range(-64..64) * 0x200;
npc.y = self.y + self.rng.range(-32..32) * 0x200;
} else {
npc.x = self.x + self.rng.range(-32..32) * 0x200;
npc.y = self.y + self.rng.range(-64..64) * 0x200;
}
npc.x = npc.x.clamp(
2 * state.tile_size.as_int() * 0x200,
(stage.map.width as i32 - 2) * state.tile_size.as_int() * 0x200,
);
npc.y = npc.y.clamp(
2 * state.tile_size.as_int() * 0x200,
(stage.map.height as i32 - 2) * state.tile_size.as_int() * 0x200,
);
let _ = npc_list.spawn(0x100, npc);
}
if self.action_counter > 50 {
self.action_num = 42;
self.action_counter = 0;
let player = self.get_closest_player_ref(&players);
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
}
}
42 => {
self.action_counter += 1;
self.anim_num = 6;
if self.action_counter > 50 {
self.action_num = 30;
self.vel_y = -0x200;
self.vel_x = self.direction.vector_x() * 0x200;
}
}
50 | 51 => {
let player = self.get_closest_player_ref(&players);
if self.action_num == 50 {
self.action_num = 51;
self.action_counter = 0;
self.vel_x = 0;
self.vel_y = 0;
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
state.sound_manager.play_sfx(103);
}
self.action_counter += 1;
self.anim_num = if self.action_counter & 2 != 0 { 4 } else { 5 };
let period = if player.equip.has_booster_2_0() { 10 } else { 24 };
if self.action_counter % period == 1 {
state.sound_manager.play_sfx(39);
let mut npc = NPC::create(301, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x + 0x1400 * self.direction.vector_x();
npc.y = self.y;
npc.tsc_direction = match ((self.action_counter / 6) & 3, self.direction) {
(0, Direction::Left) => 0x58,
(1, Direction::Left) => 0x6C,
(2, Direction::Left) => 0x94,
(3, Direction::Left) => 0xA8,
(0, _) => 0xD8,
(1, _) => 0xEC,
(2, _) => 0x14,
(3, _) => 0x28,
_ => unsafe {
unreachable_unchecked();
},
};
let _ = npc_list.spawn(0x100, npc);
}
if self.action_counter > 50 {
self.action_num = 42;
self.action_counter = 0;
let player = self.get_closest_player_ref(&players);
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
}
}
99 => {
self.vel_x = 0;
self.vel_y = 0;
self.anim_num = 9;
self.npc_flags.set_shootable(false);
}
100 | 101 => {
if self.action_num == 100 {
self.action_num = 101;
self.anim_num = 9;
self.damage = 0;
self.npc_flags.set_shootable(false);
self.npc_flags.set_ignore_solidity(true);
self.vel_y = -0x200;
self.shock += 50;
self.hit_bounds.bottom = 0x1800;
boss.parts[0].anim_num += 1;
}
self.vel_y += 0x20;
if self.y > 0x1B000 - self.hit_bounds.bottom as i32 {
self.y = 0x1B000 - self.hit_bounds.bottom as i32;
self.action_num = 102;
self.anim_num = 10;
self.vel_x = 0;
self.vel_y = 0;
}
}
_ => (),
}
self.x += if self.shock > 0 { self.vel_x / 2 } else { self.vel_x };
self.y += self.vel_y;
let dir_offset = if self.direction == Direction::Left { 0 } else { 11 };
self.anim_rect = state.constants.npc.n283_misery_possessed[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n289_critter_orange(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
stage: &mut Stage,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 2;
let player = self.get_closest_player_ref(&players);
self.direction = if player.x > self.x { Direction::Left } else { Direction::Right };
}
self.action_counter += 1;
if self.action_counter > 16 {
self.action_num = 10;
self.display_bounds.top = 0x1000;
self.display_bounds.bottom = 0x1000;
self.damage = 2;
self.npc_flags.set_shootable(true);
}
}
10 => {
if self.flags.hit_bottom_wall() {
self.action_num = 11;
self.anim_num = 0;
self.action_counter = 0;
self.vel_x = 0;
let player = self.get_closest_player_ref(&players);
self.direction = if player.x > self.x { Direction::Left } else { Direction::Right };
}
}
11 => {
self.action_counter += 1;
if self.action_counter > 10 {
self.anim_num = 2;
self.action_counter2 += 1;
self.action_num = if self.action_counter2 > 4 { 12 } else { 10 };
state.sound_manager.play_sfx(30);
self.vel_x = self.direction.vector_x() * 0x200;
self.vel_y = -0x600;
}
}
12 => {
self.npc_flags.set_ignore_solidity(true);
if self.y > stage.map.height as i32 * state.tile_size.as_int() * 0x200 {
self.vanish(state);
}
}
_ => (),
}
if self.action_num >= 10 {
self.vel_y += 0x40;
}
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
self.x += self.vel_x;
self.y += self.vel_y;
let dir_offset = if self.direction == Direction::Left { 0 } else { 3 };
self.anim_rect = state.constants.npc.n289_critter_orange[self.anim_num as usize + dir_offset];
if self.action_num == 1 {
self.anim_rect.top += 8 - self.action_counter / 2;
self.anim_rect.bottom -= 8 + self.action_counter / 2;
self.display_bounds.top = (self.action_counter as u32 * 0x200) / 2;
self.display_bounds.bottom = (self.action_counter as u32 * 0x200) / 2;
}
Ok(())
}
pub(crate) fn tick_n290_bat_misery(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
stage: &mut Stage,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 2;
let player = self.get_closest_player_ref(&players);
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
}
self.action_counter += 1;
if self.action_counter > 16 {
self.action_num = 10;
self.display_bounds.top = 0x1000;
self.display_bounds.bottom = 0x1000;
self.damage = 2;
self.npc_flags.set_shootable(true);
self.target_y = self.y;
self.vel_y = 0x400;
}
}
10 => {
self.animate(2, 0, 2);
self.vel_y += if self.y >= self.target_y { -0x40 } else { 0x40 };
self.vel_x += if self.direction == Direction::Left { -0x10 } else { 0x10 };
if self.x < 0
|| self.y < 0
|| self.x > (stage.map.width as i32 * state.tile_size.as_int())
|| self.y > (stage.map.height as i32 * state.tile_size.as_int())
{
self.vanish(state);
return Ok(());
}
}
_ => (),
}
self.x += self.vel_x;
self.y += self.vel_y;
let dir_offset = if self.direction == Direction::Left { 0 } else { 3 };
self.anim_rect = state.constants.npc.n290_bat_misery[self.anim_num as usize + dir_offset];
if self.action_num == 1 {
self.anim_rect.top += 8 - self.action_counter / 2;
self.anim_rect.bottom -= 8 + self.action_counter / 2;
self.display_bounds.top = (self.action_counter as u32 * 0x200) / 2;
self.display_bounds.bottom = (self.action_counter as u32 * 0x200) / 2;
}
Ok(())
}
pub(crate) fn tick_n301_misery_fish_missile(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter2 = self.tsc_direction;
}
let radians = self.action_counter2 as f64 * CDEG_RAD;
self.vel_x = 2 * (radians.cos() * 512.0) as i32;
self.vel_y = 2 * (radians.sin() * 512.0) as i32;
self.x += self.vel_x;
self.y += self.vel_y;
let player = self.get_closest_player_mut(players);
let direction = f64::atan2(-(self.y - player.y) as f64, -(self.x - player.x) as f64);
if direction < radians {
if radians - direction < std::f64::consts::PI {
self.action_counter2 = self.action_counter2.wrapping_sub(1) & 0xff;
} else {
self.action_counter2 = (self.action_counter2 + 1) & 0xff;
}
} else {
if direction - radians < std::f64::consts::PI {
self.action_counter2 = (self.action_counter2 + 1) & 0xff;
} else {
self.action_counter2 = self.action_counter2.wrapping_sub(1) & 0xff;
}
}
}
_ => (),
}
self.anim_counter += 1;
if self.anim_counter > 2 {
self.anim_counter = 0;
state.create_caret(self.x, self.y, CaretType::Exhaust, Direction::FacingPlayer);
}
self.anim_num = (self.action_counter2 + 0x10) / 0x20;
if self.anim_num > 7 {
self.anim_num = 7;
}
self.anim_rect = state.constants.npc.n301_misery_fish_missile[self.anim_num as usize];
Ok(())
}
}

View file

@ -1,25 +1,27 @@
pub(in super) mod balrog;
pub(in super) mod booster;
pub(in super) mod chaco;
pub(in super) mod characters;
pub(in super) mod curly;
pub(in super) mod doctor;
pub(in super) mod egg_corridor;
pub(in super) mod first_cave;
pub(in super) mod grasstown;
pub(in super) mod igor;
pub(in super) mod intro;
pub(in super) mod last_cave;
pub(in super) mod maze;
pub(in super) mod mimiga_village;
pub(in super) mod misc;
pub(in super) mod misery;
pub(in super) mod outer_wall;
pub(in super) mod pickups;
pub(in super) mod plantation;
pub(in super) mod quote;
pub(in super) mod sand_zone;
pub(in super) mod santa;
pub(in super) mod sue;
pub(in super) mod toroko;
pub(in super) mod weapon_trail;
pub(super) mod balcony;
pub(super) mod balrog;
pub(super) mod booster;
pub(super) mod chaco;
pub(super) mod characters;
pub(super) mod curly;
pub(super) mod doctor;
pub(super) mod egg_corridor;
pub(super) mod first_cave;
pub(super) mod grasstown;
pub(super) mod hell;
pub(super) mod igor;
pub(super) mod intro;
pub(super) mod last_cave;
pub(super) mod maze;
pub(super) mod mimiga_village;
pub(super) mod misc;
pub(super) mod misery;
pub(super) mod outer_wall;
pub(super) mod pickups;
pub(super) mod plantation;
pub(super) mod quote;
pub(super) mod sand_zone;
pub(super) mod santa;
pub(super) mod sue;
pub(super) mod toroko;
pub(super) mod weapon_trail;

View file

@ -301,4 +301,77 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n347_hoppy(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
}
self.anim_num = 0;
let player = self.get_closest_player_ref(&players);
if player.y < self.y + 0x10000 && player.y > self.y - 0x10000 {
self.action_num = 10;
self.action_counter = 0;
self.anim_num = 1;
}
}
10 => {
self.action_counter += 1;
if self.action_counter == 4 {
self.anim_num = 2;
}
if self.action_counter > 12 {
self.action_num = 12;
self.anim_num = 3;
self.vel_x = 0x700;
state.sound_manager.play_sfx(6);
}
}
12 => {
let player = self.get_closest_player_ref(&players);
if player.y < self.y {
self.vel_y = -0xAA;
} else {
self.vel_y = 0xAA;
}
if self.flags.hit_left_wall() {
self.action_num = 13;
self.action_counter = 0;
self.anim_num = 2;
self.vel_x = 0;
self.vel_y = 0;
} else {
self.vel_x -= 0x2A;
if self.vel_x < -0x5FF {
self.vel_x = -0x5FF;
}
self.x += self.vel_x;
self.y += self.vel_y;
}
}
13 => {
self.action_counter += 1;
if self.action_counter == 2 {
self.anim_num = 1;
}
if self.action_counter == 6 {
self.anim_num = 0;
}
if self.action_counter > 16 {
self.action_num = 1;
}
}
_ => (),
}
self.anim_rect = state.constants.npc.n347_hoppy[self.anim_num as usize];
Ok(())
}
}

View file

@ -1,5 +1,5 @@
use crate::caret::CaretType;
use crate::common::Direction;
use crate::common::{Direction, CDEG_RAD};
use crate::framework::error::GameResult;
use crate::npc::list::NPCList;
use crate::npc::NPC;
@ -481,4 +481,733 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n232_orangebell(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.target_x = self.x;
self.target_y = self.y;
self.vel_y = 0x200;
for _ in 0..8 {
let mut npc = NPC::create(233, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y;
npc.direction = self.direction;
npc.parent_id = self.id;
let _ = npc_list.spawn(0x100, npc);
}
}
if self.vel_x < 0 && self.flags.hit_left_wall() {
self.direction = Direction::Right;
}
if self.vel_x > 0 && self.flags.hit_right_wall() {
self.direction = Direction::Left;
}
self.vel_x = self.direction.vector_x() * 0x100;
self.vel_y += if self.y < self.target_y { 8 } else { -8 };
self.vel_y = self.vel_y.clamp(-0x200, 0x200);
self.animate(5, 0, 2);
}
_ => (),
}
self.x += self.vel_x;
self.y += self.vel_y;
self.anim_rect = state.constants.npc.n232_orangebell[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n233_orangebell_bat(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.vel_x = ((self.rng.range(0..255) as f64 * CDEG_RAD).cos() * -512.0) as i32;
self.vel_y = ((self.rng.range(0..255) as f64 * CDEG_RAD).sin() * -512.0) as i32;
self.action_counter2 = 120;
self.vel_y2 = self.rng.range(-32..32) * 0x200;
}
if let Some(parent) = self.get_parent_ref_mut(npc_list) {
if parent.npc_type == 232 {
self.target_x = parent.x;
self.target_y = parent.y;
self.direction = parent.direction;
}
}
self.vel_x += (self.target_x - self.x).signum() * 8;
self.vel_y += ((self.target_y + self.vel_y2) - self.y).signum() * 0x20;
self.vel_x = self.vel_x.clamp(-0x400, 0x400);
self.vel_y = self.vel_y.clamp(-0x400, 0x400);
if self.action_counter2 >= 120 {
let player = self.get_closest_player_ref(&players);
if self.x - 0x1000 < player.x
&& self.x + 0x1000 > player.x
&& self.y < player.y
&& self.y + 0x16000 > player.y
{
self.vel_x /= 4;
self.vel_y = 0;
self.action_num = 3;
self.npc_flags.set_ignore_solidity(false);
}
} else {
self.action_counter2 += 1;
}
}
3 => {
self.vel_y += 0x40;
if self.vel_y > 0x5ff {
self.vel_y = 0x5ff;
}
if self.flags.hit_bottom_wall() {
self.vel_y = 0;
self.vel_x *= 2;
self.action_counter2 = 0;
self.action_num = 1;
self.npc_flags.set_ignore_solidity(true);
}
}
_ => (),
}
self.x += self.vel_x;
self.y += self.vel_y;
if self.action_num == 3 {
self.anim_num = 3;
} else {
self.animate(1, 0, 2);
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
self.anim_rect = state.constants.npc.n233_orangebell_bat[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n235_midorin(&mut self, state: &mut SharedGameState) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 0;
self.anim_counter = 0;
self.vel_x = 0;
}
if self.rng.range(0..30) == 1 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
if self.rng.range(0..30) == 1 {
self.action_num = 10;
self.action_counter = 0;
self.anim_num = 1;
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 8 {
self.action_num = 1;
self.anim_num = 0;
}
}
10 | 11 => {
if self.action_num == 10 {
self.action_num = 11;
self.action_counter = self.rng.range(0..16) as u16;
self.anim_num = 2;
self.anim_counter = 0;
self.direction = if self.rng.range(0..9) & 1 != 0 { Direction::Left } else { Direction::Right };
}
if self.direction == Direction::Left && self.flags.hit_left_wall() {
self.direction = Direction::Right;
}
if self.direction == Direction::Right && self.flags.hit_right_wall() {
self.direction = Direction::Left;
}
self.vel_x = self.direction.vector_x() * 0x400;
self.animate(1, 2, 3);
self.action_counter += 1;
if self.action_counter > 64 {
self.action_num = 0;
}
}
_ => (),
}
self.vel_y += 0x20;
if self.vel_y > 0x5ff {
self.vel_y = 0x5ff;
}
self.x += self.vel_x;
self.y += self.vel_y;
if self.anim_num == 2 {
self.hit_bounds.top = 0xa00;
} else {
self.hit_bounds.top = 0x800;
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 4 };
self.anim_rect = state.constants.npc.n235_midorin[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n236_gunfish(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter = self.rng.range(0..50) as u16;
self.target_x = self.x;
self.target_y = self.y;
self.vel_y = 0;
}
if self.action_counter > 0 {
self.action_counter -= 1;
} else {
self.action_num = 2;
self.vel_y = 0x200;
}
}
2 => {
let player = self.get_closest_player_ref(&players);
if self.x >= player.x {
self.direction = Direction::Left;
} else {
self.direction = Direction::Right;
}
if player.x - 0x10000 < self.x
&& player.x + 0x10000 > self.x
&& player.y - 0x4000 < self.y
&& player.y + 0x14000 > self.y
{
self.action_counter += 1;
}
if self.action_counter > 80 {
self.action_num = 10;
self.action_counter = 0;
}
self.animate(1, 0, 1);
}
10 => {
self.action_counter += 1;
if self.action_counter > 20 {
self.action_num = 20;
self.action_counter = 0;
}
self.animate(1, 2, 3);
}
20 => {
self.action_counter += 1;
if self.action_counter > 60 {
self.action_num = 2;
self.action_counter = 0;
}
if self.action_counter % 10 == 3 {
state.sound_manager.play_sfx(39);
let mut npc = NPC::create(237, &state.npc_table);
npc.cond.set_alive(true);
if self.direction == Direction::Left {
npc.x = self.x - 0x1000;
npc.y = self.y - 0x1000;
npc.vel_x = -0x400;
npc.vel_y = -0x400;
} else {
npc.x = self.x + 0x1000;
npc.y = self.y - 0x1000;
npc.vel_x = 0x400;
npc.vel_y = -0x400;
}
let _ = npc_list.spawn(0x100, npc);
}
self.animate(1, 4, 5);
}
_ => (),
}
if self.y < self.target_y {
self.vel_y += 0x10;
} else {
self.vel_y -= 0x10;
}
self.vel_y = self.vel_y.clamp(-0x100, 0x100);
let dir_offset = if self.direction == Direction::Left { 0 } else { 6 };
self.anim_rect = state.constants.npc.n236_gunfish[dir_offset + self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n237_gunfish_projectile(&mut self, state: &mut SharedGameState) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
}
if self.action_num == 1 {
self.action_counter += 1;
let hit = self.flags.hit_anything() || (self.action_counter > 10 && self.flags.in_water());
if hit {
for _ in 0..5 {
state.create_caret(self.x, self.y, CaretType::Bubble, Direction::Left);
}
state.sound_manager.play_sfx(21);
self.cond.set_alive(false);
return Ok(());
}
}
self.vel_y += 0x20;
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
self.x += self.vel_x;
self.y += self.vel_y;
self.anim_rect = state.constants.npc.n237_gunfish_projectile;
Ok(())
}
pub(crate) fn tick_n240_mimiga_jailed(&mut self, state: &mut SharedGameState) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 0;
self.anim_counter = 0;
self.vel_x = 0;
}
if self.rng.range(0..60) == 1 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
if self.rng.range(0..60) == 1 {
self.action_num = 10;
self.action_counter = 0;
self.anim_num = 1;
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 8 {
self.action_num = 1;
self.anim_num = 0;
}
}
10 | 11 => {
if self.action_num == 10 {
self.action_num = 11;
self.action_counter = self.rng.range(0..16) as u16;
self.anim_num = 2;
self.anim_counter = 0;
self.direction = if self.rng.range(0..9) & 1 != 0 { Direction::Left } else { Direction::Right };
}
if self.direction == Direction::Left && self.flags.hit_left_wall() {
self.direction = Direction::Right;
}
if self.direction == Direction::Right && self.flags.hit_right_wall() {
self.direction = Direction::Left;
}
self.vel_x = self.direction.vector_x() * 0x200;
self.animate(4, 2, 5);
self.action_counter += 1;
if self.action_counter > 32 {
self.action_num = 0;
}
}
_ => (),
}
self.vel_y += 0x20;
if self.vel_y > 0x5ff {
self.vel_y = 0x5ff;
}
self.x += self.vel_x;
self.y += self.vel_y;
let dir_offset = if self.direction == Direction::Left { 0 } else { 6 };
self.anim_rect = state.constants.npc.n240_mimiga_jailed[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n273_droll_projectile(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
if self.action_num == 0 {
self.action_num = 1;
}
if self.action_num == 1 {
self.x += self.vel_x;
self.y += self.vel_y;
if self.flags.any_flag() {
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y;
for _ in 0..3 {
let _ = npc_list.spawn(0x100, npc.clone());
}
self.vanish(state);
return Ok(());
}
}
self.action_counter += 1;
if self.action_counter % 5 != 0 {
state.sound_manager.play_sfx(110);
}
self.anim_num += 1;
if self.anim_num > 2 {
self.anim_num = 0;
}
self.anim_rect = state.constants.npc.n273_droll_projectile[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n274_droll(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 | 2 => {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x1000;
self.target_x = self.x;
}
if self.action_num == 1 {
self.vel_x = 0;
self.action_num = 2;
self.anim_num = 0;
}
let player = self.get_closest_player_ref(&players);
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
self.animate(40, 0, 1);
if self.shock > 0 {
self.action_num = 10;
}
}
10 | 11 => {
if self.action_num == 10 {
self.action_num = 11;
self.anim_num = 2;
self.action_counter = 0;
}
self.action_counter += 1;
if self.action_counter > 10 {
self.action_num = 12;
self.anim_num = 3;
self.action_counter2 = 0;
self.vel_y = -0x600;
self.vel_x = self.direction.vector_x() * 0x200;
}
}
12 => {
if self.vel_y > 0 {
self.anim_num = 4;
if self.action_counter2 == 0 {
self.action_counter2 += 1;
let player = self.get_closest_player_ref(&players);
let mut npc = NPC::create(273, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.x;
npc.y = self.y;
let angle = f64::atan2((player.y - 0x1400 - self.y) as f64, (player.x - self.x) as f64);
npc.vel_x = (2048.0 * angle.cos()) as i32;
npc.vel_y = (2048.0 * angle.sin()) as i32;
let _ = npc_list.spawn(0x100, npc);
state.sound_manager.play_sfx(39);
}
}
if self.vel_y > 0x200 {
self.anim_num = 5;
}
if self.flags.hit_bottom_wall() {
self.action_num = 13;
self.anim_num = 2;
self.action_counter = 0;
self.vel_x = 0;
}
}
13 => {
self.vel_x /= 2;
self.action_counter += 1;
if self.action_counter > 10 {
self.action_num = 1;
}
}
_ => (),
}
self.vel_y += 0x55;
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
self.x += self.vel_x;
self.y += self.vel_y;
let dir_offset = if self.direction == Direction::Left { 0 } else { 6 };
self.anim_rect = state.constants.npc.n274_droll[self.anim_num as usize + dir_offset];
Ok(())
}
pub(crate) fn tick_n275_puppy_plantation(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.anim_num = 0;
self.action_counter = 0;
}
if self.rng.range(0..120) == 10 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
let player = self.get_closest_player_ref(&players);
if self.x - 0x8000 < player.x
&& self.x + 0x8000 > player.x
&& self.y - 0x4000 < player.y
&& self.y + 0x2000 > player.y
{
self.animate(3, 2, 3);
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 8 {
self.action_num = 1;
self.anim_num = 0;
}
}
_ => (),
}
self.vel_y += 0x40;
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
self.x += self.vel_x;
self.y += self.vel_y;
self.anim_rect = state.constants.npc.n275_puppy_plantation[self.anim_num as usize];
Ok(())
}
pub(crate) fn tick_n308_stumpy(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
match self.action_num {
0 | 1 => {
self.action_num = 1;
let player = self.get_closest_player_ref(&players);
if player.x < self.x + 0x1e000
&& player.x > self.x - 0x1e000
&& player.y < self.y + 0x18000
&& player.y > self.y - 0x18000
{
self.action_num = 10;
}
}
10 | 11 => {
let player = self.get_closest_player_ref(&players);
if self.action_num == 10 {
self.action_num = 11;
self.action_counter = 0;
self.vel_x2 = 0;
self.vel_y2 = 0;
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
}
self.action_counter += 1;
if self.action_counter > 50 {
self.action_num = 20;
}
self.anim_counter += 1;
// this is a typo in original exe
if self.action_counter > 1 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num > 1 {
self.anim_num = 0;
}
}
if player.x > self.x + 0x28000
&& player.x < self.x - 0x28000
&& player.y > self.y + 0x1e000
&& player.y < self.y - 0x1e000
{
self.action_num = 0;
}
}
20 | 21 => {
if self.action_num == 20 {
self.action_num = 21;
self.action_counter = 0;
let player = self.get_closest_player_ref(&players);
let deg = f64::atan2((self.y - player.y) as f64, (self.x - player.x) as f64);
let deg = deg + self.rng.range(-3..3) as f64 * CDEG_RAD;
self.vel_x2 = (deg.cos() * -1024.0) as i32;
self.vel_y2 = (deg.sin() * -1024.0) as i32;
self.direction = if self.vel_x2 < 0 { Direction::Left } else { Direction::Right };
}
if self.vel_x2 < 0 && self.flags.hit_left_wall() {
self.direction = Direction::Right;
self.vel_x2 = -self.vel_x2;
}
if self.vel_x2 > 0 && self.flags.hit_right_wall() {
self.direction = Direction::Left;
self.vel_x2 = -self.vel_x2;
}
if self.vel_y2 < 0 && self.flags.hit_top_wall() {
self.vel_y2 = -self.vel_y2;
}
if self.vel_y2 > 0 && self.flags.hit_bottom_wall() {
self.vel_y2 = -self.vel_y2;
}
if self.flags.in_water() {
self.vel_y2 = -0x200;
}
self.x += self.vel_x2;
self.y += self.vel_y2;
self.action_counter += 1;
if self.action_counter > 50 {
self.action_num = 10;
}
self.anim_num += 1;
if self.anim_num > 1 {
self.anim_num = 0;
}
}
_ => (),
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
self.anim_rect = state.constants.npc.n308_stumpy[self.anim_num as usize + dir_offset];
Ok(())
}
}

View file

@ -1,7 +1,7 @@
use crate::framework::error::GameResult;
use num_traits::abs;
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::npc::NPC;
use crate::player::Player;
use crate::rng::RNG;
@ -24,13 +24,8 @@ impl NPC {
}
let player = self.get_closest_player_mut(players);
if abs(self.x - player.x) < 32 * 0x200
&& self.y - 32 * 0x200 < player.y && self.y + 0x2000 > player.y {
self.direction = if self.x > player.x {
Direction::Left
} else {
Direction::Right
};
if abs(self.x - player.x) < 32 * 0x200 && self.y - 32 * 0x200 < player.y && self.y + 0x2000 > player.y {
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
}
}
2 => {
@ -71,4 +66,38 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n307_santa_caged(&mut self, state: &mut SharedGameState) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter = 0;
self.anim_num = 0;
self.x += 0x200;
self.y -= 0x400;
}
if self.rng.range(0..160) == 10 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 12 {
self.action_num = 1;
self.anim_num = 0;
}
}
_ => (),
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
self.anim_rect = state.constants.npc.n307_santa_caged[self.anim_num as usize + dir_offset];
Ok(())
}
}

View file

@ -1,14 +1,20 @@
use crate::framework::error::GameResult;
use crate::common::Direction;
use crate::framework::error::GameResult;
use crate::npc::boss::BossNPC;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::player::Player;
use crate::rng::RNG;
use crate::shared_game_state::SharedGameState;
use crate::stage::Stage;
impl NPC {
pub fn tick_n042_sue(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
pub fn tick_n042_sue(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
@ -111,7 +117,8 @@ impl NPC {
self.vel_y = 0;
self.action_num = 14;
self.parent_id = npc_list.iter_alive()
self.parent_id = npc_list
.iter_alive()
.find_map(|npc| if npc.event_num == 501 { Some(npc.id) } else { None })
.unwrap_or(0);
}
@ -143,19 +150,13 @@ impl NPC {
let _ = npc_list.spawn(0x80, npc);
}
state.npc_super_pos = (
self.x - 24 * 0x200,
self.y - 0x1000
);
state.npc_super_pos = (self.x - 24 * 0x200, self.y - 0x1000);
}
17 => {
self.vel_x = 0;
self.anim_num = 12;
state.npc_super_pos = (
self.x,
self.y - 0x1000
);
state.npc_super_pos = (self.x, self.y - 0x1000);
}
20 | 21 => {
if self.action_num == 20 {
@ -282,4 +283,312 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n280_sue_teleported(&mut self, state: &mut SharedGameState) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter = 0;
self.anim_counter = 0;
self.x += 0xc00;
self.target_x = self.x;
}
self.action_counter += 1;
if self.action_counter > 64 {
self.action_num = 2;
self.action_counter = 0;
}
}
2 => {
self.anim_num = 0;
if self.flags.hit_bottom_wall() {
self.action_num = 4;
self.action_counter = 0;
self.anim_num = 1;
state.sound_manager.play_sfx(23);
}
}
_ => (),
}
if self.action_num > 1 {
self.vel_y += 0x20;
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
self.y += self.vel_y;
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 2 };
self.anim_rect = state.constants.npc.n280_sue_teleported[self.anim_num as usize + dir_offset];
if self.action_num == 1 {
self.anim_rect.bottom = self.anim_rect.top + self.action_counter / 4;
self.x = if self.action_counter & 2 != 0 { self.target_x } else { self.target_x + 0x200 };
}
Ok(())
}
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,
) -> GameResult {
if self.action_num < 100 && (!boss.parts[0].cond.alive() || self.life < 400) {
self.action_num = 100;
}
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.y -= 0x800;
self.action_counter3 = self.life;
state.sound_manager.play_sfx(29);
}
self.action_counter += 1;
if self.action_counter & 2 != 0 {
self.display_bounds.top = 0x2000;
self.display_bounds.right = 0x2000;
self.display_bounds.left = 0x2000;
self.anim_num = 11;
} else {
self.display_bounds.top = 0x600;
self.display_bounds.right = 0x1000;
self.display_bounds.left = 0x1000;
self.anim_num = 12;
}
if self.action_counter > 50 {
self.action_num = 10;
}
}
10 => {
self.action_num = 11;
self.anim_num = 11;
self.display_bounds.top = 0x2000;
self.display_bounds.right = 0x2000;
self.display_bounds.left = 0x2000;
npc_list.kill_npcs_by_type(257, true, state);
}
20 | 21 => {
if self.action_num == 20 {
self.action_num = 21;
self.action_counter = 0;
self.anim_num = 0;
self.anim_counter = 0;
self.damage = 0;
self.npc_flags.set_shootable(true);
self.npc_flags.set_ignore_solidity(false);
}
self.vel_x = 7 * self.vel_x / 8;
self.vel_y = 7 * self.vel_y / 8;
self.animate(20, 0, 1);
self.action_counter += 1;
if self.action_counter > 80 {
self.action_num = 30;
}
let player = self.get_closest_player_ref(&players);
self.direction = if player.x > self.x { Direction::Left } else { Direction::Right };
if self.life + 50 < self.action_counter3 {
self.action_counter3 = self.life;
state.npc_super_pos.0 = 10;
}
}
30 | 31 => {
if self.action_num == 30 {
self.action_num = 31;
self.action_counter = 0;
self.anim_num = 2;
self.vel_x = 0;
self.vel_y = 0;
}
self.action_counter += 1;
if self.action_counter > 16 {
self.action_counter2 += 1;
self.action_counter2 &= 3;
self.action_num = match self.action_counter2 {
0 | 2 => 32,
1 | 3 => 34,
_ => self.action_num,
};
}
}
32 | 33 => {
let player = self.get_closest_player_ref(&players);
if self.action_num == 32 {
self.action_num = 33;
self.action_counter = 0;
self.npc_flags.set_shootable(false);
self.npc_flags.set_ignore_solidity(false);
self.target_x = if player.x >= self.x { player.x + 0x14000 } else { player.x - 0x14000 };
self.target_y = player.y;
let angle = f64::atan2((self.y - self.target_y) as f64, (self.x - self.target_x) as f64);
self.vel_x = (-1536.0 * angle.cos()) as i32;
self.vel_y = (-1536.0 * angle.sin()) as i32;
let half_w = stage.map.width as i32 * state.tile_size.as_int() * 0x200 / 2;
let half_h = stage.map.height as i32 * state.tile_size.as_int() * 0x200 / 2;
if ((self.x < half_w && self.vel_x > 0) || (self.x > half_w && self.vel_x < 0))
|| ((self.y < half_h && self.vel_y > 0) || (self.y > half_h && self.vel_y < 0))
{
self.npc_flags.set_ignore_solidity(true);
}
self.direction = if self.vel_x <= 0 { Direction::Left } else { Direction::Right };
}
self.action_counter += 1;
self.anim_num = if self.action_counter & 2 != 0 { 3 } else { 8 };
if self.action_counter > 50 || (self.flags.hit_right_wall() || self.flags.hit_left_wall()) {
self.action_num = 20;
}
}
34 | 35 => {
let player = self.get_closest_player_ref(&players);
if self.action_num == 34 {
self.action_num = 35;
self.action_counter = 0;
self.damage = 4;
self.npc_flags.set_ignore_solidity(false);
self.target_x = player.x;
self.target_y = player.y;
let angle = f64::atan2((self.y - self.target_y) as f64, (self.x - self.target_x) as f64);
self.vel_x = (-1536.0 * angle.cos()) as i32;
self.vel_y = (-1536.0 * angle.sin()) as i32;
let half_w = stage.map.width as i32 * state.tile_size.as_int() * 0x200 / 2;
let half_h = stage.map.height as i32 * state.tile_size.as_int() * 0x200 / 2;
if ((self.x < half_w && self.vel_x > 0) || (self.x > half_w && self.vel_x < 0))
|| ((self.y < half_h && self.vel_y > 0) || (self.y > half_h && self.vel_y < 0))
{
self.npc_flags.set_ignore_solidity(true);
}
self.direction = if self.vel_x <= 0 { Direction::Left } else { Direction::Right };
}
self.action_counter += 1;
if self.action_counter > 20 && self.shock != 0 {
self.action_num = 40;
} else if self.action_counter > 50 || (self.flags.hit_right_wall() || self.flags.hit_left_wall()) {
self.action_num = 20;
}
self.animate(1, 4, 7);
if self.action_counter % 5 == 1 {
state.sound_manager.play_sfx(109);
}
}
40 | 41 => {
if self.action_num == 40 {
self.action_num = 41;
self.action_counter = 0;
self.anim_num = 2;
self.damage = 0;
self.npc_flags.set_ignore_solidity(false);
}
self.vel_x = 7 * self.vel_x / 8;
self.vel_y = 7 * self.vel_y / 8;
self.action_counter += 1;
if self.action_counter > 6 {
self.action_num = 42;
self.action_counter = 0;
self.vel_x = self.direction.vector_x() * 0x200;
self.vel_y = -0x200;
}
}
42 => {
self.anim_num = 9;
if self.flags.hit_bottom_wall() {
self.action_num = 43;
self.action_counter = 0;
self.anim_num = 2;
let player = self.get_closest_player_ref(&players);
self.direction = if self.x < player.x { Direction::Right } else { Direction::Left };
}
self.vel_y += 0x20;
if self.vel_y > 0x5FF {
self.vel_y = 0x5FF;
}
}
43 => {
self.action_counter += 1;
if self.action_counter > 16 {
self.action_num = 20;
}
}
99 => {
self.vel_x = 0;
self.vel_y = 0;
self.anim_num = 9;
self.npc_flags.set_shootable(false);
}
100 | 101 => {
if self.action_num == 100 {
self.action_num = 101;
self.anim_num = 9;
self.damage = 0;
self.npc_flags.set_shootable(false);
self.npc_flags.set_ignore_solidity(true);
self.shock += 50;
boss.parts[0].anim_num += 1;
}
self.vel_y += 0x20;
if self.y > 0x1B000 - self.hit_bounds.bottom as i32 {
self.y = 0x1B000 - self.hit_bounds.bottom as i32;
self.action_num = 102;
self.anim_num = 10;
self.vel_x = 0;
self.vel_y = 0;
}
}
_ => (),
}
self.x += if self.shock > 0 { self.vel_x / 2 } else { self.vel_x };
self.y += self.vel_y;
let dir_offset = if self.direction == Direction::Left { 0 } else { 13 };
self.anim_rect = state.constants.npc.n284_sue_possessed[self.anim_num as usize + dir_offset];
Ok(())
}
}

View file

@ -20,14 +20,7 @@ impl NPC {
self.y += self.vel_y;
self.action_counter += 1;
self.anim_counter += 1;
if self.anim_counter > 1 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num > 2 {
self.anim_num = 0;
}
}
self.animate(1, 0, 2);
self.anim_rect = state.constants.npc.n108_balfrog_projectile[self.anim_num as usize];

View file

@ -1,9 +1,11 @@
use crate::common::Direction;
use crate::common::{Direction, Rect};
use crate::framework::error::GameResult;
use crate::npc::boss::BossNPC;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::shared_game_state::SharedGameState;
use crate::player::Player;
use crate::rng::RNG;
use crate::shared_game_state::SharedGameState;
impl NPC {
pub(crate) fn tick_n196_ironhead_wall(&mut self, state: &mut SharedGameState) -> GameResult {
@ -96,5 +98,174 @@ impl NPC {
}
impl BossNPC {
pub(crate) fn tick_b05_ironhead(&mut self) {}
pub(crate) fn tick_b05_ironhead(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
) {
match self.parts[0].action_num {
0 => {
self.parts[0].cond.set_alive(true);
self.parts[0].exp = 1;
self.parts[0].direction = Direction::Right;
self.parts[0].action_num = 100;
self.parts[0].x = 0x14000;
self.parts[0].y = 0x10000;
self.hurt_sound[0] = 54;
self.parts[0].npc_flags.set_event_when_killed(true);
self.parts[0].npc_flags.set_shootable(true);
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_show_damage(true);
self.parts[0].size = 3;
self.parts[0].damage = 10;
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);
}
100 | 101 => {
if self.parts[0].action_num == 100 {
self.parts[0].action_num = 101;
self.parts[0].action_counter = 0;
self.parts[0].npc_flags.set_shootable(false);
}
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 50 {
self.parts[0].action_num = 250;
self.parts[0].action_counter = 0;
}
if self.parts[0].action_counter % 4 == 0 {
let mut npc = NPC::create(197, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.parts[0].rng.range(15..18) * state.tile_size.as_int() * 0x200;
npc.y = self.parts[0].rng.range(2..13) * state.tile_size.as_int() * 0x200;
let _ = npc_list.spawn(0x100, npc);
}
}
250 | 251 => {
if self.parts[0].action_num == 250 {
self.parts[0].action_num = 251;
if self.parts[0].direction == Direction::Right {
let player = self.parts[0].get_closest_player_ref(&players);
self.parts[0].x = 0x1E000;
self.parts[0].y = player.y;
} else {
self.parts[0].x = 0x5A000;
self.parts[0].y = self.parts[0].rng.range(2..13) * state.tile_size.as_int() * 0x200;
}
self.parts[0].target_x = self.parts[0].x;
self.parts[0].target_y = self.parts[0].y;
self.parts[0].vel_y = self.parts[0].rng.range(-0x200..0x200);
self.parts[0].vel_x = self.parts[0].rng.range(-0x200..0x200);
self.parts[0].npc_flags.set_shootable(true);
}
if self.parts[0].direction == Direction::Right {
self.parts[0].target_x += 0x400;
} else {
self.parts[0].target_x -= 0x200;
if self.parts[0].target_y >= self.parts[0].y {
self.parts[0].target_y -= 0x200;
} else {
self.parts[0].target_y += 0x200;
}
}
self.parts[0].vel_x += if self.parts[0].x >= self.parts[0].target_x { -8 } else { 8 };
self.parts[0].vel_y += if self.parts[0].y >= self.parts[0].target_y { -8 } else { 8 };
self.parts[0].vel_y = self.parts[0].vel_y.clamp(-0x200, 0x200);
self.parts[0].x += self.parts[0].vel_x;
self.parts[0].y += self.parts[0].vel_y;
if self.parts[0].direction == Direction::Right {
if self.parts[0].x > 0x5A000 {
self.parts[0].direction = Direction::Left;
self.parts[0].action_num = 100;
}
} else if self.parts[0].x < 0x22000 {
self.parts[0].direction = Direction::Right;
self.parts[0].action_num = 100;
}
self.parts[0].action_counter += 1;
if [300, 310, 320].contains(&self.parts[0].action_counter) {
state.sound_manager.play_sfx(39);
let mut npc = NPC::create(198, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.parts[0].x + 0x1400;
npc.y = self.parts[0].y + 0x200;
npc.vel_x = self.parts[0].rng.range(-3..0) * 0x200;
npc.vel_y = self.parts[0].rng.range(-3..3) * 0x200;
npc.direction = Direction::Right;
let _ = npc_list.spawn(0x100, npc);
}
self.parts[0].animate(2, 0, 7);
}
1000 | 1001 => {
if self.parts[0].action_num == 1000 {
self.parts[0].action_num = 1001;
self.parts[0].anim_num = 8;
self.parts[0].npc_flags.set_shootable(false);
self.parts[0].damage = 0;
self.parts[0].target_x = self.parts[0].x;
self.parts[0].target_y = self.parts[0].y;
state.quake_counter = 20;
for _ in 0..32 {
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.parts[0].x + self.parts[0].rng.range(-0x80..0x80) * 0x200;
npc.y = self.parts[0].y + self.parts[0].rng.range(-0x40..0x40) * 0x200;
npc.vel_x = self.parts[0].rng.range(-0x80..0x80) * 0x200;
npc.vel_y = self.parts[0].rng.range(-0x80..0x80) * 0x200;
npc.direction = Direction::Left;
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);
}
self.parts[0].target_x -= 0x200;
self.parts[0].x = self.parts[0].target_x + self.parts[0].rng.range(-1..1) * 0x200;
self.parts[0].y = self.parts[0].target_y + self.parts[0].rng.range(-1..1) * 0x200;
if self.parts[0].action_counter % 4 == 0 {
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.parts[0].x + self.parts[0].rng.range(-0x80..0x80) * 0x200;
npc.y = self.parts[0].y + self.parts[0].rng.range(-0x40..0x40) * 0x200;
npc.vel_x = self.parts[0].rng.range(-0x80..0x80) * 0x200;
npc.vel_y = self.parts[0].rng.range(-0x80..0x80) * 0x200;
npc.direction = Direction::Left;
let _ = npc_list.spawn(0x100, npc);
}
}
_ => (),
}
let offset = if self.parts[0].shock != 0 {
self.parts[19].action_counter += 1;
if self.parts[19].action_counter & 2 != 0 {
0
} else {
9
}
} else {
0
};
self.parts[0].anim_rect = state.constants.npc.b05_ironhead[self.parts[0].anim_num as usize + offset];
}
}

View file

@ -20,7 +20,7 @@ pub mod ironhead;
pub mod monster_x;
pub mod omega;
pub mod press;
pub mod twins;
pub mod sisters;
pub mod undead_core;
pub struct BossNPC {
@ -82,9 +82,9 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl
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(),
6 => self.tick_b06_twins(),
7 => self.tick_b07_undead_core(),
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_press(),
9 => self.tick_b09_ballos(),
_ => {}

464
src/npc/boss/sisters.rs Normal file
View file

@ -0,0 +1,464 @@
use crate::common::{Direction, Rect, SliceExt, CDEG_RAD};
use crate::components::flash::Flash;
use crate::npc::boss::BossNPC;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::player::Player;
use crate::rng::RNG;
use crate::SharedGameState;
impl BossNPC {
pub(crate) fn tick_b06_sisters(
&mut self,
state: &mut SharedGameState,
players: [&mut Player; 2],
npc_list: &NPCList,
flash: &mut Flash,
) {
match self.parts[0].action_num {
0 => {
self.parts[0].cond.set_alive(true);
self.parts[0].direction = Direction::Left;
self.parts[0].action_num = 10;
self.parts[0].exp = 0;
self.parts[0].x = 0x14000;
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.hurt_sound[0] = 54;
self.parts[0].npc_flags.set_ignore_solidity(true);
self.parts[0].npc_flags.set_event_when_killed(true);
self.parts[0].size = 3;
self.parts[0].damage = 0;
self.parts[0].event_num = 1000;
self.parts[0].life = 500;
self.parts[0].action_counter3 = self.parts[0].rng.range(700..1200) as u16;
self.parts[0].target_x = 0xB4;
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].npc_flags.set_ignore_solidity(true);
self.parts[2].npc_flags.set_invulnerable(true);
self.parts[2].parent_id = 3;
self.parts[2].cond.set_alive(true);
self.parts[2].cond.set_damage_boss(true);
self.parts[2].damage = 10;
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].npc_flags.set_ignore_solidity(true);
self.parts[3].parent_id = 0;
self.parts[3].damage = 10;
self.parts[4] = self.parts[2].clone();
self.parts[4].id = 4;
self.parts[4].parent_id = 5;
self.parts[5] = self.parts[3].clone();
self.parts[5].id = 5;
self.parts[5].action_counter2 = 128;
}
20 => {
self.parts[0].target_x -= 1;
if self.parts[0].target_x <= 112 {
self.parts[0].action_num = 100;
self.parts[0].action_counter = 0;
self.parts[2].action_num = 100;
self.parts[4].action_num = 100;
self.parts[3].action_num = 100;
self.parts[5].action_num = 100;
}
}
100 => {
let actr2: &mut i16 = unsafe { std::mem::transmute(&mut self.parts[0].action_counter2) };
let mut b = true;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter < 100 {
*actr2 += 1;
} else if self.parts[0].action_counter < 120 {
*actr2 += 2;
} else if self.parts[0].action_counter < self.parts[0].action_counter3 {
*actr2 += 4;
} else if self.parts[0].action_counter < self.parts[0].action_counter3 + 40 {
*actr2 += 2;
} else if self.parts[0].action_counter < self.parts[0].action_counter3 + 60 {
*actr2 += 1;
} else {
self.parts[0].action_counter = 0;
self.parts[0].action_num = 110;
self.parts[0].action_counter3 = self.parts[0].rng.range(400..700) as u16;
b = false;
}
if b && *actr2 >= 0x400 {
*actr2 -= 0x400;
}
}
110 => {
let actr2: &mut i16 = unsafe { std::mem::transmute(&mut self.parts[0].action_counter2) };
let mut b = true;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter < 20 {
*actr2 -= 1;
} else if self.parts[0].action_counter < 60 {
*actr2 -= 2;
} else if self.parts[0].action_counter < self.parts[0].action_counter2 {
*actr2 -= 4;
} else if self.parts[0].action_counter < self.parts[0].action_counter2 + 40 {
*actr2 -= 2;
} else if self.parts[0].action_counter < self.parts[0].action_counter2 + 60 {
*actr2 -= 1;
} else {
if self.parts[0].life >= 300 {
self.parts[0].action_counter = 0;
self.parts[0].action_num = 100;
*actr2 = self.parts[0].rng.range(400..700) as i16;
} else {
self.parts[0].action_counter = 0;
self.parts[0].action_num = 400;
self.parts[2].action_num = 400;
self.parts[4].action_num = 400;
b = false;
}
}
if b && *actr2 <= 0 {
*actr2 += 0x400;
}
}
400 => {
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 100 {
self.parts[0].action_counter = 0;
self.parts[0].action_num = 401;
}
}
401 => {
let actr2: &mut i16 = unsafe { std::mem::transmute(&mut self.parts[0].action_counter2) };
let mut b = true;
self.parts[0].action_counter += 1;
if self.parts[0].action_counter < 100 {
*actr2 += 1;
} else if self.parts[0].action_counter < 120 {
*actr2 += 2;
} else if self.parts[0].action_counter < 500 {
*actr2 += 2;
} else if self.parts[0].action_counter < 540 {
*actr2 += 4;
} else if self.parts[0].action_counter < 560 {
*actr2 += 2;
} else {
self.parts[0].action_num = 100;
self.parts[0].action_counter = 0;
self.parts[2].action_num = 100;
self.parts[4].action_num = 100;
b = false;
}
if b && *actr2 >= 0x400 {
*actr2 -= 0x400;
}
}
1000 | 1001 => {
if self.parts[0].action_num == 1000 {
self.parts[0].action_num = 1001;
self.parts[0].action_counter = 0;
self.parts[2].action_num = 1000;
self.parts[3].action_num = 1000;
self.parts[4].action_num = 1000;
self.parts[5].action_num = 1000;
npc_list.create_death_smoke(
self.parts[0].x,
self.parts[0].y,
self.parts[0].display_bounds.right as usize,
40,
state,
&self.parts[0].rng,
);
}
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 100 {
self.parts[0].action_num = 1010;
}
let mut npc = NPC::create(4, &state.npc_table);
npc.cond.set_alive(true);
npc.x = self.parts[0].x + self.parts[0].rng.range(-128..128) * 0x200;
npc.y = self.parts[0].y + self.parts[0].rng.range(-70..70) * 0x200;
let _ = npc_list.spawn(0x100, npc);
}
1010 => {
let actr2: &mut i16 = unsafe { std::mem::transmute(&mut self.parts[0].action_counter2) };
*actr2 += 4;
if *actr2 >= 0x400 {
*actr2 -= 0x400;
}
if self.parts[0].target_x > 8 {
self.parts[0].target_x -= 1;
}
if self.parts[0].target_y > 0 {
self.parts[0].target_y -= 1;
}
if self.parts[0].target_x < -8 {
self.parts[0].target_x += 1;
}
if self.parts[0].target_y < 0 {
self.parts[0].target_y += 1;
}
if self.parts[0].target_y == 0 {
self.parts[0].action_num = 1020;
self.parts[0].action_counter = 0;
flash.set_cross(self.parts[0].x, self.parts[0].y);
state.sound_manager.play_sfx(35);
}
}
1020 => {
self.parts[0].action_counter += 1;
if self.parts[0].action_counter > 50 {
npc_list.kill_npcs_by_type(211, true, state);
self.parts[0].cond.set_alive(false);
self.parts[1].cond.set_alive(false);
self.parts[2].cond.set_alive(false);
self.parts[3].cond.set_alive(false);
self.parts[4].cond.set_alive(false);
self.parts[5].cond.set_alive(false);
self.parts[0].action_num = 0;
}
}
_ => (),
}
self.tick_b06_sisters_dragon_head(2, state, &players, npc_list);
self.tick_b06_sisters_dragon_body(3, state, &players);
self.tick_b06_sisters_dragon_head(4, state, &players, npc_list);
self.tick_b06_sisters_dragon_body(5, state, &players);
self.parts[0].anim_rect = Rect::new(0, 0, 0, 0);
}
fn tick_b06_sisters_dragon_head(
&mut self,
i: usize,
state: &mut SharedGameState,
players: &[&mut Player; 2],
npc_list: &NPCList,
) {
let parent = self.parts[i].parent_id as usize;
let (base, part) = if let Some(x) = self.parts.get_two_mut(parent, i) {
x
} else {
return;
};
match part.action_num {
0 => {
part.action_num = 1;
}
100 | 200 | 201 => {
if part.action_num == 100 {
part.action_num = 200;
}
if part.action_num == 200 {
part.action_num = 201;
part.anim_num = 0;
part.hit_bounds.left = 0x2000;
part.npc_flags.set_shootable(false);
part.action_counter2 = part.rng.range(100..200) as u16;
}
if part.action_counter2 > 0 {
part.action_counter2 -= 1;
} else {
part.action_num = 210;
part.action_counter = 0;
part.action_counter3 = 0;
}
}
210 => {
part.action_counter += 1;
if part.action_counter == 3 {
part.anim_num = 1;
}
if part.action_counter == 6 {
part.anim_num = 2;
part.hit_bounds.left = 0x1000;
part.npc_flags.set_shootable(true);
part.action_counter3 = 0;
}
if part.action_counter > 150 {
part.action_num = 220;
part.action_counter = 0;
}
if part.shock != 0 {
part.action_counter3 += 1;
}
if part.action_counter3 > 10 {
part.action_num = 300;
part.action_counter = 0;
part.anim_num = 3;
part.hit_bounds.left = 0x2000;
state.sound_manager.play_sfx(51);
npc_list.remove_by_type(211, state);
}
}
220 => {
part.action_counter += 1;
if part.action_counter % 8 == 1 {
let mut npc = NPC::create(202, &state.npc_table);
npc.cond.set_alive(true);
npc.x = part.x + 0x1000 * part.direction.vector_x();
npc.y = part.y;
let player = part.get_closest_player_ref(players);
let angle = f64::atan2((player.y - npc.y) as f64, (player.x - npc.x) as f64)
+ (part.rng.range(-6..6) as f64 * CDEG_RAD);
npc.vel_x = (angle.cos() * 512.0) as i32;
npc.vel_y = (angle.sin() * 512.0) as i32;
let _ = npc_list.spawn(0x100, npc);
state.sound_manager.play_sfx(33);
}
if part.action_counter > 50 {
part.action_num = 200;
}
}
300 => {
part.action_counter += 1;
if part.action_counter > 100 {
part.action_num = 200;
}
}
400 | 401 => {
if part.action_num == 400 {
part.action_num = 401;
part.action_counter = 0;
part.anim_num = 0;
part.hit_bounds.left = 0x2000;
part.npc_flags.set_shootable(false);
}
part.action_counter += 1;
if part.action_counter == 3 {
part.anim_num = 1;
}
if part.action_counter == 6 {
part.anim_num = 2;
part.hit_bounds.left = 0x1000;
part.npc_flags.set_shootable(true);
part.action_counter3 = 0;
}
if part.action_counter > 20 && part.action_counter % 32 == 1 {
let mut npc = NPC::create(202, &state.npc_table);
npc.cond.set_alive(true);
let player = part.get_closest_player_ref(players);
let angle = f64::atan2((player.y - npc.y) as f64, (player.x - npc.x) as f64)
+ (part.rng.range(-6..6) as f64 * CDEG_RAD);
npc.x = part.x + 0x1000 * part.direction.vector_x();
npc.y = part.y;
npc.vel_x = (angle.cos() * 512.0) as i32;
npc.vel_y = (angle.sin() * 512.0) as i32;
let _ = npc_list.spawn(0x100, npc);
state.sound_manager.play_sfx(33);
}
}
_ => (),
}
part.direction = base.direction;
part.x = base.x + 0x800 * part.direction.vector_x();
part.y = base.y - 0x1000;
let dir_offset = if part.direction == Direction::Left { 0 } else { 4 };
part.anim_rect = state.constants.npc.b06_sisters[part.anim_num as usize + dir_offset];
}
fn tick_b06_sisters_dragon_body(&mut self, i: usize, state: &mut SharedGameState, players: &[&mut Player; 2]) {
let parent = self.parts[i].parent_id as usize;
let (base, part) = if let Some(x) = self.parts.get_two_mut(parent, i) {
x
} else {
return;
};
match part.action_num {
0 | 10 => {
if part.action_num == 0 {
part.action_num = 10;
let angle =
((part.action_counter2) as u8).wrapping_add((base.action_counter2 / 4) as u8) as f64 * CDEG_RAD;
part.x += base.x + base.target_x as i32 * (angle.cos() * -512.0) as i32;
part.y += base.y + base.target_y as i32 * (angle.sin() * -512.0) as i32;
}
let player = part.get_closest_player_ref(players);
part.direction = if part.x > player.x { Direction::Left } else { Direction::Right };
}
100 => {
let angle =
((part.action_counter2) as u8).wrapping_add((base.action_counter2 / 4) as u8) as f64 * CDEG_RAD;
part.target_x = base.x + base.target_x as i32 * (angle.cos() * -512.0) as i32;
part.target_y = base.y + base.target_y as i32 * (angle.sin() * -512.0) as i32;
part.x += (part.target_x - part.x) / 8;
part.y += (part.target_y - part.y) / 8;
let player = part.get_closest_player_ref(players);
part.direction = if part.x > player.x { Direction::Left } else { Direction::Right };
}
1000 | 1001 => {
if part.action_num == 1000 {
part.action_num = 1001;
part.npc_flags.set_shootable(false);
}
let angle =
((part.action_counter2) as u8).wrapping_add((base.action_counter2 / 4) as u8) as f64 * CDEG_RAD;
part.target_x = base.x + base.target_x as i32 * (angle.cos() * -512.0) as i32;
part.target_y = base.y + base.target_y as i32 * (angle.sin() * -512.0) as i32;
part.x += (part.target_x - part.x) / 8;
part.y += (part.target_y - part.y) / 8;
part.direction = if part.x > base.x { Direction::Left } else { Direction::Right };
}
_ => (),
}
part.animate(2, 0, 2);
let dir_offset = if part.direction == Direction::Left { 0 } else { 3 };
part.anim_rect = state.constants.npc.b06_sisters[part.anim_num as usize + dir_offset + 8];
}
}

View file

@ -1,7 +0,0 @@
use crate::npc::boss::BossNPC;
impl BossNPC {
pub(crate) fn tick_b06_twins(&mut self) {
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,6 @@ use std::io;
use std::io::Cursor;
use byteorder::{ReadBytesExt, LE};
use num_traits::abs;
use crate::bitfield;
use crate::common::Direction;
@ -14,6 +13,7 @@ use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::boss::BossNPC;
use crate::npc::list::NPCList;
use crate::physics::PhysicalEntity;
use crate::player::Player;
@ -192,7 +192,8 @@ impl NPC {
if let Some(batch) = state.texture_set.get_or_load_batch(ctx, &state.constants, texture)?.glow() {
let off_x =
if self.direction == Direction::Left { self.display_bounds.left } else { self.display_bounds.right } as i32;
if self.direction == Direction::Left { self.display_bounds.left } else { self.display_bounds.right }
as i32;
let shock = if self.shock > 0 { (2 * ((self.shock as i32 / 2) % 2) - 1) as f32 } else { 0.0 };
let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);
@ -214,16 +215,17 @@ impl NPC {
}
}
impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mut Flash)> for 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): (
(players, npc_list, stage, bullet_manager, flash, boss): (
[&mut Player; 2],
&NPCList,
&mut Stage,
&mut BulletManager,
&mut Flash,
&mut BossNPC,
),
) -> GameResult {
#[allow(unused_assignments)]
@ -467,31 +469,96 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu
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),
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_energy_shot(state),
249 => self.tick_n249_misery_boss_appearing(state),
250 => self.tick_n250_misery_boss_lighting_ball(state, players, npc_list),
251 => self.tick_n251_misery_boss_lighting(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_unconcious(state, players, npc_list),
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),
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),
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),
337 => self.tick_n337_numahachi(state),
347 => self.tick_n347_hoppy(state, players),
349 => self.tick_n349_statue(state),
351 => self.tick_n351_statue_shootable(state, npc_list),
352 => self.tick_n352_ending_characters(state, npc_list),
355 => self.tick_n355_quote_and_curly_on_balrog(state, 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),
_ => {
#[cfg(feature = "hooks")]
{
@ -509,11 +576,11 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu
self.shock -= 1;
}
if abs(self.prev_x - self.x) > 0x1000 {
if (self.prev_x - self.x).abs() > 0x1000 {
self.prev_x = self.x;
}
if abs(self.prev_y - self.y) > 0x1000 {
if (self.prev_y - self.y).abs() > 0x1000 {
self.prev_y = self.y;
}

View file

@ -1,46 +1,48 @@
///! Various utility functions for NPC-related objects
use num_traits::abs;
use crate::weapon::bullet::Bullet;
use crate::caret::CaretType;
use crate::common::{Condition, Direction, Flag, Rect};
use crate::map::NPCData;
use crate::npc::{NPC, NPCFlag, NPCTable, NPCLayer};
use crate::npc::list::NPCList;
use crate::player::Player;
use crate::rng::{RNG, Xoroshiro32PlusPlus};
use crate::shared_game_state::{SharedGameState, TileSize};
use crate::components::number_popup::NumberPopup;
use crate::map::NPCData;
use crate::npc::list::NPCList;
use crate::npc::{NPCFlag, NPCLayer, NPCTable, NPC};
use crate::player::Player;
use crate::rng::{Xoroshiro32PlusPlus, RNG};
use crate::shared_game_state::{SharedGameState, TileSize};
use crate::weapon::bullet::Bullet;
impl NPC {
/// Initializes the RNG. Called when the [NPC] is being added to an [NPCList].
pub(crate) fn init_rng(&mut self) {
self.rng = Xoroshiro32PlusPlus::new((self.id as u32)
.wrapping_sub(self.npc_type as u32)
.wrapping_add(self.flag_num as u32)
.wrapping_mul(214013)
.wrapping_add(2531011) >> 5);
self.rng = Xoroshiro32PlusPlus::new(
(self.id as u32)
.wrapping_sub(self.npc_type as u32)
.rotate_right(5)
.wrapping_sub(self.flag_num as u32)
.rotate_right((self.event_num % 13) as u32)
.wrapping_mul(214013)
.rotate_right(13)
.wrapping_add(2531011)
.rotate_right(5),
);
}
/// Creates a new NPC object with properties that have been populated with data from given NPC data table.
pub fn create(npc_type: u16, table: &NPCTable) -> NPC {
let display_bounds = table.get_display_bounds(npc_type);
let hit_bounds = table.get_hit_bounds(npc_type);
let (size, life, damage, flags, exp, spritesheet_id) =
match table.get_entry(npc_type) {
Some(entry) => {
(
entry.size,
entry.life,
entry.damage as u16,
entry.npc_flags,
entry.experience as u16,
entry.spritesheet_id as u16
)
}
None => { (2, 0, 0, NPCFlag(0), 0, 0) }
};
let (size, life, damage, flags, exp, spritesheet_id) = match table.get_entry(npc_type) {
Some(entry) => (
entry.size,
entry.life,
entry.damage as u16,
entry.npc_flags,
entry.experience as u16,
entry.spritesheet_id as u16,
),
None => (2, 0, 0, NPCFlag(0), 0, 0),
};
let npc_flags = NPCFlag(flags.0);
NPC {
@ -161,19 +163,16 @@ impl NPC {
/// Returns true if the [NPC] collides with a [Bullet].
pub fn collides_with_bullet(&self, bullet: &Bullet) -> bool {
(
self.npc_flags.shootable()
&& (self.x - self.hit_bounds.right as i32) < (bullet.x + bullet.enemy_hit_width as i32)
&& (self.x + self.hit_bounds.right as i32) > (bullet.x - bullet.enemy_hit_width as i32)
&& (self.y - self.hit_bounds.top as i32) < (bullet.y + bullet.enemy_hit_height as i32)
&& (self.y + self.hit_bounds.bottom as i32) > (bullet.y - bullet.enemy_hit_height as i32)
) || (
self.npc_flags.invulnerable()
(self.npc_flags.shootable()
&& (self.x - self.hit_bounds.right as i32) < (bullet.x + bullet.enemy_hit_width as i32)
&& (self.x + self.hit_bounds.right as i32) > (bullet.x - bullet.enemy_hit_width as i32)
&& (self.y - self.hit_bounds.top as i32) < (bullet.y + bullet.enemy_hit_height as i32)
&& (self.y + self.hit_bounds.bottom as i32) > (bullet.y - bullet.enemy_hit_height as i32))
|| (self.npc_flags.invulnerable()
&& (self.x - self.hit_bounds.right as i32) < (bullet.x + bullet.hit_bounds.right as i32)
&& (self.x + self.hit_bounds.right as i32) > (bullet.x - bullet.hit_bounds.left as i32)
&& (self.y - self.hit_bounds.top as i32) < (bullet.y + bullet.hit_bounds.bottom as i32)
&& (self.y + self.hit_bounds.bottom as i32) > (bullet.y - bullet.hit_bounds.top as i32)
)
&& (self.y + self.hit_bounds.bottom as i32) > (bullet.y - bullet.hit_bounds.top as i32))
}
/// Creates experience drop for this NPC.
@ -247,9 +246,15 @@ impl NPCList {
}
match npc.size {
1 => { self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 3, state, &npc.rng); }
2 => { self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 7, state, &npc.rng); }
3 => { self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 12, state, &npc.rng); }
1 => {
self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 3, state, &npc.rng);
}
2 => {
self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 7, state, &npc.rng);
}
3 => {
self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 12, state, &npc.rng);
}
_ => {}
};
}
@ -264,9 +269,15 @@ impl NPCList {
}
match npc.size {
1 => { self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 3, state, &npc.rng); }
2 => { self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 7, state, &npc.rng); }
3 => { self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 12, state, &npc.rng); }
1 => {
self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 3, state, &npc.rng);
}
2 => {
self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 7, state, &npc.rng);
}
3 => {
self.create_death_smoke(npc.x, npc.y, npc.display_bounds.right as usize, 12, state, &npc.rng);
}
_ => {}
};
@ -344,18 +355,43 @@ impl NPCList {
/// Creates NPC death smoke diffusing in random directions.
#[inline]
pub fn create_death_smoke(&self, x: i32, y: i32, radius: usize, amount: usize, state: &mut SharedGameState, rng: &dyn RNG) {
pub fn create_death_smoke(
&self,
x: i32,
y: i32,
radius: usize,
amount: usize,
state: &mut SharedGameState,
rng: &dyn RNG,
) {
self.create_death_smoke_common(x, y, radius, amount, Direction::Left, state, rng)
}
/// Creates NPC death smoke diffusing upwards.
#[inline]
pub fn create_death_smoke_up(&self, x: i32, y: i32, radius: usize, amount: usize, state: &mut SharedGameState, rng: &dyn RNG) {
pub fn create_death_smoke_up(
&self,
x: i32,
y: i32,
radius: usize,
amount: usize,
state: &mut SharedGameState,
rng: &dyn RNG,
) {
self.create_death_smoke_common(x, y, radius, amount, Direction::Up, state, rng)
}
#[allow(clippy::too_many_arguments)]
fn create_death_smoke_common(&self, x: i32, y: i32, radius: usize, amount: usize, direction: Direction, state: &mut SharedGameState, rng: &dyn RNG) {
fn create_death_smoke_common(
&self,
x: i32,
y: i32,
radius: usize,
amount: usize,
direction: Direction,
state: &mut SharedGameState,
rng: &dyn RNG,
) {
let radius = (radius / 0x200) as i32;
let mut npc = NPC::create(4, &state.npc_table);

View file

@ -472,8 +472,8 @@ impl Player {
state.constants.player.air_physics.max_move
};
self.vel_x = clamp(self.vel_x, -max_move, max_move);
self.vel_y = clamp(self.vel_y, -max_move, max_move);
self.vel_x = self.vel_x.clamp(-max_move, max_move);
self.vel_y = self.vel_y.clamp(-max_move, max_move);
if !self.splash && self.flags.in_water() {
let vertical_splash = !self.flags.hit_bottom_wall() && self.vel_y > 0x200;
@ -551,8 +551,100 @@ impl Player {
Ok(())
}
fn tick_ironhead(&mut self, _state: &mut SharedGameState) -> GameResult {
// todo ironhead boss controls
fn tick_ironhead(&mut self, state: &mut SharedGameState) -> GameResult {
self.up = false;
self.down = false;
if state.control_flags.control_enabled() {
if self.controller.move_left() || self.controller.move_right() {
if self.controller.move_left() {
self.vel_x -= 0x100;
}
if self.controller.move_right() {
self.vel_x += 0x100;
}
} else if self.vel_x > 0x7f || self.vel_x < -0x7f {
self.vel_x += 0x80 * -self.vel_x.signum();
} else {
self.vel_x = 0;
}
if self.controller.move_up() || self.controller.move_down() {
if self.controller.move_up() {
self.vel_y -= 0x100;
}
if self.controller.move_down() {
self.vel_y += 0x100;
}
} else if self.vel_y > 0x7f || self.vel_y < -0x7f {
self.vel_y += 0x80 * -self.vel_y.signum();
} else {
self.vel_y = 0;
}
} else {
if self.vel_x > 0x7f || self.vel_x < -0x7f {
self.vel_x += 0x80 * -self.vel_x.signum();
} else {
self.vel_x = 0;
}
if self.vel_y > 0x7f || self.vel_y < -0x7f {
self.vel_y += 0x80 * -self.vel_y.signum();
} else {
self.vel_y = 0;
}
}
if self.vel_y < -0x200 && self.flags.hit_top_wall() {
state.create_caret(self.x, self.y - self.hit_bounds.top as i32, CaretType::LittleParticles, Direction::FacingPlayer);
}
if self.vel_y > 0x200 && self.flags.hit_bottom_wall() {
state.create_caret(self.x, self.y + self.hit_bounds.bottom as i32, CaretType::LittleParticles, Direction::FacingPlayer);
}
self.vel_x = self.vel_x.clamp(-0x400, 0x400);
self.vel_y = self.vel_y.clamp(-0x400, 0x400);
if self.controller.move_left() && self.controller.move_up() {
if self.vel_x < -0x30c {
self.vel_x = -0x30c;
}
if self.vel_y < -0x30c {
self.vel_y = -0x30c;
}
}
if self.controller.move_right() && self.controller.move_up() {
if self.vel_x > 0x30c {
self.vel_x = 0x30c;
}
if self.vel_y < -0x30c {
self.vel_y = -0x30c;
}
}
if self.controller.move_left() && self.controller.move_down() {
if self.vel_x < -0x30c {
self.vel_x = -0x30c;
}
if self.vel_y > 0x30c {
self.vel_y = 0x30c;
}
}
if self.controller.move_right() && self.controller.move_down() {
if self.vel_x > 0x30c {
self.vel_x = 0x30c;
}
if self.vel_y > 0x30c {
self.vel_y = 0x30c;
}
}
self.x += self.vel_x;
self.y += self.vel_y;
Ok(())
}

View file

@ -7,6 +7,7 @@ use crate::common::{interpolate_fix9_scale, Color, Direction, FadeDirection, Fad
use crate::components::boss_life_bar::BossLifeBar;
use crate::components::credits::Credits;
use crate::components::draw_common::Alignment;
use crate::components::falling_island::FallingIsland;
use crate::components::flash::Flash;
use crate::components::hud::HUD;
use crate::components::inventory::InventoryUI;
@ -32,9 +33,11 @@ use crate::rng::XorShift;
use crate::scene::title_scene::TitleScene;
use crate::scene::Scene;
use crate::scripting::tsc::credit_script::CreditScriptVM;
use crate::scripting::tsc::text_script::{
ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM,
};
use crate::shared_game_state::{SharedGameState, TileSize};
use crate::stage::{BackgroundType, Stage};
use crate::scripting::tsc::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM};
use crate::texture_set::SpriteBatch;
use crate::weapon::bullet::BulletManager;
use crate::weapon::{Weapon, WeaponType};
@ -48,6 +51,7 @@ pub struct GameScene {
pub stage_select: StageSelect,
pub flash: Flash,
pub credits: Credits,
pub falling_island: FallingIsland,
pub inventory_ui: InventoryUI,
pub hud_player1: HUD,
pub hud_player2: HUD,
@ -136,6 +140,7 @@ impl GameScene {
stage_select: StageSelect::new(),
flash: Flash::new(),
credits: Credits::new(),
falling_island: FallingIsland::new(),
inventory_ui: InventoryUI::new(),
hud_player1: HUD::new(Alignment::Left),
hud_player2: HUD::new(Alignment::Right),
@ -398,7 +403,11 @@ impl GameScene {
for y in 0..(state.canvas_size.1 as i32 / 16 + 1) {
if direction == FadeDirection::Left {
batch.add_rect(state.canvas_size.0 - x as f32 * 16.0 - 16.0, y as f32 * 16.0, &rect);
batch.add_rect(
state.canvas_size.0 - x as f32 * 16.0 - 16.0,
y as f32 * 16.0,
&rect,
);
} else {
batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect);
}
@ -813,14 +822,14 @@ impl GameScene {
for npc in self.npc_list.iter_alive() {
if npc.x < (self.frame.x - 128 * 0x200 - npc.display_bounds.width() as i32 * 0x200)
|| npc.x
> (self.frame.x
+ 128 * 0x200
+ (state.canvas_size.0 as i32 + npc.display_bounds.width() as i32) * 0x200)
&& npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as i32 * 0x200)
> (self.frame.x
+ 128 * 0x200
+ (state.canvas_size.0 as i32 + npc.display_bounds.width() as i32) * 0x200)
&& npc.y < (self.frame.y - 128 * 0x200 - npc.display_bounds.height() as i32 * 0x200)
|| npc.y
> (self.frame.y
+ 128 * 0x200
+ (state.canvas_size.1 as i32 + npc.display_bounds.height() as i32) * 0x200)
> (self.frame.y
+ 128 * 0x200
+ (state.canvas_size.1 as i32 + npc.display_bounds.height() as i32) * 0x200)
{
continue;
}
@ -1209,6 +1218,27 @@ impl GameScene {
(10, 50, 255),
batch,
),
270 => self.draw_light(
interpolate_fix9_scale(npc.prev_x - self.frame.prev_x, npc.x - self.frame.x, state.frame_time),
interpolate_fix9_scale(npc.prev_y - self.frame.prev_y, npc.y - self.frame.y, state.frame_time),
0.4,
(192, 0, 0),
batch,
),
285 | 287 => self.draw_light(
interpolate_fix9_scale(npc.prev_x - self.frame.prev_x, npc.x - self.frame.x, state.frame_time),
interpolate_fix9_scale(npc.prev_y - self.frame.prev_y, npc.y - self.frame.y, state.frame_time),
1.0,
(150, 90, 0),
batch,
),
293 => self.draw_light(
interpolate_fix9_scale(npc.prev_x - self.frame.prev_x, npc.x - self.frame.x, state.frame_time),
interpolate_fix9_scale(npc.prev_y - self.frame.prev_y, npc.y - self.frame.y, state.frame_time),
4.0,
(255, 255, 255),
batch,
),
_ => {}
}
}
@ -1227,7 +1257,6 @@ impl GameScene {
canvas.add(SpriteBatchCommand::DrawRect(rect, rect));
canvas.draw()?;
graphics::set_render_target(ctx, Some(canvas))?;
graphics::draw_rect(
ctx,
@ -1620,6 +1649,7 @@ impl GameScene {
&mut self.stage,
&mut self.bullet_manager,
&mut self.flash,
&mut self.boss,
),
)?;
}
@ -1950,6 +1980,7 @@ impl Scene for GameScene {
| TextScriptExecutionState::WaitTicks(_, _, _)
| TextScriptExecutionState::WaitInput(_, _, _)
| TextScriptExecutionState::Msg(_, _, _, _)
| TextScriptExecutionState::FallingIsland(_, _, _, _, _, _)
if !state.control_flags.control_enabled() && !state.textscript_vm.flags.cutscene_skip() =>
{
if self.player1.controller.inventory() {
@ -1966,61 +1997,59 @@ impl Scene for GameScene {
}
}
let mut ticks = 1;
if state.textscript_vm.mode == ScriptMode::Map && state.textscript_vm.flags.cutscene_skip() {
ticks = 4;
}
match state.textscript_vm.mode {
ScriptMode::Map => {
TextScriptVM::run(state, self, ctx)?;
for _ in 0..ticks {
match state.textscript_vm.mode {
ScriptMode::Map => {
TextScriptVM::run(state, self, ctx)?;
if state.control_flags.tick_world() {
self.tick_world(state)?;
match state.textscript_vm.state {
TextScriptExecutionState::FallingIsland(_, _, _, _, _, _) => (),
_ => {
if state.control_flags.tick_world() {
self.tick_world(state)?;
}
}
}
ScriptMode::StageSelect => {
self.stage_select.tick(state, (ctx, &self.player1, &self.player2))?;
TextScriptVM::run(state, self, ctx)?;
}
ScriptMode::Inventory => {
self.inventory_ui.tick(state, (ctx, &mut self.player1, &mut self.inventory_player1))?;
TextScriptVM::run(state, self, ctx)?;
}
}
ScriptMode::StageSelect => {
self.stage_select.tick(state, (ctx, &self.player1, &self.player2))?;
if state.control_flags.credits_running() {
self.skip_counter = 0;
CreditScriptVM::run(state, ctx)?;
TextScriptVM::run(state, self, ctx)?;
}
ScriptMode::Inventory => {
self.inventory_ui.tick(state, (ctx, &mut self.player1, &mut self.inventory_player1))?;
match state.fade_state {
FadeState::FadeOut(tick, direction) if tick < 15 => {
state.fade_state = FadeState::FadeOut(tick + 1, direction);
}
FadeState::FadeOut(tick, _) if tick == 15 => {
state.fade_state = FadeState::Hidden;
}
FadeState::FadeIn(tick, direction) if tick > -15 => {
state.fade_state = FadeState::FadeIn(tick - 1, direction);
}
FadeState::FadeIn(tick, _) if tick == -15 => {
state.fade_state = FadeState::Visible;
}
_ => {}
TextScriptVM::run(state, self, ctx)?;
}
}
self.flash.tick(state, ())?;
if state.control_flags.credits_running() {
self.skip_counter = 0;
CreditScriptVM::run(state, ctx)?;
}
#[cfg(feature = "scripting-lua")]
state.lua.scene_tick();
if state.control_flags.tick_world() {
self.tick = self.tick.wrapping_add(1);
match state.fade_state {
FadeState::FadeOut(tick, direction) if tick < 15 => {
state.fade_state = FadeState::FadeOut(tick + 1, direction);
}
FadeState::FadeOut(tick, _) if tick == 15 => {
state.fade_state = FadeState::Hidden;
}
FadeState::FadeIn(tick, direction) if tick > -15 => {
state.fade_state = FadeState::FadeIn(tick - 1, direction);
}
FadeState::FadeIn(tick, _) if tick == -15 => {
state.fade_state = FadeState::Visible;
}
_ => {}
}
self.flash.tick(state, ())?;
#[cfg(feature = "scripting-lua")]
state.lua.scene_tick();
if state.control_flags.tick_world() {
self.tick = self.tick.wrapping_add(1);
}
Ok(())
@ -2105,7 +2134,10 @@ impl Scene for GameScene {
self.player1.popup.draw(state, ctx, &self.frame)?;
self.player2.popup.draw(state, ctx, &self.frame)?;
if state.settings.shader_effects && self.lighting_mode == LightingMode::Ambient {
if !state.control_flags.credits_running()
&& state.settings.shader_effects
&& self.lighting_mode == LightingMode::Ambient
{
self.draw_light_map(state, ctx)?;
}
self.flash.draw(state, ctx, &self.frame)?;
@ -2206,6 +2238,7 @@ impl Scene for GameScene {
self.credits.draw(state, ctx, &self.frame)?;
}
self.falling_island.draw(state, ctx, &self.frame)?;
self.draw_text_boxes(state, ctx)?;
if self.skip_counter > 0 {

View file

@ -92,6 +92,7 @@ pub enum TextScriptExecutionState {
WaitStanding(u16, u32),
WaitConfirmation(u16, u32, u16, u8, ConfirmSelection),
WaitFade(u16, u32),
FallingIsland(u16, u32, i32, i32, u16, bool),
SaveProfile(u16, u32),
LoadProfile,
Reset,
@ -517,7 +518,31 @@ impl TextScriptVM {
}
break;
}
TextScriptExecutionState::FallingIsland(event, ip, pos_x, mut pos_y, mut tick, mode) => {
if tick == 900 {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
break;
}
tick += 1;
if mode {
if tick < 350 {
pos_y += 0x33;
} else if tick < 500 {
pos_y += 0x19;
} else if tick < 600 {
pos_y += 0xC;
} else if tick == 750 {
tick = 900;
}
} else {
pos_y += 0x33;
}
state.textscript_vm.state = TextScriptExecutionState::FallingIsland(event, ip, pos_x, pos_y, tick, mode);
break;
}
TextScriptExecutionState::SaveProfile(event, ip) => {
state.save_game(game_scene, ctx)?;
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
@ -1326,6 +1351,7 @@ impl TextScriptVM {
&mut game_scene.stage,
&mut game_scene.bullet_manager,
&mut game_scene.flash,
&mut game_scene.boss,
),
)?;
}
@ -1561,22 +1587,45 @@ impl TextScriptVM {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CPS => {
state.sound_manager.stop_sfx(58);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SPS => {
state.sound_manager.loop_sfx(58);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::CSS => {
state.sound_manager.stop_sfx(40);
state.sound_manager.stop_sfx(41);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::SSS => {
let _freq = read_cur_varint(&mut cursor)?;
// todo change freq
state.sound_manager.loop_sfx(40);
state.sound_manager.loop_sfx(41);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
TSCOpCode::XX1 => {
let mode = read_cur_varint(&mut cursor)?;
exec_state = TextScriptExecutionState::FallingIsland(event, cursor.position() as u32, 0x15000, 0x8000, 0, mode != 0);
}
// unimplemented opcodes
// Zero operands
TSCOpCode::CPS
| TSCOpCode::KE2
| TSCOpCode::CSS
| TSCOpCode::MLP
| TSCOpCode::SPS
| TSCOpCode::FR2
| TSCOpCode::STC
| TSCOpCode::HM2 => {
TSCOpCode::KE2 | TSCOpCode::MLP | TSCOpCode::FR2 | TSCOpCode::STC | TSCOpCode::HM2 => {
log::warn!("unimplemented opcode: {:?}", op);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
// One operand codes
TSCOpCode::UNJ | TSCOpCode::XX1 | TSCOpCode::SSS | TSCOpCode::ACH => {
TSCOpCode::UNJ | TSCOpCode::ACH => {
let par_a = read_cur_varint(&mut cursor)?;
log::warn!("unimplemented opcode: {:?} {}", op, par_a);

View file

@ -13,7 +13,7 @@ impl Weapon {
bullet_manager: &mut BulletManager,
state: &mut SharedGameState,
) {
const BULLETS: [u16; 6] = [44, 45, 46, 47, 48, 49];
const BULLETS: [u16; 6] = [37, 38, 39, 40, 41, 42];
let mut shoot = false;
let btype;
@ -22,7 +22,7 @@ impl Weapon {
self.add_xp(if player.equip.has_turbocharge() { 3 } else { 2 }, player, state);
self.counter1 += 1;
if (self.counter1 / 2 % 2) != 0 {
if self.counter1 & 2 != 0 {
match self.level {
WeaponLevel::Level1 => {
state.sound_manager.play_sfx(59);