doukutsu-rs/src/npc/mod.rs

721 lines
22 KiB
Rust
Raw Normal View History

use std::cell::RefCell;
2020-09-04 23:08:33 +00:00
use std::collections::{BTreeSet, HashMap};
use std::io;
use std::io::Cursor;
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-09-13 05:18:24 +00:00
use itertools::Itertools;
2020-08-18 16:46:07 +00:00
use crate::bitfield;
use crate::caret::CaretType;
2020-09-04 23:08:33 +00:00
use crate::common::{Condition, Rect};
use crate::common::Direction;
use crate::common::Flag;
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::ggez::{Context, GameResult};
use crate::map::NPCData;
use crate::physics::PhysicalEntity;
2020-09-05 02:09:52 +00:00
use crate::player::Player;
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-10-02 22:11:09 +00:00
use crate::npc::boss::BossNPCMap;
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-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-09-06 00:37:42 +00:00
pub mod mimiga_village;
2020-09-04 23:08:33 +00:00
pub mod misc;
pub mod misery;
2020-09-12 21:43:27 +00:00
pub mod pickups;
2020-10-02 01:30:42 +00:00
pub mod sand_zone;
pub mod toroko;
2020-10-02 01:30:42 +00:00
pub mod weapon_trail;
2020-08-18 16:46:07 +00:00
bitfield! {
#[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,
pub vel_x: isize,
pub vel_y: isize,
pub target_x: isize,
pub target_y: isize,
2020-09-12 21:43:27 +00:00
pub exp: u16,
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,
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,
pub anim_rect: Rect<usize>,
}
2020-10-02 01:30:42 +00:00
static PARTICLE_NPCS: [u16; 5] = [1, 4, 73, 129, 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 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,
target_x: 0,
target_y: 0,
exp: 0,
size: 0,
shock: 0,
life: 0,
damage: 0,
cond: Condition(0),
flags: Flag(0),
npc_flags: NPCFlag(0),
direction: Direction::Left,
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-09-30 03:11:25 +00:00
impl GameEntity<(&mut Player, &HashMap<u16, RefCell<NPC>>, &mut Stage)> for NPC {
fn tick(&mut self, state: &mut SharedGameState, (player, map, stage): (&mut Player, &HashMap<u16, RefCell<NPC>>, &mut Stage)) -> GameResult {
2020-09-04 23:08:33 +00:00
match self.npc_type {
2020-09-13 00:10:49 +00:00
0 => { self.tick_n000_null() }
2020-09-12 21:43:27 +00:00
1 => { self.tick_n001_experience(state) }
2020-09-10 11:29:25 +00:00
2 => { self.tick_n002_behemoth(state) }
2020-09-13 05:18:24 +00:00
3 => { self.tick_n003_dead_enemy() }
4 => { self.tick_n004_smoke(state) }
2020-09-10 11:29:25 +00:00
5 => { self.tick_n005_green_critter(state, player) }
2020-09-13 05:57:03 +00:00
6 => { self.tick_n006_green_beetle(state) }
2020-09-10 11:29:25 +00:00
7 => { self.tick_n007_basil(state, player) }
2020-09-13 05:57:03 +00:00
8 => { self.tick_n008_blue_beetle(state, player) }
2020-09-30 03:11:25 +00:00
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) }
2020-10-02 19:27:55 +00:00
13 => { self.tick_n013_forcefield(state) }
14 => { self.tick_n014_key(state) }
2020-09-13 00:10:49 +00:00
15 => { self.tick_n015_chest_closed(state) }
2020-09-06 00:37:42 +00:00
16 => { self.tick_n016_save_point(state) }
17 => { self.tick_n017_health_refill(state) }
18 => { self.tick_n018_door(state) }
2020-10-02 18:37:56 +00:00
19 => { self.tick_n019_balrog_bust_in(state) }
2020-09-06 00:37:42 +00:00
20 => { self.tick_n020_computer(state) }
21 => { self.tick_n021_chest_open(state) }
22 => { self.tick_n022_teleporter(state) }
27 => { self.tick_n027_death_trap(state) }
30 => { self.tick_n030_gunsmith(state) }
32 => { self.tick_n032_life_capsule(state) }
34 => { self.tick_n034_bed(state) }
37 => { self.tick_n037_sign(state) }
38 => { self.tick_n038_fireplace(state) }
39 => { self.tick_n039_save_sign(state) }
41 => { self.tick_n041_busted_door(state) }
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) }
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) }
2020-09-19 13:20:06 +00:00
63 => { self.tick_n063_toroko_stick(state) }
2020-09-09 13:28:58 +00:00
64 => { self.tick_n064_first_cave_critter(state, player) }
2020-09-09 16:25:39 +00:00
65 => { self.tick_n065_first_cave_bat(state, player) }
66 => { self.tick_n066_misery_bubble(state, map) }
67 => { self.tick_n067_misery_floating(state) }
2020-09-23 19:43:50 +00:00
68 => { self.tick_n068_balrog_running(state, player) }
2020-09-23 13:10:42 +00:00
69 => { self.tick_n069_pignon(state) }
2020-09-06 00:37:42 +00:00
70 => { self.tick_n070_sparkle(state) }
2020-09-09 22:21:14 +00:00
71 => { self.tick_n071_chinfish(state) }
2020-09-23 13:10:42 +00:00
72 => { self.tick_n072_sprinkler(state, player) }
73 => { self.tick_n073_water_droplet(state, stage) }
2020-09-06 00:37:42 +00:00
74 => { self.tick_n074_jack(state) }
75 => { self.tick_n075_kanpachi(state, player) }
2020-09-23 13:10:42 +00:00
76 => { self.tick_n076_flowers() }
2020-09-06 00:37:42 +00:00
77 => { self.tick_n077_yamashita(state) }
78 => { self.tick_n078_pot(state) }
79 => { self.tick_n079_mahin(state, player) }
2020-10-02 01:30:42 +00:00
129 => { self.tick_n129_fireball_snake_trail(state) }
2020-09-09 16:25:39 +00:00
211 => { self.tick_n211_small_spikes(state) }
2020-09-04 23:08:33 +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
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-09-13 00:21:12 +00:00
(((self.x - off_x) / 0x200) - (frame.x / 0x200)) as f32 + shock,
2020-09-04 23:08:33 +00:00
(((self.y - self.display_bounds.top as isize) / 0x200) - (frame.y / 0x200)) as f32,
&self.anim_rect,
);
batch.draw(ctx)?;
Ok(())
}
}
impl PhysicalEntity for NPC {
#[inline(always)]
2020-10-01 03:06:18 +00:00
fn x(&self) -> isize { self.x }
#[inline(always)]
2020-10-01 03:06:18 +00:00
fn y(&self) -> isize { self.y }
#[inline(always)]
2020-10-01 03:06:18 +00:00
fn vel_x(&self) -> isize { self.vel_x }
#[inline(always)]
2020-10-01 03:06:18 +00:00
fn vel_y(&self) -> isize { self.vel_y }
#[inline(always)]
2020-10-01 03:06:18 +00:00
fn hit_rect_size(&self) -> usize { if self.size >= 3 { 3 } else { 2 } }
#[inline(always)]
fn offset_x(&self) -> isize { if self.size >= 3 { -0x1000 } else { 0 } }
#[inline(always)]
fn offset_y(&self) -> isize { if self.size >= 3 { -0x1000 } else { 0 } }
#[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
}
#[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 {
/// A sorted pool of used IDs to used to iterate over NPCs in order, as original game does.
pub npc_ids: BTreeSet<u16>,
/// Do not iterate over this directly outside render pipeline.
pub npcs: HashMap<u16, RefCell<NPC>>,
2020-10-02 22:11:09 +00:00
/// NPCMap but for bosses and of static size.
pub boss_map: BossNPCMap,
2020-09-04 23:08:33 +00:00
}
impl NPCMap {
#[allow(clippy::new_without_default)]
pub fn new() -> NPCMap {
NPCMap {
npc_ids: BTreeSet::new(),
npcs: HashMap::with_capacity(256),
2020-10-02 22:11:09 +00:00
boss_map: BossNPCMap::new(),
2020-09-04 23:08:33 +00:00
}
}
pub fn clear(&mut self) {
self.npc_ids.clear();
self.npcs.clear();
}
pub fn create_npc_from_data(&mut self, table: &NPCTable, data: &NPCData) -> &mut NPC {
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) }
};
let npc_flags = NPCFlag(data.flags | flags.0);
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,
target_x: 0,
target_y: 0,
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,
size,
life,
2020-09-09 16:25:39 +00:00
damage,
2020-09-04 23:08:33 +00:00
cond: Condition(0x00),
flags: Flag(0),
2020-09-06 00:37:42 +00:00
direction: if npc_flags.spawn_facing_right() { Direction::Right } else { Direction::Left },
npc_flags,
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),
};
self.npc_ids.insert(data.id);
self.npcs.insert(data.id, RefCell::new(npc));
2020-09-04 23:08:33 +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) }
None => { (1, 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,
target_x: 0,
target_y: 0,
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 },
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),
}
}
pub fn garbage_collect(&mut self) {
2020-09-13 05:18:24 +00:00
let dead_npcs = self.npcs.iter().filter_map(|(&id, npc_cell)| {
if !npc_cell.borrow().cond.alive() {
Some(id)
} else {
None
}
}).collect_vec();
for npc_id in dead_npcs.iter() {
self.npc_ids.remove(npc_id);
self.npcs.remove(npc_id);
}
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) {
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-12 21:43:27 +00:00
pub fn allocate_id(&mut self, start: u16) -> u16 {
for i in start..(u16::MAX) {
if !self.npc_ids.contains(&i) {
return i;
}
}
2020-09-30 03:11:25 +00:00
0xffff
}
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) {
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-13 05:18:24 +00:00
state.new_npcs.push(npc);
}
state.create_caret(x, y, CaretType::Explosion, Direction::Left);
}
2020-09-13 05:18:24 +00:00
pub fn process_dead_npcs(&mut self, list: &[u16], state: &mut SharedGameState) {
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-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 21:43:27 +00:00
if npc.exp != 0 {
//if state.game_rng.range(0..4) == 0 {
// health
//} else {
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
};
2020-09-13 05:18:24 +00:00
let mut xp_npc = NPCMap::create_npc(1, &state.npc_table);
2020-09-12 21:43:27 +00:00
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;
2020-09-13 05:18:24 +00:00
state.new_npcs.push(xp_npc);
2020-09-12 21:43:27 +00:00
}
//}
}
state.game_flags.set(npc.flag_num as usize, true);
// todo vanish / show damage
npc.cond.set_alive(false);
}
}
2020-09-13 05:18:24 +00:00
self.process_npc_changes(state);
}
pub fn process_npc_changes(&mut self, state: &mut SharedGameState) {
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;
self.npc_ids.insert(id);
self.npcs.insert(id, RefCell::new(*npc));
}
2020-09-13 05:18:24 +00:00
state.new_npcs.clear();
}
}
pub fn is_alive(&self, npc_id: u16) -> bool {
if let Some(npc_cell) = self.npcs.get(&npc_id) {
return npc_cell.borrow().cond.alive();
}
false
}
pub fn is_alive_by_event(&self, event_num: u16) -> bool {
for npc_cell in self.npcs.values() {
let npc = npc_cell.borrow();
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,
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 {
#[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,
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() {
npc.hurt_sound = f.read_u8()?;
2020-09-04 23:08:33 +00:00
}
for npc in table.entries.iter_mut() {
npc.death_sound = f.read_u8()?;
2020-09-04 23:08:33 +00:00
}
for npc in table.entries.iter_mut() {
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,
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",
_ => "Npc/Npc0"
}
} else {
"Npc/Npc0"
}
}
}