use std::io::{Cursor, Read}; use std::str::from_utf8; use byteorder::{BE, LE, ReadBytesExt}; use ggez::{Context, filesystem, GameResult}; use ggez::GameError::ResourceLoadError; use log::info; use strum::AsStaticRef; use crate::map::Map; #[derive(Debug, EnumIter, AsStaticStr, PartialEq, Eq, Hash, Copy, Clone)] pub enum NpcType { #[strum(serialize = "0")] Zero, Almo1, Almo2, Ballos, Bllg, Cemet, Cent, Curly, Dark, Dr, Eggs1, Eggs2, Frog, Guest, Hell, Heri, IronH, Island, Kings, Maze, Miza, Moon, Omg, Plant, Press, Priest, Ravil, Red, Regu, Sand, Stream, Sym, Toro, TwinD, Weed, X, } impl NpcType { pub fn filename(&self) -> String { ["Npc", self.as_static()].join("") } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct Tileset { name: String, } impl Clone for Tileset { fn clone(&self) -> Self { Self { name: self.name.clone(), } } } impl Tileset { pub fn new(name: &str) -> Self { Self { name: name.to_owned(), } } 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 { Self { name: self.name.clone(), } } } impl Background { pub fn new(name: &str) -> Self { Self { name: name.to_owned(), } } pub fn filename(&self) -> String { self.name.to_owned() } } #[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)] pub enum BackgroundType { Stationary, MoveDistant, MoveNear, Water, Black, Autoscroll, OutsideWind, Outside, } impl BackgroundType { pub fn new(id: usize) -> Self { match id { 0 => { Self::Stationary } 1 => { Self::MoveDistant } 2 => { Self::MoveNear } 3 => { Self::Water } 4 => { Self::Black } 5 => { Self::Autoscroll } 6 => { Self::OutsideWind } 7 => { Self::Outside } _ => { Self::Black } } } } #[derive(Debug)] pub struct StageData { pub name: String, pub map: String, pub boss: String, pub boss_no: usize, pub tileset: Tileset, pub background: Background, pub background_type: BackgroundType, pub npc: NpcType, } impl Clone for StageData { fn clone(&self) -> Self { StageData { name: self.name.clone(), map: self.map.clone(), boss: self.boss.clone(), boss_no: self.boss_no, tileset: self.tileset.clone(), background: self.background.clone(), background_type: self.background_type, npc: self.npc, } } } impl StageData { // todo: refactor to make it less repetitive. pub fn load_stage_table(ctx: &mut Context, root: &str) -> GameResult> { let stage_tbl_path = [root, "stage.tbl"].join(""); let mrmap_bin_path = [root, "mrmap.bin"].join(""); if filesystem::exists(ctx, &stage_tbl_path) { // Cave Story+ stage table. Probably one of most awful formats out there. let mut stages = Vec::new(); info!("Loading stage table from {}", &stage_tbl_path); let mut data = Vec::new(); filesystem::open(ctx, stage_tbl_path)?.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 npc_buf = vec![0u8; 0x20]; let mut boss_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::()? as usize; f.read_exact(&mut back_buf)?; f.read_exact(&mut npc_buf)?; f.read_exact(&mut boss_buf)?; let boss_no = f.read_u8()? as usize; f.read_exact(&mut name_jap_buf)?; f.read_exact(&mut name_buf)?; let tileset = from_utf8(&ts_buf) .map_err(|_| ResourceLoadError("UTF-8 error in tileset field".to_string()))? .trim_matches('\0').to_owned(); let map = from_utf8(&map_buf) .map_err(|_| ResourceLoadError("UTF-8 error in map field".to_string()))? .trim_matches('\0').to_owned(); let background = from_utf8(&back_buf) .map_err(|_| ResourceLoadError("UTF-8 error in background field".to_string()))? .trim_matches('\0').to_owned(); let boss = from_utf8(&boss_buf) .map_err(|_| ResourceLoadError("UTF-8 error in boss field".to_string()))? .trim_matches('\0').to_owned(); let name = from_utf8(&name_buf) .map_err(|_| ResourceLoadError("UTF-8 error in name field".to_string()))? .trim_matches('\0').to_owned(); println!("bg type: {}", bg_type); let stage = Self { name: name.clone(), map: map.clone(), boss: boss.clone(), boss_no, tileset: Tileset::new(&tileset), background: Background::new(&background), background_type: BackgroundType::new(bg_type), npc: NpcType::Zero, }; stages.push(stage); } return Ok(stages); } else if filesystem::exists(ctx, &mrmap_bin_path) { // CSE2 stage table let mut stages = Vec::new(); info!("Loading stage table from {}", &mrmap_bin_path); let mut data = Vec::new(); let mut fh = filesystem::open(ctx, &mrmap_bin_path)?; let count = fh.read_u32::()?; fh.read_to_end(&mut data)?; if data.len() < count as usize * 0x74 { return Err(ResourceLoadError("Specified stage table size is bigger than actual number of entries.".to_string())); } let mut f = Cursor::new(data); for _ in 0..count { let mut ts_buf = vec![0u8; 0x10]; let mut map_buf = vec![0u8; 0x10]; let mut back_buf = vec![0u8; 0x10]; let mut npc_buf = vec![0u8; 0x10]; let mut boss_buf = vec![0u8; 0x10]; let mut name_buf = vec![0u8; 0x22]; f.read_exact(&mut ts_buf)?; f.read_exact(&mut map_buf)?; let bg_type = f.read_u8()? as usize; f.read_exact(&mut back_buf)?; f.read_exact(&mut npc_buf)?; f.read_exact(&mut boss_buf)?; let boss_no = f.read_u8()? as usize; f.read_exact(&mut name_buf)?; let tileset = from_utf8(&ts_buf) .map_err(|_| ResourceLoadError("UTF-8 error in tileset field".to_string()))? .trim_matches('\0').to_owned(); let map = from_utf8(&map_buf) .map_err(|_| ResourceLoadError("UTF-8 error in map field".to_string()))? .trim_matches('\0').to_owned(); let background = from_utf8(&back_buf) .map_err(|_| ResourceLoadError("UTF-8 error in background field".to_string()))? .trim_matches('\0').to_owned(); let boss = from_utf8(&boss_buf) .map_err(|_| ResourceLoadError("UTF-8 error in boss field".to_string()))? .trim_matches('\0').to_owned(); let name = from_utf8(&name_buf) .map_err(|_| ResourceLoadError("UTF-8 error in name field".to_string()))? .trim_matches('\0').to_owned(); let stage = Self { name: name.clone(), map: map.clone(), boss: boss.clone(), boss_no, tileset: Tileset::new(&tileset), background: Background::new(&background), background_type: BackgroundType::new(bg_type), npc: NpcType::Zero, }; stages.push(stage); } return Ok(stages); } // todo: NXEngine stage.dat support? Err(ResourceLoadError("No stage table found.".to_string())) } } pub struct Stage { pub map: Map, pub data: StageData, } impl Stage { pub fn load(ctx: &mut Context, root: &str, data: &StageData) -> GameResult { let map_file = filesystem::open(ctx, [root, "Stage/", &data.map, ".pxm"].join(""))?; let attrib_file = filesystem::open(ctx, [root, "Stage/", &data.tileset.name, ".pxa"].join(""))?; let map = Map::load_from(map_file, attrib_file)?; let stage = Self { map, data: data.clone(), }; Ok(stage) } }