2020-08-18 16:46:07 +00:00
|
|
|
use std::io::{Cursor, Read};
|
|
|
|
use std::str::from_utf8;
|
|
|
|
|
2020-08-19 01:59:16 +00:00
|
|
|
use byteorder::ReadBytesExt;
|
2022-01-06 01:11:17 +00:00
|
|
|
use byteorder::LE;
|
2020-08-18 16:46:07 +00:00
|
|
|
use log::info;
|
|
|
|
|
2022-01-05 04:50:16 +00:00
|
|
|
use crate::common::Color;
|
2020-09-25 17:14:52 +00:00
|
|
|
use crate::encoding::read_cur_shift_jis;
|
|
|
|
use crate::engine_constants::EngineConstants;
|
2021-01-27 18:20:47 +00:00
|
|
|
use crate::framework::context::Context;
|
2021-06-16 15:19:26 +00:00
|
|
|
use crate::framework::error::GameError::ResourceLoadError;
|
2021-01-27 18:20:47 +00:00
|
|
|
use crate::framework::error::GameResult;
|
|
|
|
use crate::framework::filesystem;
|
2020-09-03 12:19:46 +00:00
|
|
|
use crate::map::{Map, NPCData};
|
2021-10-15 14:36:05 +00:00
|
|
|
use crate::scripting::tsc::text_script::TextScript;
|
2022-02-10 07:54:20 +00:00
|
|
|
use crate::GameError;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2020-08-19 01:59:16 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
|
|
pub struct NpcType {
|
|
|
|
name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Clone for NpcType {
|
|
|
|
fn clone(&self) -> Self {
|
2021-06-16 15:19:26 +00:00
|
|
|
Self { name: self.name.clone() }
|
2020-08-19 01:59:16 +00:00
|
|
|
}
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl NpcType {
|
2020-08-19 01:59:16 +00:00
|
|
|
pub fn new(name: &str) -> Self {
|
2021-06-16 15:19:26 +00:00
|
|
|
Self { name: name.to_owned() }
|
2020-08-19 01:59:16 +00:00
|
|
|
}
|
|
|
|
|
2020-08-18 16:46:07 +00:00
|
|
|
pub fn filename(&self) -> String {
|
2020-08-19 01:59:16 +00:00
|
|
|
["Npc", &self.name].join("")
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
|
|
pub struct Tileset {
|
2021-06-20 19:41:09 +00:00
|
|
|
pub(crate) name: String,
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Clone for Tileset {
|
|
|
|
fn clone(&self) -> Self {
|
2021-06-16 15:19:26 +00:00
|
|
|
Self { name: self.name.clone() }
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Tileset {
|
2020-08-19 00:55:21 +00:00
|
|
|
pub fn new(name: &str) -> Self {
|
2021-06-16 15:19:26 +00:00
|
|
|
Self { name: name.to_owned() }
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn filename(&self) -> String {
|
|
|
|
["Prt", &self.name].join("")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn orig_width(&self) -> usize {
|
|
|
|
// todo: move to json or something?
|
|
|
|
|
|
|
|
if self.name == "Labo" {
|
|
|
|
return 128;
|
|
|
|
}
|
|
|
|
256
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
|
|
pub struct Background {
|
|
|
|
name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Clone for Background {
|
|
|
|
fn clone(&self) -> Self {
|
2021-06-16 15:19:26 +00:00
|
|
|
Self { name: self.name.clone() }
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Background {
|
2020-08-19 00:55:21 +00:00
|
|
|
pub fn new(name: &str) -> Self {
|
2021-06-16 15:19:26 +00:00
|
|
|
Self { name: name.to_owned() }
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
2020-11-27 18:19:12 +00:00
|
|
|
pub fn name(&self) -> &str {
|
|
|
|
&self.name
|
|
|
|
}
|
|
|
|
|
2020-08-18 16:46:07 +00:00
|
|
|
pub fn filename(&self) -> String {
|
|
|
|
self.name.to_owned()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
|
|
|
pub enum BackgroundType {
|
2021-06-16 15:19:26 +00:00
|
|
|
TiledStatic,
|
|
|
|
TiledParallax,
|
|
|
|
Tiled,
|
|
|
|
/// Used in Core room, renders water in front of tilemap which also affects player's physics.
|
2020-08-18 16:46:07 +00:00
|
|
|
Water,
|
|
|
|
Black,
|
2021-06-16 15:19:26 +00:00
|
|
|
/// Used in Ironhead fight. Affects physics of XP/heart/missile drops.
|
|
|
|
Scrolling,
|
|
|
|
/// Same as Outside, except it affects physics of XP/heart/missile drops.
|
2020-08-18 16:46:07 +00:00
|
|
|
OutsideWind,
|
|
|
|
Outside,
|
2021-06-16 15:19:26 +00:00
|
|
|
/// Present in CS+KAGE, Seems to be a clone of Outside, isn't used anywhere and has unknown purpose
|
|
|
|
OutsideUnknown,
|
|
|
|
/// Used by CS+KAGE in waterway, it's just TiledParallax with bkCircle2 drawn behind water
|
|
|
|
Waterway,
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
impl From<u8> for BackgroundType {
|
|
|
|
fn from(val: u8) -> Self {
|
|
|
|
match val {
|
2021-06-16 15:19:26 +00:00
|
|
|
0 => Self::TiledStatic,
|
|
|
|
1 => Self::TiledParallax,
|
|
|
|
2 => Self::Tiled,
|
|
|
|
3 => Self::Water,
|
|
|
|
4 => Self::Black,
|
|
|
|
5 => Self::Scrolling,
|
|
|
|
6 => Self::OutsideWind,
|
|
|
|
7 => Self::Outside,
|
|
|
|
8 => Self::OutsideUnknown,
|
|
|
|
9 => Self::Waterway,
|
|
|
|
_ => {
|
2021-06-27 01:06:56 +00:00
|
|
|
// log::warn!("Unknown background type: {}", val);
|
2021-06-16 15:19:26 +00:00
|
|
|
Self::Black
|
|
|
|
}
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
|
|
pub enum PxPackScroll {
|
|
|
|
Normal,
|
|
|
|
ThreeQuarters,
|
|
|
|
Half,
|
|
|
|
Quarter,
|
|
|
|
Eighth,
|
|
|
|
Zero,
|
|
|
|
HThreeQuarters,
|
|
|
|
HHalf,
|
|
|
|
HQuarter,
|
|
|
|
V0Half,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<u8> for PxPackScroll {
|
|
|
|
fn from(val: u8) -> Self {
|
|
|
|
match val {
|
|
|
|
0 => PxPackScroll::Normal,
|
|
|
|
1 => PxPackScroll::ThreeQuarters,
|
|
|
|
2 => PxPackScroll::Half,
|
|
|
|
3 => PxPackScroll::Quarter,
|
|
|
|
4 => PxPackScroll::Eighth,
|
|
|
|
5 => PxPackScroll::Zero,
|
|
|
|
6 => PxPackScroll::HThreeQuarters,
|
|
|
|
7 => PxPackScroll::HHalf,
|
|
|
|
8 => PxPackScroll::HQuarter,
|
|
|
|
9 => PxPackScroll::V0Half,
|
|
|
|
_ => PxPackScroll::Normal,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PxPackScroll {
|
|
|
|
pub fn transform_camera_pos(self, x: f32, y: f32) -> (f32, f32) {
|
|
|
|
match self {
|
|
|
|
PxPackScroll::Normal => (x, y),
|
|
|
|
PxPackScroll::ThreeQuarters => (x * 0.75, y * 0.75),
|
|
|
|
PxPackScroll::Half => (x * 0.5, y * 0.5),
|
|
|
|
PxPackScroll::Quarter => (x * 0.25, y * 0.25),
|
|
|
|
PxPackScroll::Eighth => (x * 0.125, y * 0.125),
|
|
|
|
PxPackScroll::Zero => (0.0, 0.0),
|
|
|
|
PxPackScroll::HThreeQuarters => (x * 0.75, y),
|
|
|
|
PxPackScroll::HHalf => (x * 0.5, y),
|
|
|
|
PxPackScroll::HQuarter => (x * 0.25, y),
|
2022-01-05 04:50:16 +00:00
|
|
|
PxPackScroll::V0Half => (x, y), // ???
|
2021-06-27 01:06:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct PxPackStageData {
|
|
|
|
pub tileset_fg: String,
|
|
|
|
pub tileset_mg: String,
|
|
|
|
pub tileset_bg: String,
|
|
|
|
pub scroll_fg: PxPackScroll,
|
|
|
|
pub scroll_mg: PxPackScroll,
|
|
|
|
pub scroll_bg: PxPackScroll,
|
|
|
|
pub size_fg: (u16, u16),
|
|
|
|
pub size_mg: (u16, u16),
|
|
|
|
pub size_bg: (u16, u16),
|
|
|
|
pub offset_mg: u32,
|
|
|
|
pub offset_bg: u32,
|
|
|
|
}
|
|
|
|
|
2020-08-18 16:46:07 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct StageData {
|
|
|
|
pub name: String,
|
2022-03-15 01:54:03 +00:00
|
|
|
pub name_jp: String,
|
2020-08-18 16:46:07 +00:00
|
|
|
pub map: String,
|
2021-06-27 01:06:56 +00:00
|
|
|
pub boss_no: u8,
|
2020-08-18 16:46:07 +00:00
|
|
|
pub tileset: Tileset,
|
2021-06-27 01:06:56 +00:00
|
|
|
pub pxpack_data: Option<PxPackStageData>,
|
2020-08-18 16:46:07 +00:00
|
|
|
pub background: Background,
|
|
|
|
pub background_type: BackgroundType,
|
2021-06-27 01:06:56 +00:00
|
|
|
pub background_color: Color,
|
2020-08-19 01:59:16 +00:00
|
|
|
pub npc1: NpcType,
|
|
|
|
pub npc2: NpcType,
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Clone for StageData {
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
StageData {
|
|
|
|
name: self.name.clone(),
|
2022-03-15 01:54:03 +00:00
|
|
|
name_jp: self.name_jp.clone(),
|
2020-08-18 16:46:07 +00:00
|
|
|
map: self.map.clone(),
|
|
|
|
boss_no: self.boss_no,
|
|
|
|
tileset: self.tileset.clone(),
|
2021-06-27 01:06:56 +00:00
|
|
|
pxpack_data: self.pxpack_data.clone(),
|
2020-08-18 16:46:07 +00:00
|
|
|
background: self.background.clone(),
|
|
|
|
background_type: self.background_type,
|
2021-06-27 01:06:56 +00:00
|
|
|
background_color: self.background_color,
|
2020-08-19 01:59:16 +00:00
|
|
|
npc1: self.npc1.clone(),
|
|
|
|
npc2: self.npc2.clone(),
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 01:59:16 +00:00
|
|
|
const NXENGINE_BACKDROPS: [&str; 15] = [
|
2021-06-16 15:19:26 +00:00
|
|
|
"bk0",
|
|
|
|
"bkBlue",
|
|
|
|
"bkGreen",
|
|
|
|
"bkBlack",
|
|
|
|
"bkGard",
|
|
|
|
"bkMaze",
|
|
|
|
"bkGray",
|
|
|
|
"bkRed",
|
|
|
|
"bkWater",
|
|
|
|
"bkMoon",
|
|
|
|
"bkFog",
|
|
|
|
"bkFall",
|
|
|
|
"bkLight",
|
|
|
|
"bkSunset",
|
|
|
|
"bkHellish",
|
2020-08-19 01:59:16 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
const NXENGINE_TILESETS: [&str; 22] = [
|
2021-06-16 15:19:26 +00:00
|
|
|
"0", "Pens", "Eggs", "EggX", "EggIn", "Store", "Weed", "Barr", "Maze", "Sand", "Mimi", "Cave", "River", "Gard",
|
|
|
|
"Almond", "Oside", "Cent", "Jail", "White", "Fall", "Hell", "Labo",
|
2020-08-19 01:59:16 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
const NXENGINE_NPCS: [&str; 34] = [
|
2021-06-16 15:19:26 +00:00
|
|
|
"Guest", "0", "Eggs1", "Ravil", "Weed", "Maze", "Sand", "Omg", "Cemet", "Bllg", "Plant", "Frog", "Curly", "Stream",
|
|
|
|
"IronH", "Toro", "X", "Dark", "Almo1", "Eggs2", "TwinD", "Moon", "Cent", "Heri", "Red", "Miza", "Dr", "Almo2",
|
|
|
|
"Kings", "Hell", "Press", "Priest", "Ballos", "Island",
|
2020-08-19 01:59:16 +00:00
|
|
|
];
|
|
|
|
|
2020-09-11 16:30:18 +00:00
|
|
|
fn zero_index(s: &[u8]) -> usize {
|
|
|
|
s.iter().position(|&c| c == b'\0').unwrap_or(s.len())
|
|
|
|
}
|
|
|
|
|
2020-09-13 03:30:56 +00:00
|
|
|
fn from_shift_jis(s: &[u8]) -> String {
|
|
|
|
let mut cursor = Cursor::new(s);
|
|
|
|
let mut chars = Vec::new();
|
|
|
|
let mut bytes = s.len() as u32;
|
|
|
|
|
|
|
|
while bytes > 0 {
|
|
|
|
let (consumed, chr) = read_cur_shift_jis(&mut cursor, bytes);
|
|
|
|
chars.push(chr);
|
|
|
|
bytes -= consumed;
|
|
|
|
}
|
|
|
|
|
|
|
|
chars.iter().collect()
|
|
|
|
}
|
|
|
|
|
2022-03-15 01:54:03 +00:00
|
|
|
fn from_csplus_stagetbl(s: &[u8], is_switch: bool) -> String {
|
|
|
|
if is_switch {
|
|
|
|
from_utf8(s).unwrap_or("").trim_matches('\0').to_string()
|
|
|
|
} else {
|
|
|
|
from_shift_jis(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-18 16:46:07 +00:00
|
|
|
impl StageData {
|
2022-03-15 01:54:03 +00:00
|
|
|
pub fn load_stage_table(ctx: &mut Context, roots: &Vec<String>, is_switch: bool) -> GameResult<Vec<Self>> {
|
2022-02-10 07:54:20 +00:00
|
|
|
let stage_tbl_path = "/stage.tbl";
|
|
|
|
let stage_sect_path = "/stage.sect";
|
|
|
|
let mrmap_bin_path = "/mrmap.bin";
|
|
|
|
let stage_dat_path = "/stage.dat";
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2022-02-10 23:02:45 +00:00
|
|
|
if filesystem::exists_find(ctx, roots, stage_tbl_path) {
|
2020-08-19 01:59:16 +00:00
|
|
|
// Cave Story+ stage table.
|
2022-02-10 23:02:45 +00:00
|
|
|
// Mod stage.tbl expects to overwrite from base stage.tbl
|
2020-08-18 16:46:07 +00:00
|
|
|
let mut stages = Vec::new();
|
|
|
|
|
2022-02-10 23:02:45 +00:00
|
|
|
for path in roots.iter().rev() {
|
|
|
|
if let Ok(mut file) = filesystem::open(ctx, [path, stage_tbl_path].join("")) {
|
|
|
|
info!("Loading Cave Story+ stage table from {}", &path);
|
|
|
|
|
|
|
|
let mut new_stages = Vec::new();
|
|
|
|
|
|
|
|
let mut data = Vec::new();
|
|
|
|
file.read_to_end(&mut data)?;
|
|
|
|
|
|
|
|
let count = data.len() / 0xe5;
|
|
|
|
let mut f = Cursor::new(data);
|
|
|
|
for _ in 0..count {
|
|
|
|
let mut ts_buf = vec![0u8; 0x20];
|
|
|
|
let mut map_buf = vec![0u8; 0x20];
|
|
|
|
let mut back_buf = vec![0u8; 0x20];
|
|
|
|
let mut npc1_buf = vec![0u8; 0x20];
|
|
|
|
let mut npc2_buf = vec![0u8; 0x20];
|
|
|
|
let mut name_jap_buf = vec![0u8; 0x20];
|
|
|
|
let mut name_buf = vec![0u8; 0x20];
|
|
|
|
|
|
|
|
f.read_exact(&mut ts_buf)?;
|
|
|
|
f.read_exact(&mut map_buf)?;
|
|
|
|
let bg_type = f.read_u32::<LE>()? as u8;
|
|
|
|
f.read_exact(&mut back_buf)?;
|
|
|
|
f.read_exact(&mut npc1_buf)?;
|
|
|
|
f.read_exact(&mut npc2_buf)?;
|
|
|
|
let boss_no = f.read_u8()?;
|
|
|
|
f.read_exact(&mut name_jap_buf)?;
|
|
|
|
f.read_exact(&mut name_buf)?;
|
|
|
|
|
2022-03-15 01:54:03 +00:00
|
|
|
let tileset = from_csplus_stagetbl(&ts_buf[0..zero_index(&ts_buf)], is_switch);
|
|
|
|
let map = from_csplus_stagetbl(&map_buf[0..zero_index(&map_buf)], is_switch);
|
|
|
|
let background = from_csplus_stagetbl(&back_buf[0..zero_index(&back_buf)], is_switch);
|
|
|
|
let npc1 = from_csplus_stagetbl(&npc1_buf[0..zero_index(&npc1_buf)], is_switch);
|
|
|
|
let npc2 = from_csplus_stagetbl(&npc2_buf[0..zero_index(&npc2_buf)], is_switch);
|
|
|
|
let name = from_csplus_stagetbl(&name_buf[0..zero_index(&name_buf)], is_switch);
|
|
|
|
let name_jp = from_csplus_stagetbl(&name_jap_buf[0..zero_index(&name_jap_buf)], is_switch);
|
2022-02-10 23:02:45 +00:00
|
|
|
|
|
|
|
let stage = StageData {
|
|
|
|
name: name.clone(),
|
2022-03-15 01:54:03 +00:00
|
|
|
name_jp: name_jp.clone(),
|
2022-02-10 23:02:45 +00:00
|
|
|
map: map.clone(),
|
|
|
|
boss_no,
|
|
|
|
tileset: Tileset::new(&tileset),
|
|
|
|
pxpack_data: None,
|
|
|
|
background: Background::new(&background),
|
|
|
|
background_type: BackgroundType::from(bg_type),
|
|
|
|
background_color: Color::from_rgb(0, 0, 32),
|
|
|
|
npc1: NpcType::new(&npc1),
|
|
|
|
npc2: NpcType::new(&npc2),
|
|
|
|
};
|
|
|
|
new_stages.push(stage);
|
|
|
|
}
|
|
|
|
|
|
|
|
if new_stages.len() >= stages.len() {
|
|
|
|
stages = new_stages;
|
|
|
|
} else {
|
|
|
|
let _ = stages.splice(0..new_stages.len(), new_stages);
|
|
|
|
}
|
|
|
|
}
|
2020-10-07 14:08:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(stages);
|
2022-02-10 07:54:20 +00:00
|
|
|
} else if let Ok(mut file) = filesystem::open_find(ctx, roots, stage_sect_path) {
|
2020-10-07 14:08:12 +00:00
|
|
|
// Cave Story freeware executable dump.
|
|
|
|
let mut stages = Vec::new();
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
info!("Loading Cave Story freeware exe dump stage table from {}", &stage_sect_path);
|
2020-10-07 14:08:12 +00:00
|
|
|
|
|
|
|
let mut data = Vec::new();
|
2022-02-10 07:54:20 +00:00
|
|
|
file.read_to_end(&mut data)?;
|
2020-10-07 14:08:12 +00:00
|
|
|
|
|
|
|
let count = data.len() / 0xc8;
|
|
|
|
let mut f = Cursor::new(data);
|
|
|
|
for _ in 0..count {
|
|
|
|
let mut ts_buf = vec![0u8; 0x20];
|
|
|
|
let mut map_buf = vec![0u8; 0x20];
|
|
|
|
let mut back_buf = vec![0u8; 0x20];
|
|
|
|
let mut npc1_buf = vec![0u8; 0x20];
|
|
|
|
let mut npc2_buf = vec![0u8; 0x20];
|
|
|
|
let mut name_buf = vec![0u8; 0x20];
|
|
|
|
|
|
|
|
f.read_exact(&mut ts_buf)?;
|
|
|
|
f.read_exact(&mut map_buf)?;
|
2021-06-27 01:06:56 +00:00
|
|
|
let bg_type = f.read_u32::<LE>()? as u8;
|
2020-10-07 14:08:12 +00:00
|
|
|
f.read_exact(&mut back_buf)?;
|
|
|
|
f.read_exact(&mut npc1_buf)?;
|
|
|
|
f.read_exact(&mut npc2_buf)?;
|
2021-06-27 01:06:56 +00:00
|
|
|
let boss_no = f.read_u8()?;
|
2020-10-07 14:08:12 +00:00
|
|
|
f.read_exact(&mut name_buf)?;
|
|
|
|
// alignment
|
2021-06-27 01:06:56 +00:00
|
|
|
{
|
|
|
|
let mut lol = [0u8; 3];
|
|
|
|
let _ = f.read(&mut lol)?;
|
|
|
|
}
|
2020-10-07 14:08:12 +00:00
|
|
|
|
|
|
|
let tileset = from_shift_jis(&ts_buf[0..zero_index(&ts_buf)]);
|
2020-09-13 03:30:56 +00:00
|
|
|
let map = from_shift_jis(&map_buf[0..zero_index(&map_buf)]);
|
|
|
|
let background = from_shift_jis(&back_buf[0..zero_index(&back_buf)]);
|
|
|
|
let npc1 = from_shift_jis(&npc1_buf[0..zero_index(&npc1_buf)]);
|
|
|
|
let npc2 = from_shift_jis(&npc2_buf[0..zero_index(&npc2_buf)]);
|
|
|
|
let name = from_shift_jis(&name_buf[0..zero_index(&name_buf)]);
|
2020-08-19 00:55:21 +00:00
|
|
|
|
2020-09-11 22:52:20 +00:00
|
|
|
let stage = StageData {
|
2020-08-19 00:55:21 +00:00
|
|
|
name: name.clone(),
|
2022-03-15 01:54:03 +00:00
|
|
|
name_jp: name.clone(),
|
2020-08-19 00:55:21 +00:00
|
|
|
map: map.clone(),
|
|
|
|
boss_no,
|
|
|
|
tileset: Tileset::new(&tileset),
|
2021-06-27 01:06:56 +00:00
|
|
|
pxpack_data: None,
|
2020-08-19 00:55:21 +00:00
|
|
|
background: Background::new(&background),
|
2021-06-27 01:06:56 +00:00
|
|
|
background_type: BackgroundType::from(bg_type),
|
|
|
|
background_color: Color::from_rgb(0, 0, 32),
|
2020-08-19 01:59:16 +00:00
|
|
|
npc1: NpcType::new(&npc1),
|
|
|
|
npc2: NpcType::new(&npc2),
|
2020-08-19 00:55:21 +00:00
|
|
|
};
|
|
|
|
stages.push(stage);
|
|
|
|
}
|
2020-08-18 16:46:07 +00:00
|
|
|
|
|
|
|
return Ok(stages);
|
2022-02-10 07:54:20 +00:00
|
|
|
} else if let Ok(mut file) = filesystem::open_find(ctx, roots, mrmap_bin_path) {
|
2021-06-27 01:06:56 +00:00
|
|
|
// Moustache Rider stage table
|
2020-08-18 16:46:07 +00:00
|
|
|
let mut stages = Vec::new();
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
info!("Loading Moustache Rider stage table from {}", &mrmap_bin_path);
|
2020-08-18 16:46:07 +00:00
|
|
|
|
|
|
|
let mut data = Vec::new();
|
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
let count = file.read_u32::<LE>()?;
|
|
|
|
file.read_to_end(&mut data)?;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
|
|
|
if data.len() < count as usize * 0x74 {
|
2021-06-16 15:19:26 +00:00
|
|
|
return Err(ResourceLoadError(
|
|
|
|
"Specified stage table size is bigger than actual number of entries.".to_string(),
|
|
|
|
));
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut f = Cursor::new(data);
|
|
|
|
for _ in 0..count {
|
|
|
|
let mut ts_buf = vec![0u8; 0x10];
|
2020-08-19 00:55:21 +00:00
|
|
|
let mut map_buf = vec![0u8; 0x10];
|
2020-08-18 16:46:07 +00:00
|
|
|
let mut back_buf = vec![0u8; 0x10];
|
2020-08-19 01:59:16 +00:00
|
|
|
let mut npc1_buf = vec![0u8; 0x10];
|
|
|
|
let mut npc2_buf = vec![0u8; 0x10];
|
2020-08-19 00:55:21 +00:00
|
|
|
let mut name_buf = vec![0u8; 0x22];
|
2020-08-18 16:46:07 +00:00
|
|
|
|
|
|
|
f.read_exact(&mut ts_buf)?;
|
|
|
|
f.read_exact(&mut map_buf)?;
|
2021-06-27 01:06:56 +00:00
|
|
|
let bg_type = f.read_u8()?;
|
2020-08-18 16:46:07 +00:00
|
|
|
f.read_exact(&mut back_buf)?;
|
2020-08-19 01:59:16 +00:00
|
|
|
f.read_exact(&mut npc1_buf)?;
|
|
|
|
f.read_exact(&mut npc2_buf)?;
|
2021-06-27 01:06:56 +00:00
|
|
|
let boss_no = f.read_u8()?;
|
2020-08-18 16:46:07 +00:00
|
|
|
f.read_exact(&mut name_buf)?;
|
|
|
|
|
2020-09-13 03:30:56 +00:00
|
|
|
let tileset = from_shift_jis(&ts_buf[0..zero_index(&ts_buf)]);
|
|
|
|
let map = from_shift_jis(&map_buf[0..zero_index(&map_buf)]);
|
|
|
|
let background = from_shift_jis(&back_buf[0..zero_index(&back_buf)]);
|
|
|
|
let npc1 = from_shift_jis(&npc1_buf[0..zero_index(&npc1_buf)]);
|
|
|
|
let npc2 = from_shift_jis(&npc2_buf[0..zero_index(&npc2_buf)]);
|
|
|
|
let name = from_shift_jis(&name_buf[0..zero_index(&name_buf)]);
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2020-09-11 22:52:20 +00:00
|
|
|
let stage = StageData {
|
2020-08-18 16:46:07 +00:00
|
|
|
name: name.clone(),
|
2022-03-15 01:54:03 +00:00
|
|
|
name_jp: name.clone(),
|
2020-08-18 16:46:07 +00:00
|
|
|
map: map.clone(),
|
|
|
|
boss_no,
|
|
|
|
tileset: Tileset::new(&tileset),
|
2021-06-27 01:06:56 +00:00
|
|
|
pxpack_data: None,
|
2020-08-18 16:46:07 +00:00
|
|
|
background: Background::new(&background),
|
2021-06-27 01:06:56 +00:00
|
|
|
background_type: BackgroundType::from(bg_type),
|
|
|
|
background_color: Color::from_rgb(0, 0, 32),
|
2020-08-19 01:59:16 +00:00
|
|
|
npc1: NpcType::new(&npc1),
|
|
|
|
npc2: NpcType::new(&npc2),
|
|
|
|
};
|
|
|
|
stages.push(stage);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(stages);
|
2022-02-10 07:54:20 +00:00
|
|
|
} else if let Ok(mut file) = filesystem::open_find(ctx, roots, stage_dat_path) {
|
2020-08-19 01:59:16 +00:00
|
|
|
let mut stages = Vec::new();
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
info!("Loading NXEngine stage table from {}", &stage_dat_path);
|
2020-08-19 01:59:16 +00:00
|
|
|
|
|
|
|
let mut data = Vec::new();
|
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
let count = file.read_u8()? as usize;
|
|
|
|
file.read_to_end(&mut data)?;
|
2020-08-19 01:59:16 +00:00
|
|
|
|
|
|
|
if data.len() < count * 0x49 {
|
2021-06-16 15:19:26 +00:00
|
|
|
return Err(ResourceLoadError(
|
|
|
|
"Specified stage table size is bigger than actual number of entries.".to_string(),
|
|
|
|
));
|
2020-08-19 01:59:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut f = Cursor::new(data);
|
|
|
|
for _ in 0..count {
|
|
|
|
let mut map_buf = vec![0u8; 0x20];
|
|
|
|
let mut name_buf = vec![0u8; 0x23];
|
|
|
|
|
|
|
|
f.read_exact(&mut map_buf)?;
|
|
|
|
f.read_exact(&mut name_buf)?;
|
|
|
|
|
|
|
|
let tileset_id = f.read_u8()? as usize;
|
|
|
|
let bg_id = f.read_u8()? as usize;
|
2021-06-27 01:06:56 +00:00
|
|
|
let bg_type = f.read_u8()?;
|
|
|
|
let boss_no = f.read_u8()?;
|
2020-09-11 22:52:20 +00:00
|
|
|
let npc1 = f.read_u8()? as usize;
|
|
|
|
let npc2 = f.read_u8()? as usize;
|
2020-08-19 01:59:16 +00:00
|
|
|
|
|
|
|
let map = from_utf8(&map_buf)
|
|
|
|
.map_err(|_| ResourceLoadError("UTF-8 error in map field".to_string()))?
|
2021-06-16 15:19:26 +00:00
|
|
|
.trim_matches('\0')
|
|
|
|
.to_owned();
|
2020-08-19 01:59:16 +00:00
|
|
|
let name = from_utf8(&name_buf)
|
|
|
|
.map_err(|_| ResourceLoadError("UTF-8 error in name field".to_string()))?
|
2021-06-16 15:19:26 +00:00
|
|
|
.trim_matches('\0')
|
|
|
|
.to_owned();
|
2020-08-19 01:59:16 +00:00
|
|
|
|
2020-09-11 22:52:20 +00:00
|
|
|
let stage = StageData {
|
2020-08-19 01:59:16 +00:00
|
|
|
name: name.clone(),
|
2022-03-15 01:54:03 +00:00
|
|
|
name_jp: name.clone(),
|
2020-08-19 01:59:16 +00:00
|
|
|
map: map.clone(),
|
|
|
|
boss_no,
|
|
|
|
tileset: Tileset::new(NXENGINE_TILESETS.get(tileset_id).unwrap_or(&"0")),
|
2021-06-27 01:06:56 +00:00
|
|
|
pxpack_data: None,
|
2020-08-19 01:59:16 +00:00
|
|
|
background: Background::new(NXENGINE_BACKDROPS.get(bg_id).unwrap_or(&"0")),
|
2021-06-27 01:06:56 +00:00
|
|
|
background_type: BackgroundType::from(bg_type),
|
|
|
|
background_color: Color::from_rgb(0, 0, 32),
|
2020-09-11 22:52:20 +00:00
|
|
|
npc1: NpcType::new(NXENGINE_NPCS.get(npc1).unwrap_or(&"0")),
|
|
|
|
npc2: NpcType::new(NXENGINE_NPCS.get(npc2).unwrap_or(&"0")),
|
2020-08-18 16:46:07 +00:00
|
|
|
};
|
|
|
|
stages.push(stage);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(stages);
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(ResourceLoadError("No stage table found.".to_string()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-06 01:11:17 +00:00
|
|
|
#[derive(Clone)]
|
2020-08-18 16:46:07 +00:00
|
|
|
pub struct Stage {
|
|
|
|
pub map: Map,
|
|
|
|
pub data: StageData,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Stage {
|
2022-02-10 07:54:20 +00:00
|
|
|
pub fn load(roots: &Vec<String>, data: &StageData, ctx: &mut Context) -> GameResult<Self> {
|
2021-06-27 01:06:56 +00:00
|
|
|
let mut data = data.clone();
|
2020-08-23 02:17:45 +00:00
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
if let Ok(pxpack_file) = filesystem::open_find(ctx, roots, ["/Stage/", &data.map, ".pxpack"].join("")) {
|
|
|
|
let map = Map::load_pxpack(pxpack_file, roots, &mut data, ctx)?;
|
2021-06-27 01:06:56 +00:00
|
|
|
let stage = Self { map, data };
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
return Ok(stage);
|
|
|
|
} else if let Ok(map_file) = filesystem::open_find(ctx, roots, ["/Stage/", &data.map, ".pxm"].join("")) {
|
|
|
|
let attrib_file = filesystem::open_find(ctx, roots, ["/Stage/", &data.tileset.name, ".pxa"].join(""))?;
|
2021-06-27 01:06:56 +00:00
|
|
|
|
|
|
|
let map = Map::load_pxm(map_file, attrib_file)?;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
let stage = Self { map, data };
|
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
return Ok(stage);
|
2021-06-27 01:06:56 +00:00
|
|
|
}
|
2022-02-10 07:54:20 +00:00
|
|
|
|
|
|
|
Err(GameError::ResourceLoadError(format!("Stage {} not found", data.map)))
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
2020-08-27 23:34:28 +00:00
|
|
|
|
2021-06-16 15:19:26 +00:00
|
|
|
pub fn load_text_script(
|
|
|
|
&self,
|
2022-02-10 07:54:20 +00:00
|
|
|
roots: &Vec<String>,
|
2021-06-16 15:19:26 +00:00
|
|
|
constants: &EngineConstants,
|
|
|
|
ctx: &mut Context,
|
|
|
|
) -> GameResult<TextScript> {
|
2022-02-10 07:54:20 +00:00
|
|
|
let tsc_file = filesystem::open_find(ctx, roots, ["/Stage/", &self.data.map, ".tsc"].join(""))?;
|
2020-09-25 17:14:52 +00:00
|
|
|
let text_script = TextScript::load_from(tsc_file, constants)?;
|
2020-08-27 23:34:28 +00:00
|
|
|
|
|
|
|
Ok(text_script)
|
|
|
|
}
|
2020-09-03 12:19:46 +00:00
|
|
|
|
2022-02-10 07:54:20 +00:00
|
|
|
pub fn load_npcs(&self, roots: &Vec<String>, ctx: &mut Context) -> GameResult<Vec<NPCData>> {
|
|
|
|
let pxe_file = filesystem::open_find(ctx, roots, ["/Stage/", &self.data.map, ".pxe"].join(""))?;
|
2020-09-03 12:19:46 +00:00
|
|
|
let npc_data = NPCData::load_from(pxe_file)?;
|
|
|
|
|
|
|
|
Ok(npc_data)
|
|
|
|
}
|
2020-09-30 03:11:25 +00:00
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
/// Returns map tile from foreground layer.
|
2020-11-01 19:08:52 +00:00
|
|
|
pub fn tile_at(&self, x: usize, y: usize) -> u8 {
|
2021-04-19 19:19:55 +00:00
|
|
|
if let Some(&tile) = self.map.tiles.get(y.wrapping_mul(self.map.width as usize).wrapping_add(x)) {
|
2020-11-01 19:08:52 +00:00
|
|
|
tile
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-27 01:06:56 +00:00
|
|
|
/// Changes map tile on foreground layer. Returns true if smoke should be emitted
|
2020-09-30 03:11:25 +00:00
|
|
|
pub fn change_tile(&mut self, x: usize, y: usize, tile_type: u8) -> bool {
|
2021-04-19 19:19:55 +00:00
|
|
|
if let Some(ptr) = self.map.tiles.get_mut(y.wrapping_mul(self.map.width as usize).wrapping_add(x)) {
|
2020-09-30 03:11:25 +00:00
|
|
|
if *ptr != tile_type {
|
|
|
|
*ptr = tile_type;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
2022-01-05 04:50:16 +00:00
|
|
|
|
|
|
|
pub struct StageTexturePaths {
|
|
|
|
/// Path to the stage's background texture.
|
|
|
|
pub background: String,
|
|
|
|
|
|
|
|
/// Path to the stage's foreground tileset texture.
|
|
|
|
pub tileset_fg: String,
|
|
|
|
|
|
|
|
/// Path to the stage's middleground tileset texture.
|
|
|
|
pub tileset_mg: String,
|
|
|
|
|
|
|
|
/// Path to the stage's background tileset texture.
|
|
|
|
pub tileset_bg: String,
|
|
|
|
|
|
|
|
/// Path to the stage's NPC spritesheet 1.
|
|
|
|
pub npc1: String,
|
|
|
|
|
|
|
|
/// Path to the stage's NPC spritesheet 2.
|
|
|
|
pub npc2: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl StageTexturePaths {
|
|
|
|
pub fn new() -> StageTexturePaths {
|
|
|
|
StageTexturePaths {
|
|
|
|
background: "bk0".to_string(),
|
|
|
|
tileset_fg: "Stage/Prt0".to_owned(),
|
|
|
|
tileset_mg: "Stage/Prt0".to_owned(),
|
|
|
|
tileset_bg: "Stage/Prt0".to_owned(),
|
|
|
|
npc1: "Npc/Npc0".to_owned(),
|
|
|
|
npc2: "Npc/Npc0".to_owned(),
|
|
|
|
}
|
|
|
|
}
|
2022-01-06 01:11:17 +00:00
|
|
|
|
|
|
|
pub fn update(&mut self, stage: &Stage) {
|
|
|
|
self.background = stage.data.background.filename();
|
2022-01-17 22:29:30 +00:00
|
|
|
let (tileset_fg, tileset_mg, tileset_bg) = if let Some(pxpack_data) = &stage.data.pxpack_data {
|
2022-01-06 01:11:17 +00:00
|
|
|
let t_fg = ["Stage/", &pxpack_data.tileset_fg].join("");
|
|
|
|
let t_mg = ["Stage/", &pxpack_data.tileset_mg].join("");
|
|
|
|
let t_bg = ["Stage/", &pxpack_data.tileset_bg].join("");
|
|
|
|
|
|
|
|
(t_fg, t_mg, t_bg)
|
|
|
|
} else {
|
|
|
|
let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join("");
|
|
|
|
|
|
|
|
(tex_tileset_name.clone(), tex_tileset_name.clone(), tex_tileset_name)
|
|
|
|
};
|
|
|
|
self.tileset_fg = tileset_fg;
|
|
|
|
self.tileset_mg = tileset_mg;
|
|
|
|
self.tileset_bg = tileset_bg;
|
|
|
|
|
|
|
|
self.npc1 = ["Npc/", &stage.data.npc1.filename()].join("");
|
|
|
|
self.npc2 = ["Npc/", &stage.data.npc2.filename()].join("");
|
|
|
|
}
|
2022-01-05 04:50:16 +00:00
|
|
|
}
|