2020-09-12 04:43:29 +00:00
|
|
|
use std::cell::RefCell;
|
2020-11-04 23:25:18 +00:00
|
|
|
use std::collections::{BTreeMap, HashSet};
|
2020-09-04 23:08:33 +00:00
|
|
|
use std::io;
|
|
|
|
use std::io::Cursor;
|
2020-11-04 23:25:18 +00:00
|
|
|
use std::ops::DerefMut;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2020-09-05 02:09:52 +00:00
|
|
|
use bitvec::vec::BitVec;
|
2020-09-04 23:08:33 +00:00
|
|
|
use byteorder::{LE, ReadBytesExt};
|
2020-11-04 23:25:18 +00:00
|
|
|
use ggez::{Context, GameResult};
|
2020-10-30 22:47:29 +00:00
|
|
|
use num_traits::abs;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2020-09-20 15:27:31 +00:00
|
|
|
use crate::bitfield;
|
2020-09-12 04:43:29 +00:00
|
|
|
use crate::caret::CaretType;
|
2020-10-30 22:47:29 +00:00
|
|
|
use crate::common::{Condition, fix9_scale, interpolate_fix9_scale, Rect};
|
2020-09-04 23:08:33 +00:00
|
|
|
use crate::common::Direction;
|
|
|
|
use crate::common::Flag;
|
|
|
|
use crate::entity::GameEntity;
|
|
|
|
use crate::frame::Frame;
|
|
|
|
use crate::map::NPCData;
|
2020-11-02 02:20:16 +00:00
|
|
|
use crate::npc::boss::BossNPC;
|
2020-09-09 13:06:11 +00:00
|
|
|
use crate::physics::PhysicalEntity;
|
2020-09-05 02:09:52 +00:00
|
|
|
use crate::player::Player;
|
2020-09-20 15:27:31 +00:00
|
|
|
use crate::shared_game_state::SharedGameState;
|
2020-09-23 13:10:42 +00:00
|
|
|
use crate::stage::Stage;
|
2020-09-20 23:05:02 +00:00
|
|
|
use crate::str;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2020-09-23 19:43:50 +00:00
|
|
|
pub mod balrog;
|
2020-10-02 22:11:09 +00:00
|
|
|
pub mod boss;
|
2020-11-24 23:08:27 +00:00
|
|
|
pub mod chaco;
|
2020-09-06 00:37:42 +00:00
|
|
|
pub mod characters;
|
2020-09-10 11:29:25 +00:00
|
|
|
pub mod egg_corridor;
|
2020-09-06 00:37:42 +00:00
|
|
|
pub mod first_cave;
|
2020-10-02 01:30:42 +00:00
|
|
|
pub mod grasstown;
|
2020-10-27 01:05:49 +00:00
|
|
|
pub mod igor;
|
2020-10-30 13:50:10 +00:00
|
|
|
pub mod intro;
|
2020-10-02 23:53:32 +00:00
|
|
|
pub mod maze;
|
2020-09-06 00:37:42 +00:00
|
|
|
pub mod mimiga_village;
|
2020-09-04 23:08:33 +00:00
|
|
|
pub mod misc;
|
2020-09-19 15:37:32 +00:00
|
|
|
pub mod misery;
|
2020-09-12 21:43:27 +00:00
|
|
|
pub mod pickups;
|
2020-10-31 01:16:51 +00:00
|
|
|
pub mod quote;
|
2020-10-02 01:30:42 +00:00
|
|
|
pub mod sand_zone;
|
2020-10-30 13:50:10 +00:00
|
|
|
pub mod santa;
|
2020-10-27 01:05:49 +00:00
|
|
|
pub mod sue;
|
2020-09-19 15:37:32 +00:00
|
|
|
pub mod toroko;
|
2020-10-02 01:30:42 +00:00
|
|
|
pub mod weapon_trail;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
|
|
|
bitfield! {
|
2020-09-12 04:43:29 +00:00
|
|
|
#[derive(Clone, Copy)]
|
2020-09-04 23:08:33 +00:00
|
|
|
pub struct NPCFlag(u16);
|
2020-08-18 16:46:07 +00:00
|
|
|
impl Debug;
|
|
|
|
|
2020-09-30 03:11:25 +00:00
|
|
|
pub solid_soft, set_solid_soft: 0; // 0x01
|
|
|
|
pub ignore_tile_44, set_ignore_tile_44: 1; // 0x02
|
|
|
|
pub invulnerable, set_invulnerable: 2; // 0x04
|
|
|
|
pub ignore_solidity, set_ignore_solidity: 3; // 0x08
|
|
|
|
pub bouncy, set_bouncy: 4; // 0x10
|
|
|
|
pub shootable, set_shootable: 5; // 0x20
|
|
|
|
pub solid_hard, set_solid_hard: 6; // 0x40
|
|
|
|
pub rear_and_top_not_hurt, set_rear_and_top_not_hurt: 7; // 0x80
|
|
|
|
pub event_when_touched, set_event_when_touched: 8; // 0x100
|
|
|
|
pub event_when_killed, set_event_when_killed: 9; // 0x200
|
|
|
|
pub flag_x400, set_flag_x400: 10; // 0x400
|
|
|
|
pub appear_when_flag_set, set_appear_when_flag_set: 11; // 0x800
|
|
|
|
pub spawn_facing_right, set_spawn_facing_right: 12; // 0x1000
|
|
|
|
pub interactable, set_interactable: 13; // 0x2000
|
|
|
|
pub hide_unless_flag_set, set_hide_unless_flag_set: 14; // 0x4000
|
|
|
|
pub show_damage, set_show_damage: 15; // 0x8000
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
2020-09-04 23:08:33 +00:00
|
|
|
|
2020-09-13 05:18:24 +00:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
2020-09-04 23:08:33 +00:00
|
|
|
pub struct NPC {
|
|
|
|
pub id: u16,
|
|
|
|
pub npc_type: u16,
|
|
|
|
pub x: isize,
|
|
|
|
pub y: isize,
|
2020-11-01 19:05:29 +00:00
|
|
|
/// X velocity, affected by physics code
|
2020-09-04 23:08:33 +00:00
|
|
|
pub vel_x: isize,
|
2020-11-01 19:05:29 +00:00
|
|
|
/// Y velocity, affected by physics code
|
2020-09-04 23:08:33 +00:00
|
|
|
pub vel_y: isize,
|
2020-11-01 19:05:29 +00:00
|
|
|
/// X velocity, unaffected by physics code
|
|
|
|
pub vel_x2: isize,
|
|
|
|
/// Y velocity, unaffected by physics code
|
|
|
|
pub vel_y2: isize,
|
2020-09-04 23:08:33 +00:00
|
|
|
pub target_x: isize,
|
|
|
|
pub target_y: isize,
|
2020-11-01 19:05:29 +00:00
|
|
|
/// Previous X position, used by frame interpolator
|
2020-10-30 22:47:29 +00:00
|
|
|
pub prev_x: isize,
|
2020-11-01 19:05:29 +00:00
|
|
|
/// Previous Y position, used by frame interpolator
|
2020-10-30 22:47:29 +00:00
|
|
|
pub prev_y: isize,
|
2020-09-12 21:43:27 +00:00
|
|
|
pub exp: u16,
|
2020-09-09 13:06:11 +00:00
|
|
|
pub size: u8,
|
2020-09-06 00:37:42 +00:00
|
|
|
pub shock: u16,
|
2020-09-04 23:08:33 +00:00
|
|
|
pub life: u16,
|
2020-09-09 16:25:39 +00:00
|
|
|
pub damage: u16,
|
2020-09-04 23:08:33 +00:00
|
|
|
pub cond: Condition,
|
|
|
|
pub flags: Flag,
|
|
|
|
pub npc_flags: NPCFlag,
|
|
|
|
pub direction: Direction,
|
2020-11-01 19:05:29 +00:00
|
|
|
/// Raw direction value set by TSC because some NPCs have it set outside 0-4 range,
|
|
|
|
/// breaking the direction type.
|
|
|
|
pub tsc_direction: u16,
|
2020-09-04 23:08:33 +00:00
|
|
|
pub display_bounds: Rect<usize>,
|
|
|
|
pub hit_bounds: Rect<usize>,
|
2020-09-30 03:11:25 +00:00
|
|
|
pub parent_id: u16,
|
2020-09-04 23:08:33 +00:00
|
|
|
pub action_num: u16,
|
|
|
|
pub anim_num: u16,
|
2020-09-05 02:09:52 +00:00
|
|
|
pub flag_num: u16,
|
2020-09-04 23:08:33 +00:00
|
|
|
pub event_num: u16,
|
|
|
|
pub action_counter: u16,
|
2020-09-12 21:43:27 +00:00
|
|
|
pub action_counter2: u16,
|
2020-09-04 23:08:33 +00:00
|
|
|
pub anim_counter: u16,
|
2020-11-17 02:42:45 +00:00
|
|
|
pub anim_rect: Rect<u16>,
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 10:46:51 +00:00
|
|
|
static PARTICLE_NPCS: [u16; 11] = [1, 4, 11, 73, 84, 86, 87, 108, 129, 199, 355];
|
2020-09-23 13:10:42 +00:00
|
|
|
|
2020-09-13 05:18:24 +00:00
|
|
|
impl NPC {
|
|
|
|
pub fn get_start_index(&self) -> u16 {
|
2020-09-23 13:10:42 +00:00
|
|
|
if PARTICLE_NPCS.contains(&self.npc_type) {
|
2020-09-13 05:18:24 +00:00
|
|
|
0x100
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
2020-10-02 23:53:32 +00:00
|
|
|
|
2020-10-02 22:11:09 +00:00
|
|
|
pub fn empty() -> NPC {
|
|
|
|
NPC {
|
|
|
|
id: 0,
|
|
|
|
npc_type: 0,
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
vel_x: 0,
|
|
|
|
vel_y: 0,
|
2020-11-01 19:05:29 +00:00
|
|
|
vel_x2: 0,
|
|
|
|
vel_y2: 0,
|
2020-10-02 22:11:09 +00:00
|
|
|
target_x: 0,
|
|
|
|
target_y: 0,
|
2020-10-30 22:47:29 +00:00
|
|
|
prev_x: 0,
|
|
|
|
prev_y: 0,
|
2020-10-02 22:11:09 +00:00
|
|
|
exp: 0,
|
|
|
|
size: 0,
|
|
|
|
shock: 0,
|
|
|
|
life: 0,
|
|
|
|
damage: 0,
|
|
|
|
cond: Condition(0),
|
|
|
|
flags: Flag(0),
|
|
|
|
npc_flags: NPCFlag(0),
|
|
|
|
direction: Direction::Left,
|
2020-11-01 19:05:29 +00:00
|
|
|
tsc_direction: 0,
|
2020-10-02 22:11:09 +00:00
|
|
|
display_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 },
|
|
|
|
hit_bounds: Rect { left: 0, top: 0, right: 0, bottom: 0 },
|
|
|
|
parent_id: 0,
|
|
|
|
action_num: 0,
|
|
|
|
anim_num: 0,
|
|
|
|
flag_num: 0,
|
|
|
|
event_num: 0,
|
|
|
|
action_counter: 0,
|
|
|
|
action_counter2: 0,
|
|
|
|
anim_counter: 0,
|
|
|
|
anim_rect: Rect { left: 0, top: 0, right: 0, bottom: 0 },
|
|
|
|
}
|
|
|
|
}
|
2020-09-13 05:18:24 +00:00
|
|
|
}
|
|
|
|
|
2020-11-04 23:25:18 +00:00
|
|
|
impl GameEntity<(&mut Player, &BTreeMap<u16, RefCell<NPC>>, &mut Stage)> for NPC {
|
|
|
|
fn tick(&mut self, state: &mut SharedGameState, (player, map, stage): (&mut Player, &BTreeMap<u16, RefCell<NPC>>, &mut Stage)) -> GameResult {
|
2020-09-04 23:08:33 +00:00
|
|
|
match self.npc_type {
|
2020-10-02 23:53:32 +00:00
|
|
|
0 => self.tick_n000_null(),
|
|
|
|
1 => self.tick_n001_experience(state),
|
|
|
|
2 => self.tick_n002_behemoth(state),
|
|
|
|
3 => self.tick_n003_dead_enemy(),
|
|
|
|
4 => self.tick_n004_smoke(state),
|
|
|
|
5 => self.tick_n005_green_critter(state, player),
|
|
|
|
6 => self.tick_n006_green_beetle(state),
|
|
|
|
7 => self.tick_n007_basil(state, player),
|
|
|
|
8 => self.tick_n008_blue_beetle(state, player),
|
|
|
|
9 => self.tick_n009_balrog_falling_in(state),
|
|
|
|
10 => self.tick_n010_balrog_shooting(state, player),
|
|
|
|
11 => self.tick_n011_balrogs_projectile(state),
|
|
|
|
12 => self.tick_n012_balrog_cutscene(state, player, map, stage),
|
|
|
|
13 => self.tick_n013_forcefield(state),
|
|
|
|
14 => self.tick_n014_key(state),
|
|
|
|
15 => self.tick_n015_chest_closed(state),
|
|
|
|
16 => self.tick_n016_save_point(state),
|
|
|
|
17 => self.tick_n017_health_refill(state),
|
|
|
|
18 => self.tick_n018_door(state),
|
|
|
|
19 => self.tick_n019_balrog_bust_in(state),
|
|
|
|
20 => self.tick_n020_computer(state),
|
|
|
|
21 => self.tick_n021_chest_open(state),
|
|
|
|
22 => self.tick_n022_teleporter(state),
|
2020-11-01 19:05:29 +00:00
|
|
|
23 => self.tick_n023_teleporter_lights(state),
|
|
|
|
24 => self.tick_n024_power_critter(state, player),
|
|
|
|
25 => self.tick_n025_lift(state),
|
|
|
|
26 => self.tick_n026_bat_flying(state, player),
|
2020-10-02 23:53:32 +00:00
|
|
|
27 => self.tick_n027_death_trap(state),
|
2020-11-01 19:05:29 +00:00
|
|
|
28 => self.tick_n028_flying_critter(state, player),
|
|
|
|
29 => self.tick_n029_cthulhu(state, player),
|
2020-10-02 23:53:32 +00:00
|
|
|
30 => self.tick_n030_gunsmith(state),
|
2020-11-26 08:41:28 +00:00
|
|
|
31 => self.tick_n031_bat_hanging(state, player),
|
2020-10-02 23:53:32 +00:00
|
|
|
32 => self.tick_n032_life_capsule(state),
|
2020-11-26 08:41:28 +00:00
|
|
|
33 => self.tick_n033_balrog_bouncing_projectile(state),
|
2020-10-02 23:53:32 +00:00
|
|
|
34 => self.tick_n034_bed(state),
|
2020-11-04 17:22:38 +00:00
|
|
|
35 => self.tick_n035_mannan(state),
|
2020-11-26 08:41:28 +00:00
|
|
|
36 => self.tick_n036_balrog_hover(state, player),
|
2020-10-02 23:53:32 +00:00
|
|
|
37 => self.tick_n037_sign(state),
|
|
|
|
38 => self.tick_n038_fireplace(state),
|
|
|
|
39 => self.tick_n039_save_sign(state),
|
2020-11-26 08:41:28 +00:00
|
|
|
40 => self.tick_n040_santa(state, player),
|
2020-10-02 23:53:32 +00:00
|
|
|
41 => self.tick_n041_busted_door(state),
|
2020-10-27 01:05:49 +00:00
|
|
|
42 => self.tick_n042_sue(state, player, map),
|
2020-10-02 23:53:32 +00:00
|
|
|
43 => self.tick_n043_chalkboard(state),
|
|
|
|
46 => self.tick_n046_hv_trigger(state, player),
|
|
|
|
52 => self.tick_n052_sitting_blue_robot(state),
|
|
|
|
55 => self.tick_n055_kazuma(state),
|
2020-11-01 19:05:29 +00:00
|
|
|
58 => self.tick_n058_basu(state, player),
|
2020-10-02 23:53:32 +00:00
|
|
|
59 => self.tick_n059_eye_door(state, player),
|
|
|
|
60 => self.tick_n060_toroko(state, player),
|
|
|
|
61 => self.tick_n061_king(state),
|
|
|
|
62 => self.tick_n062_kazuma_computer(state),
|
|
|
|
63 => self.tick_n063_toroko_stick(state),
|
|
|
|
64 => self.tick_n064_first_cave_critter(state, player),
|
|
|
|
65 => self.tick_n065_first_cave_bat(state, player),
|
|
|
|
66 => self.tick_n066_misery_bubble(state, map),
|
|
|
|
67 => self.tick_n067_misery_floating(state),
|
|
|
|
68 => self.tick_n068_balrog_running(state, player),
|
|
|
|
69 => self.tick_n069_pignon(state),
|
|
|
|
70 => self.tick_n070_sparkle(state),
|
|
|
|
71 => self.tick_n071_chinfish(state),
|
|
|
|
72 => self.tick_n072_sprinkler(state, player),
|
|
|
|
73 => self.tick_n073_water_droplet(state, stage),
|
|
|
|
74 => self.tick_n074_jack(state),
|
|
|
|
75 => self.tick_n075_kanpachi(state, player),
|
|
|
|
76 => self.tick_n076_flowers(),
|
|
|
|
77 => self.tick_n077_yamashita(state),
|
|
|
|
78 => self.tick_n078_pot(state),
|
|
|
|
79 => self.tick_n079_mahin(state, player),
|
2020-10-27 01:05:49 +00:00
|
|
|
80 => self.tick_n080_gravekeeper(state, player),
|
|
|
|
81 => self.tick_n081_giant_pignon(state, player),
|
|
|
|
82 => self.tick_n082_misery_standing(state),
|
|
|
|
83 => self.tick_n083_igor_cutscene(state),
|
|
|
|
84 => self.tick_n084_basu_projectile(state),
|
|
|
|
85 => self.tick_n085_terminal(state, player),
|
2020-10-30 10:24:55 +00:00
|
|
|
86 => self.tick_n086_missile_pickup(state),
|
|
|
|
87 => self.tick_n087_heart_pickup(state),
|
2020-11-17 10:46:51 +00:00
|
|
|
88 => self.tick_n088_igor_boss(state, player),
|
|
|
|
89 => self.tick_n089_igor_dead(state, player),
|
2020-10-27 01:05:49 +00:00
|
|
|
91 => self.tick_n091_mimiga_cage(state),
|
2020-11-24 23:08:27 +00:00
|
|
|
92 => self.tick_n092_sue_at_pc(state),
|
|
|
|
93 => self.tick_n093_chaco(state, player),
|
2020-11-04 15:37:00 +00:00
|
|
|
94 => self.tick_n094_kulala(state, player),
|
|
|
|
95 => self.tick_n095_jelly(state),
|
2020-10-26 08:34:54 +00:00
|
|
|
96 => self.tick_n096_fan_left(state, player),
|
|
|
|
97 => self.tick_n097_fan_up(state, player),
|
|
|
|
98 => self.tick_n098_fan_right(state, player),
|
|
|
|
99 => self.tick_n099_fan_down(state, player),
|
2020-11-26 08:41:28 +00:00
|
|
|
100 => self.tick_n100_grate(state),
|
|
|
|
101 => self.tick_n101_malco_screen(state),
|
|
|
|
102 => self.tick_n102_malco_computer_wave(state),
|
|
|
|
103 => self.tick_n103_mannan_projectile(state),
|
2020-11-04 09:06:02 +00:00
|
|
|
104 => self.tick_n104_frog(state, player),
|
2020-11-26 08:41:28 +00:00
|
|
|
105 => self.tick_n105_hey_bubble_low(state),
|
|
|
|
106 => self.tick_n106_hey_bubble_high(state),
|
|
|
|
107 => self.tick_n107_malco_broken(state),
|
2020-11-04 09:06:02 +00:00
|
|
|
108 => self.tick_n108_balfrog_projectile(state),
|
2020-11-26 08:41:28 +00:00
|
|
|
109 => self.tick_n109_malco_powered_on(state, player),
|
2020-11-04 09:06:02 +00:00
|
|
|
110 => self.tick_n110_puchi(state, player),
|
2020-10-31 01:16:51 +00:00
|
|
|
111 => self.tick_n111_quote_teleport_out(state, player),
|
|
|
|
112 => self.tick_n112_quote_teleport_in(state, player),
|
2020-11-26 08:41:28 +00:00
|
|
|
114 => self.tick_n114_press(state, player),
|
2020-10-02 23:53:32 +00:00
|
|
|
129 => self.tick_n129_fireball_snake_trail(state),
|
|
|
|
149 => self.tick_n149_horizontal_moving_block(state, player),
|
2020-11-01 19:05:29 +00:00
|
|
|
150 => self.tick_n150_quote(state, player),
|
|
|
|
154 => self.tick_n154_gaudi_dead(state),
|
2020-10-02 23:53:32 +00:00
|
|
|
157 => self.tick_n157_vertical_moving_block(state, player),
|
2020-10-26 08:34:54 +00:00
|
|
|
199 => self.tick_n199_wind_particles(state),
|
2020-10-02 23:53:32 +00:00
|
|
|
211 => self.tick_n211_small_spikes(state),
|
2020-10-30 13:50:10 +00:00
|
|
|
298 => self.tick_n298_intro_doctor(state),
|
|
|
|
299 => self.tick_n299_intro_balrog_misery(state),
|
|
|
|
300 => self.tick_n300_intro_demon_crown(state),
|
2020-11-01 19:05:29 +00:00
|
|
|
361 => self.tick_n361_gaudi_dashing(state, player),
|
2020-10-02 23:53:32 +00:00
|
|
|
_ => Ok(()),
|
2020-09-13 00:21:12 +00:00
|
|
|
}?;
|
|
|
|
|
|
|
|
if self.shock > 0 {
|
|
|
|
self.shock -= 1;
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
2020-09-13 00:21:12 +00:00
|
|
|
|
2020-10-30 22:47:29 +00:00
|
|
|
if abs(self.prev_x - self.x) > 0x1000 {
|
|
|
|
self.prev_x = self.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
if abs(self.prev_y - self.y) > 0x1000 {
|
|
|
|
self.prev_y = self.y;
|
|
|
|
}
|
|
|
|
|
2020-09-13 00:21:12 +00:00
|
|
|
Ok(())
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult {
|
|
|
|
if !self.cond.alive() || self.cond.hidden() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, state.npc_table.get_texture_name(self.npc_type))?;
|
|
|
|
|
|
|
|
let off_x = if self.direction == Direction::Left { self.display_bounds.left } else { self.display_bounds.right } as isize;
|
2020-09-13 00:21:12 +00:00
|
|
|
let shock = if self.shock > 0 {
|
|
|
|
(2 * ((self.shock as isize / 2) % 2) - 1) as f32
|
|
|
|
} else { 0.0 };
|
|
|
|
|
2020-09-04 23:08:33 +00:00
|
|
|
batch.add_rect(
|
2020-10-30 22:47:29 +00:00
|
|
|
interpolate_fix9_scale(self.prev_x - off_x - frame.prev_x,
|
|
|
|
self.x - off_x - frame.x,
|
2020-11-07 17:17:01 +00:00
|
|
|
state.frame_time) + shock,
|
2020-10-30 22:47:29 +00:00
|
|
|
interpolate_fix9_scale(self.prev_y - self.display_bounds.top as isize - frame.prev_y,
|
|
|
|
self.y - self.display_bounds.top as isize - frame.y,
|
2020-11-07 17:17:01 +00:00
|
|
|
state.frame_time),
|
2020-09-04 23:08:33 +00:00
|
|
|
&self.anim_rect,
|
|
|
|
);
|
|
|
|
batch.draw(ctx)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-09 13:06:11 +00:00
|
|
|
impl PhysicalEntity for NPC {
|
|
|
|
#[inline(always)]
|
2020-10-01 03:06:18 +00:00
|
|
|
fn x(&self) -> isize { self.x }
|
2020-09-09 13:06:11 +00:00
|
|
|
|
|
|
|
#[inline(always)]
|
2020-10-01 03:06:18 +00:00
|
|
|
fn y(&self) -> isize { self.y }
|
2020-09-09 13:06:11 +00:00
|
|
|
|
|
|
|
#[inline(always)]
|
2020-10-01 03:06:18 +00:00
|
|
|
fn vel_x(&self) -> isize { self.vel_x }
|
2020-09-09 13:06:11 +00:00
|
|
|
|
|
|
|
#[inline(always)]
|
2020-10-01 03:06:18 +00:00
|
|
|
fn vel_y(&self) -> isize { self.vel_y }
|
2020-09-09 13:06:11 +00:00
|
|
|
|
|
|
|
#[inline(always)]
|
2020-11-02 14:01:30 +00:00
|
|
|
fn hit_rect_size(&self) -> usize {
|
|
|
|
if self.size >= 3 {
|
|
|
|
if self.cond.drs_boss() { 4 } else { 3 }
|
|
|
|
} else {
|
|
|
|
2
|
|
|
|
}
|
|
|
|
}
|
2020-10-01 03:06:18 +00:00
|
|
|
|
|
|
|
#[inline(always)]
|
2020-11-02 14:01:30 +00:00
|
|
|
fn offset_x(&self) -> isize { if self.size >= 3 && !self.cond.drs_boss() { -0x1000 } else { 0 } }
|
2020-10-01 03:06:18 +00:00
|
|
|
|
|
|
|
#[inline(always)]
|
2020-11-02 14:01:30 +00:00
|
|
|
fn offset_y(&self) -> isize { if self.size >= 3 && !self.cond.drs_boss() { -0x1000 } else { 0 } }
|
2020-09-09 13:06:11 +00:00
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn hit_bounds(&self) -> &Rect<usize> {
|
|
|
|
&self.hit_bounds
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn set_x(&mut self, x: isize) {
|
|
|
|
self.x = x;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn set_y(&mut self, y: isize) {
|
|
|
|
self.y = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn set_vel_x(&mut self, vel_x: isize) {
|
|
|
|
self.vel_x = vel_x;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn set_vel_y(&mut self, vel_y: isize) {
|
|
|
|
self.vel_y = vel_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn cond(&mut self) -> &mut Condition {
|
|
|
|
&mut self.cond
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn flags(&mut self) -> &mut Flag {
|
|
|
|
&mut self.flags
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:27 +00:00
|
|
|
#[inline(always)]
|
|
|
|
fn direction(&self) -> Direction {
|
|
|
|
self.direction
|
|
|
|
}
|
|
|
|
|
2020-09-09 13:06:11 +00:00
|
|
|
#[inline(always)]
|
|
|
|
fn is_player(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn ignore_tile_44(&self) -> bool {
|
|
|
|
self.npc_flags.ignore_tile_44()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-04 23:08:33 +00:00
|
|
|
pub struct NPCMap {
|
2020-11-04 23:25:18 +00:00
|
|
|
ids: HashSet<u16>,
|
|
|
|
pub npcs: BTreeMap<u16, RefCell<NPC>>,
|
2020-10-02 22:11:09 +00:00
|
|
|
/// NPCMap but for bosses and of static size.
|
2020-11-02 02:20:16 +00:00
|
|
|
pub boss_map: BossNPC,
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl NPCMap {
|
|
|
|
#[allow(clippy::new_without_default)]
|
|
|
|
pub fn new() -> NPCMap {
|
|
|
|
NPCMap {
|
2020-11-04 23:25:18 +00:00
|
|
|
ids: HashSet::new(),
|
|
|
|
npcs: BTreeMap::new(),
|
2020-11-02 02:20:16 +00:00
|
|
|
boss_map: BossNPC::new(),
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear(&mut self) {
|
2020-11-04 23:25:18 +00:00
|
|
|
self.ids.clear();
|
2020-09-04 23:08:33 +00:00
|
|
|
self.npcs.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn create_npc_from_data(&mut self, table: &NPCTable, data: &NPCData) -> &mut NPC {
|
2020-09-09 13:06:11 +00:00
|
|
|
let display_bounds = table.get_display_bounds(data.npc_type);
|
|
|
|
let hit_bounds = table.get_hit_bounds(data.npc_type);
|
2020-09-20 23:05:02 +00:00
|
|
|
let (size, life, damage, flags, exp) = match table.get_entry(data.npc_type) {
|
|
|
|
Some(entry) => { (entry.size, entry.life, entry.damage as u16, entry.npc_flags, entry.experience as u16) }
|
|
|
|
None => { (1, 0, 0, NPCFlag(0), 0) }
|
2020-09-09 13:06:11 +00:00
|
|
|
};
|
2020-09-12 04:43:29 +00:00
|
|
|
let npc_flags = NPCFlag(data.flags | flags.0);
|
2020-09-09 13:06:11 +00:00
|
|
|
|
2020-09-04 23:08:33 +00:00
|
|
|
let npc = NPC {
|
|
|
|
id: data.id,
|
|
|
|
npc_type: data.npc_type,
|
|
|
|
x: data.x as isize * 16 * 0x200,
|
|
|
|
y: data.y as isize * 16 * 0x200,
|
|
|
|
vel_x: 0,
|
|
|
|
vel_y: 0,
|
2020-11-01 19:05:29 +00:00
|
|
|
vel_x2: 0,
|
|
|
|
vel_y2: 0,
|
2020-09-04 23:08:33 +00:00
|
|
|
target_x: 0,
|
|
|
|
target_y: 0,
|
2020-10-30 22:47:29 +00:00
|
|
|
prev_x: 0,
|
|
|
|
prev_y: 0,
|
2020-09-04 23:08:33 +00:00
|
|
|
action_num: 0,
|
|
|
|
anim_num: 0,
|
2020-09-05 02:09:52 +00:00
|
|
|
flag_num: data.flag_num,
|
2020-09-04 23:08:33 +00:00
|
|
|
event_num: data.event_num,
|
2020-09-06 00:37:42 +00:00
|
|
|
shock: 0,
|
2020-09-12 21:43:27 +00:00
|
|
|
exp,
|
2020-09-09 13:06:11 +00:00
|
|
|
size,
|
|
|
|
life,
|
2020-09-09 16:25:39 +00:00
|
|
|
damage,
|
2020-09-04 23:08:33 +00:00
|
|
|
cond: Condition(0x00),
|
2020-09-12 04:43:29 +00:00
|
|
|
flags: Flag(0),
|
2020-09-06 00:37:42 +00:00
|
|
|
direction: if npc_flags.spawn_facing_right() { Direction::Right } else { Direction::Left },
|
2020-11-01 19:05:29 +00:00
|
|
|
tsc_direction: 0,
|
2020-09-06 00:37:42 +00:00
|
|
|
npc_flags,
|
2020-09-09 13:06:11 +00:00
|
|
|
display_bounds,
|
|
|
|
hit_bounds,
|
2020-09-30 03:11:25 +00:00
|
|
|
parent_id: 0,
|
2020-09-04 23:08:33 +00:00
|
|
|
action_counter: 0,
|
2020-09-12 21:43:27 +00:00
|
|
|
action_counter2: 0,
|
2020-09-04 23:08:33 +00:00
|
|
|
anim_counter: 0,
|
|
|
|
anim_rect: Rect::new(0, 0, 0, 0),
|
|
|
|
};
|
|
|
|
|
2020-11-04 23:25:18 +00:00
|
|
|
let cell = RefCell::new(npc);
|
|
|
|
self.npcs.insert(data.id, cell);
|
|
|
|
self.ids.insert(data.id);
|
2020-09-04 23:08:33 +00:00
|
|
|
|
2020-09-12 04:43:29 +00:00
|
|
|
self.npcs.get_mut(&data.id).unwrap().get_mut()
|
|
|
|
}
|
|
|
|
|
2020-09-13 05:18:24 +00:00
|
|
|
pub fn create_npc(npc_type: u16, table: &NPCTable) -> NPC {
|
2020-09-12 21:43:27 +00:00
|
|
|
let display_bounds = table.get_display_bounds(npc_type);
|
|
|
|
let hit_bounds = table.get_hit_bounds(npc_type);
|
2020-09-20 23:05:02 +00:00
|
|
|
let (size, life, damage, flags, exp) = match table.get_entry(npc_type) {
|
|
|
|
Some(entry) => { (entry.size, entry.life, entry.damage as u16, entry.npc_flags, entry.experience as u16) }
|
2020-10-31 14:32:13 +00:00
|
|
|
None => { (2, 0, 0, NPCFlag(0), 0) }
|
2020-09-12 21:43:27 +00:00
|
|
|
};
|
|
|
|
let npc_flags = NPCFlag(flags.0);
|
|
|
|
|
|
|
|
NPC {
|
|
|
|
id: 0,
|
|
|
|
npc_type,
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
vel_x: 0,
|
|
|
|
vel_y: 0,
|
2020-11-01 19:05:29 +00:00
|
|
|
vel_x2: 0,
|
|
|
|
vel_y2: 0,
|
2020-09-12 21:43:27 +00:00
|
|
|
target_x: 0,
|
|
|
|
target_y: 0,
|
2020-10-30 22:47:29 +00:00
|
|
|
prev_x: 0,
|
|
|
|
prev_y: 0,
|
2020-09-12 21:43:27 +00:00
|
|
|
action_num: 0,
|
|
|
|
anim_num: 0,
|
|
|
|
flag_num: 0,
|
|
|
|
event_num: 0,
|
|
|
|
shock: 0,
|
|
|
|
exp,
|
|
|
|
size,
|
|
|
|
life,
|
|
|
|
damage,
|
|
|
|
cond: Condition(0x00),
|
|
|
|
flags: Flag(0),
|
|
|
|
direction: if npc_flags.spawn_facing_right() { Direction::Right } else { Direction::Left },
|
2020-11-01 19:05:29 +00:00
|
|
|
tsc_direction: 0,
|
2020-09-12 21:43:27 +00:00
|
|
|
npc_flags,
|
|
|
|
display_bounds,
|
|
|
|
hit_bounds,
|
2020-09-30 03:11:25 +00:00
|
|
|
parent_id: 0,
|
2020-09-12 21:43:27 +00:00
|
|
|
action_counter: 0,
|
|
|
|
action_counter2: 0,
|
|
|
|
anim_counter: 0,
|
|
|
|
anim_rect: Rect::new(0, 0, 0, 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-12 04:43:29 +00:00
|
|
|
pub fn garbage_collect(&mut self) {
|
2020-11-04 23:25:18 +00:00
|
|
|
for npc_cell in self.npcs.values_mut() {
|
|
|
|
let mut npc = npc_cell.borrow();
|
2020-09-13 05:18:24 +00:00
|
|
|
|
2020-11-04 23:25:18 +00:00
|
|
|
if !npc.cond.alive() {
|
|
|
|
self.ids.remove(&npc.id);
|
|
|
|
}
|
2020-09-13 05:18:24 +00:00
|
|
|
}
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
2020-09-05 02:09:52 +00:00
|
|
|
|
|
|
|
pub fn remove_by_event(&mut self, event_num: u16, game_flags: &mut BitVec) {
|
2020-09-12 04:43:29 +00:00
|
|
|
for npc_cell in self.npcs.values_mut() {
|
|
|
|
let mut npc = npc_cell.borrow_mut();
|
|
|
|
|
2020-09-05 02:09:52 +00:00
|
|
|
if npc.event_num == event_num {
|
|
|
|
npc.cond.set_alive(false);
|
|
|
|
game_flags.set(npc.flag_num as usize, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
|
2020-10-31 14:32:13 +00:00
|
|
|
pub fn remove_by_type(&mut self, npc_type: u16, state: &mut SharedGameState) {
|
|
|
|
for npc_cell in self.npcs.values() {
|
|
|
|
let mut npc = npc_cell.borrow_mut();
|
|
|
|
|
|
|
|
if npc.npc_type == npc_type {
|
|
|
|
npc.cond.set_alive(false);
|
|
|
|
state.game_flags.set(npc.flag_num as usize, true);
|
|
|
|
|
|
|
|
match npc.size {
|
|
|
|
1 => self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 3, state),
|
|
|
|
2 => self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 7, state),
|
|
|
|
3 => self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 12, state),
|
|
|
|
_ => {}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-12 21:43:27 +00:00
|
|
|
pub fn allocate_id(&mut self, start: u16) -> u16 {
|
|
|
|
for i in start..(u16::MAX) {
|
2020-11-04 23:25:18 +00:00
|
|
|
if !self.ids.contains(&i) {
|
2020-09-12 04:43:29 +00:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-30 03:11:25 +00:00
|
|
|
0xffff
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
|
2020-09-13 05:18:24 +00:00
|
|
|
pub fn create_death_effect(&self, x: isize, y: isize, radius: usize, count: usize, state: &mut SharedGameState) {
|
2020-09-12 04:43:29 +00:00
|
|
|
let radius = radius as i32 / 0x200;
|
|
|
|
|
|
|
|
for _ in 0..count {
|
2020-09-13 05:18:24 +00:00
|
|
|
let off_x = state.game_rng.range(-radius..radius) as isize * 0x200;
|
|
|
|
let off_y = state.game_rng.range(-radius..radius) as isize * 0x200;
|
|
|
|
|
|
|
|
let mut npc = NPCMap::create_npc(4, &state.npc_table);
|
|
|
|
|
|
|
|
npc.cond.set_alive(true);
|
|
|
|
npc.direction = Direction::Left;
|
|
|
|
npc.x = x + off_x;
|
|
|
|
npc.y = y + off_y;
|
2020-09-12 04:43:29 +00:00
|
|
|
|
2020-09-13 05:18:24 +00:00
|
|
|
state.new_npcs.push(npc);
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
state.create_caret(x, y, CaretType::Explosion, Direction::Left);
|
|
|
|
}
|
|
|
|
|
2020-11-04 09:06:02 +00:00
|
|
|
pub fn process_dead_npcs(&mut self, list: &[u16], has_missile: bool, player: &Player, state: &mut SharedGameState) {
|
2020-09-12 04:43:29 +00:00
|
|
|
for id in list {
|
2020-09-20 23:05:02 +00:00
|
|
|
if let Some(npc_cell) = self.npcs.get(id) {
|
|
|
|
let mut npc = npc_cell.borrow_mut();
|
|
|
|
|
|
|
|
if let Some(table_entry) = state.npc_table.get_entry(npc.npc_type) {
|
|
|
|
state.sound_manager.play_sfx(table_entry.death_sound);
|
|
|
|
}
|
2020-09-12 04:43:29 +00:00
|
|
|
|
2020-09-13 05:18:24 +00:00
|
|
|
match npc.size {
|
|
|
|
1 => { self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 3, state); }
|
|
|
|
2 => { self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 7, state); }
|
|
|
|
3 => { self.create_death_effect(npc.x, npc.y, npc.display_bounds.right, 12, state); }
|
|
|
|
_ => {}
|
2020-09-12 04:43:29 +00:00
|
|
|
};
|
|
|
|
|
2020-09-12 21:43:27 +00:00
|
|
|
if npc.exp != 0 {
|
2020-10-30 10:24:55 +00:00
|
|
|
let rng = state.game_rng.range(0..4);
|
|
|
|
match rng {
|
|
|
|
0 => {
|
|
|
|
let mut heart_pick = NPCMap::create_npc(87, &state.npc_table);
|
|
|
|
heart_pick.cond.set_alive(true);
|
|
|
|
heart_pick.direction = Direction::Left;
|
|
|
|
heart_pick.x = npc.x;
|
|
|
|
heart_pick.y = npc.y;
|
|
|
|
heart_pick.exp = if npc.exp > 6 { 6 } else { 2 };
|
|
|
|
|
|
|
|
state.new_npcs.push(heart_pick);
|
|
|
|
}
|
|
|
|
1 if has_missile => {
|
|
|
|
let mut missile_pick = NPCMap::create_npc(86, &state.npc_table);
|
|
|
|
missile_pick.cond.set_alive(true);
|
|
|
|
missile_pick.direction = Direction::Left;
|
|
|
|
missile_pick.x = npc.x;
|
|
|
|
missile_pick.y = npc.y;
|
|
|
|
missile_pick.exp = if npc.exp > 6 { 3 } else { 1 };
|
|
|
|
|
|
|
|
state.new_npcs.push(missile_pick);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
let mut exp = npc.exp;
|
|
|
|
|
|
|
|
while exp > 0 {
|
|
|
|
let exp_piece = if exp >= 20 {
|
|
|
|
exp -= 20;
|
|
|
|
20
|
|
|
|
} else if exp >= 5 {
|
|
|
|
exp -= 5;
|
|
|
|
5
|
|
|
|
} else {
|
|
|
|
exp -= 1;
|
|
|
|
1
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut xp_npc = NPCMap::create_npc(1, &state.npc_table);
|
|
|
|
xp_npc.cond.set_alive(true);
|
|
|
|
xp_npc.direction = Direction::Left;
|
|
|
|
xp_npc.x = npc.x;
|
|
|
|
xp_npc.y = npc.y;
|
|
|
|
xp_npc.exp = exp_piece;
|
|
|
|
|
|
|
|
state.new_npcs.push(xp_npc);
|
|
|
|
}
|
|
|
|
}
|
2020-09-12 21:43:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-12 04:43:29 +00:00
|
|
|
state.game_flags.set(npc.flag_num as usize, true);
|
|
|
|
|
|
|
|
// todo vanish / show damage
|
|
|
|
|
2020-11-04 17:22:38 +00:00
|
|
|
if npc.cond.drs_dont_remove() {
|
|
|
|
npc.cond.set_drs_dont_remove(false);
|
|
|
|
} else {
|
|
|
|
npc.cond.set_alive(false);
|
|
|
|
}
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-04 09:06:02 +00:00
|
|
|
self.process_npc_changes(player, state);
|
2020-09-13 05:18:24 +00:00
|
|
|
}
|
|
|
|
|
2020-11-04 09:06:02 +00:00
|
|
|
pub fn process_npc_changes(&mut self, player: &Player, state: &mut SharedGameState) {
|
2020-09-13 05:18:24 +00:00
|
|
|
if !state.new_npcs.is_empty() {
|
|
|
|
for mut npc in state.new_npcs.iter_mut() {
|
|
|
|
let id = if npc.id == 0 {
|
|
|
|
self.allocate_id(npc.get_start_index())
|
2020-09-12 21:43:27 +00:00
|
|
|
} else {
|
2020-09-13 05:18:24 +00:00
|
|
|
npc.id
|
|
|
|
};
|
|
|
|
|
|
|
|
npc.id = id;
|
2020-11-04 09:06:02 +00:00
|
|
|
if npc.tsc_direction == 0 {
|
|
|
|
npc.tsc_direction = npc.direction as u16;
|
|
|
|
}
|
|
|
|
|
|
|
|
if npc.direction == Direction::FacingPlayer {
|
|
|
|
npc.direction = if npc.x < player.x {
|
|
|
|
Direction::Right
|
|
|
|
} else {
|
|
|
|
Direction::Left
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-11-04 23:25:18 +00:00
|
|
|
self.ids.insert(id);
|
2020-09-13 05:18:24 +00:00
|
|
|
self.npcs.insert(id, RefCell::new(*npc));
|
|
|
|
}
|
2020-09-12 04:43:29 +00:00
|
|
|
|
2020-09-13 05:18:24 +00:00
|
|
|
state.new_npcs.clear();
|
2020-09-12 04:43:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-04 15:37:00 +00:00
|
|
|
/// Returns true if at least one NPC with specified type is alive.
|
|
|
|
pub fn is_alive_by_type(&self, npc_type: u16) -> bool {
|
|
|
|
for npc_cell in self.npcs.values() {
|
|
|
|
let npc = npc_cell.borrow();
|
|
|
|
if npc.cond.alive() && npc.npc_type == npc_type {
|
|
|
|
return true;
|
|
|
|
}
|
2020-09-10 13:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2020-11-04 15:37:00 +00:00
|
|
|
/// Returns true if at least one NPC with specified event is alive.
|
2020-09-10 13:24:04 +00:00
|
|
|
pub fn is_alive_by_event(&self, event_num: u16) -> bool {
|
2020-09-12 04:43:29 +00:00
|
|
|
for npc_cell in self.npcs.values() {
|
|
|
|
let npc = npc_cell.borrow();
|
2020-09-10 13:24:04 +00:00
|
|
|
if npc.cond.alive() && npc.event_num == event_num {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct NPCTableEntry {
|
2020-09-06 00:37:42 +00:00
|
|
|
pub npc_flags: NPCFlag,
|
|
|
|
pub life: u16,
|
|
|
|
pub spritesheet_id: u8,
|
|
|
|
pub death_sound: u8,
|
|
|
|
pub hurt_sound: u8,
|
2020-09-09 13:06:11 +00:00
|
|
|
pub size: u8,
|
2020-09-06 00:37:42 +00:00
|
|
|
pub experience: u32,
|
|
|
|
pub damage: u32,
|
|
|
|
pub display_bounds: Rect<u8>,
|
|
|
|
pub hit_bounds: Rect<u8>,
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct NPCTable {
|
|
|
|
entries: Vec<NPCTableEntry>,
|
2020-09-23 13:10:42 +00:00
|
|
|
pub tileset_name: String,
|
2020-09-04 23:08:33 +00:00
|
|
|
pub tex_npc1_name: String,
|
|
|
|
pub tex_npc2_name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NPCTable {
|
2020-09-12 00:42:44 +00:00
|
|
|
#[allow(clippy::new_without_default)]
|
2020-09-04 23:08:33 +00:00
|
|
|
pub fn new() -> NPCTable {
|
|
|
|
NPCTable {
|
|
|
|
entries: Vec::new(),
|
2020-09-23 13:10:42 +00:00
|
|
|
tileset_name: str!("Stage/Prt0"),
|
2020-09-04 23:08:33 +00:00
|
|
|
tex_npc1_name: str!("Npc/Npc0"),
|
|
|
|
tex_npc2_name: str!("Npc/Npc0"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn load_from<R: io::Read>(mut data: R) -> GameResult<NPCTable> {
|
|
|
|
let mut table = NPCTable::new();
|
|
|
|
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
data.read_to_end(&mut buf)?;
|
|
|
|
|
|
|
|
let count = buf.len() / 0x18;
|
|
|
|
let mut f = Cursor::new(buf);
|
|
|
|
|
|
|
|
for _ in 0..count {
|
|
|
|
table.entries.push(NPCTableEntry {
|
|
|
|
npc_flags: NPCFlag(0),
|
|
|
|
life: 0,
|
|
|
|
spritesheet_id: 0,
|
|
|
|
death_sound: 0,
|
|
|
|
hurt_sound: 0,
|
2020-09-09 13:06:11 +00:00
|
|
|
size: 0,
|
2020-09-04 23:08:33 +00:00
|
|
|
experience: 0,
|
|
|
|
damage: 0,
|
|
|
|
display_bounds: Rect::new(0, 0, 0, 0),
|
|
|
|
hit_bounds: Rect::new(0, 0, 0, 0),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
|
|
|
npc.npc_flags.0 = f.read_u16::<LE>()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
|
|
|
npc.life = f.read_u16::<LE>()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
|
|
|
npc.spritesheet_id = f.read_u8()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
2020-10-27 01:05:49 +00:00
|
|
|
npc.death_sound = f.read_u8()?;
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
2020-10-27 01:05:49 +00:00
|
|
|
npc.hurt_sound = f.read_u8()?;
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
2020-09-09 13:06:11 +00:00
|
|
|
npc.size = f.read_u8()?;
|
2020-09-04 23:08:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
|
|
|
npc.experience = f.read_u32::<LE>()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
|
|
|
npc.damage = f.read_u32::<LE>()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
|
|
|
npc.hit_bounds.left = f.read_u8()?;
|
|
|
|
npc.hit_bounds.top = f.read_u8()?;
|
|
|
|
npc.hit_bounds.right = f.read_u8()?;
|
|
|
|
npc.hit_bounds.bottom = f.read_u8()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
for npc in table.entries.iter_mut() {
|
|
|
|
npc.display_bounds.left = f.read_u8()?;
|
|
|
|
npc.display_bounds.top = f.read_u8()?;
|
|
|
|
npc.display_bounds.right = f.read_u8()?;
|
|
|
|
npc.display_bounds.bottom = f.read_u8()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(table)
|
|
|
|
}
|
|
|
|
|
2020-09-06 00:37:42 +00:00
|
|
|
pub fn get_entry(&self, npc_type: u16) -> Option<&NPCTableEntry> {
|
|
|
|
self.entries.get(npc_type as usize)
|
|
|
|
}
|
|
|
|
|
2020-09-04 23:08:33 +00:00
|
|
|
pub fn get_display_bounds(&self, npc_type: u16) -> Rect<usize> {
|
|
|
|
if let Some(npc) = self.entries.get(npc_type as usize) {
|
|
|
|
Rect {
|
|
|
|
left: npc.display_bounds.left as usize * 0x200,
|
|
|
|
top: npc.display_bounds.top as usize * 0x200,
|
|
|
|
right: npc.display_bounds.right as usize * 0x200,
|
|
|
|
bottom: npc.display_bounds.bottom as usize * 0x200,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Rect { left: 0, top: 0, right: 0, bottom: 0 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_hit_bounds(&self, npc_type: u16) -> Rect<usize> {
|
|
|
|
if let Some(npc) = self.entries.get(npc_type as usize) {
|
|
|
|
Rect {
|
|
|
|
left: npc.hit_bounds.left as usize * 0x200,
|
|
|
|
top: npc.hit_bounds.top as usize * 0x200,
|
|
|
|
right: npc.hit_bounds.right as usize * 0x200,
|
|
|
|
bottom: npc.hit_bounds.bottom as usize * 0x200,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Rect { left: 0, top: 0, right: 0, bottom: 0 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_texture_name(&self, npc_type: u16) -> &str {
|
|
|
|
if let Some(npc) = self.entries.get(npc_type as usize) {
|
|
|
|
match npc.spritesheet_id {
|
2020-09-23 13:10:42 +00:00
|
|
|
2 => &self.tileset_name,
|
2020-10-27 01:05:49 +00:00
|
|
|
6 => "Fade",
|
|
|
|
8 => "ItemImage",
|
|
|
|
11 => "Arms",
|
|
|
|
12 => "ArmsImage",
|
|
|
|
14 => "StageImage",
|
|
|
|
15 => "Loading",
|
|
|
|
16 => "MyChar",
|
2020-09-23 13:10:42 +00:00
|
|
|
17 => "Bullet",
|
|
|
|
19 => "Caret",
|
2020-09-04 23:08:33 +00:00
|
|
|
20 => "Npc/NpcSym",
|
2020-09-23 13:10:42 +00:00
|
|
|
21 => &self.tex_npc1_name,
|
|
|
|
22 => &self.tex_npc2_name,
|
2020-09-04 23:08:33 +00:00
|
|
|
23 => "Npc/NpcRegu",
|
2020-10-27 01:05:49 +00:00
|
|
|
26 => "TextBox",
|
2020-09-04 23:08:33 +00:00
|
|
|
_ => "Npc/Npc0"
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
"Npc/Npc0"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|