// TOOD: `pub(super) for most of these?` use std::path::PathBuf; use std::io::{Read}; use std::fs::File; use thiserror::Error; use crate::ship::monster::MonsterType; use crate::ship::room::{Episode, RoomMode}; // TODO: don't use * use crate::ship::map::*; pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec> { let mut object_data = Vec::new(); while let Ok(raw_object) = RawMapObject::from_byte_stream(cursor) { let object = MapObject::from_raw(raw_object, *episode, map_area); object_data.push(object.ok()); } object_data } fn objects_from_map_data(path: PathBuf, episode: &Episode, map_area: &MapArea) -> Vec> { let mut cursor = File::open(path).unwrap(); objects_from_stream(&mut cursor, episode, map_area) } fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) -> Vec> { let enemy = MapEnemy::from_raw(raw_enemy, episode, map_area); enemy .map_or(vec![None], |monster| { let mut monsters = vec![Some(monster)]; match monster.monster { MonsterType::Monest => { for _ in 0..30 { monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area))); } }, MonsterType::PanArms => { monsters.push(Some(MapEnemy::new(MonsterType::Hidoom, monster.map_area))); monsters.push(Some(MapEnemy::new(MonsterType::Migium, monster.map_area))); }, MonsterType::PofuillySlime => { for _ in 0..5 { monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area))); } }, MonsterType::PouillySlime => { for _ in 0..5 { monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area))); } }, MonsterType::SinowBeat => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area))); } }, MonsterType::SinowGold => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area))); } }, MonsterType::Canane => { for _ in 0..8 { monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area))); } }, MonsterType::ChaosSorcerer => { monsters.push(Some(MapEnemy::new(MonsterType::BeeR, monster.map_area))); monsters.push(Some(MapEnemy::new(MonsterType::BeeL, monster.map_area))); }, MonsterType::Bulclaw => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::Claw, monster.map_area))); } }, MonsterType::DeRolLe => { for _ in 0..10 { monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody, monster.map_area))); } for _ in 0..9 { monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area))); } }, MonsterType::VolOptPartA => { for _ in 0..6 { monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar, monster.map_area))); } for _ in 0..24 { monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area))); } for _ in 0..2 { monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area))); } monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp, monster.map_area))); monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore, monster.map_area))); monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area))); }, // TOOD: this cares about difficulty (theres an ult-specific darvant?) MonsterType::DarkFalz => { for _ in 0..509 { monsters.push(Some(MapEnemy::new(MonsterType::Darvant, monster.map_area))); } monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3, monster.map_area))); monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2, monster.map_area))); monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1, monster.map_area))); }, MonsterType::OlgaFlow => { for _ in 0..512 { monsters.push(Some(MapEnemy::new(MonsterType::OlgaFlow, monster.map_area))); } }, MonsterType::BarbaRay => { for _ in 0..47 { monsters.push(Some(MapEnemy::new(MonsterType::PigRay, monster.map_area))); } }, MonsterType::GolDragon => { for _ in 0..5 { monsters.push(Some(MapEnemy::new(MonsterType::GolDragon, monster.map_area))); } }, MonsterType::SinowBerill => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::SinowBerill, monster.map_area))); // unused clones } }, MonsterType::SinowSpigell => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::SinowSpigell, monster.map_area))); // unused clones } }, MonsterType::Recobox => { // + recons for _ in 0..raw_enemy.children { monsters.push(Some(MapEnemy::new(MonsterType::Recon, monster.map_area))); } }, MonsterType::Epsilon => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::Epsiguard, monster.map_area))); } }, _ => { for _ in 0..raw_enemy.children { monsters.push(Some(MapEnemy::new(monster.monster, monster.map_area))); } } } monsters }) } pub fn enemy_data_from_stream(cursor: &mut impl Read, map_area: &MapArea, episode: &Episode) -> Vec> { let mut enemy_data = Vec::new(); while let Ok(enemy) = RawMapEnemy::from_byte_stream(cursor) { enemy_data.append(&mut parse_enemy(episode, map_area, enemy)); } enemy_data } fn enemy_data_from_map_data(map_variant: &MapVariant, episode: &Episode) -> Vec> { let path = map_variant.dat_file(); let mut cursor = File::open(path).unwrap(); enemy_data_from_stream(&mut cursor, &map_variant.map, episode) } #[derive(Error, Debug)] #[error("")] pub enum MapsError { InvalidMonsterId(usize), InvalidObjectId(usize), } #[derive(Debug)] pub struct Maps { map_variants: Vec, enemy_data: Vec>, object_data: Vec>, } impl Maps { pub fn new(room_mode: RoomMode, rare_monster_table: &enemy::RareMonsterAppearTable) -> Maps { let map_variants = match (room_mode.episode(), room_mode.single_player()) { (Episode::One, 0) => { vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online), MapVariant::new(MapArea::Forest1, MapVariantMode::Online), MapVariant::new(MapArea::Forest2, MapVariantMode::Online), MapVariant::new(MapArea::Caves1, MapVariantMode::Online), MapVariant::new(MapArea::Caves2, MapVariantMode::Online), MapVariant::new(MapArea::Caves3, MapVariantMode::Online), MapVariant::new(MapArea::Mines1, MapVariantMode::Online), MapVariant::new(MapArea::Mines2, MapVariantMode::Online), MapVariant::new(MapArea::Ruins1, MapVariantMode::Online), MapVariant::new(MapArea::Ruins2, MapVariantMode::Online), MapVariant::new(MapArea::Ruins3, MapVariantMode::Online), MapVariant::new(MapArea::Dragon, MapVariantMode::Online), MapVariant::new(MapArea::DeRolLe, MapVariantMode::Online), MapVariant::new(MapArea::VolOpt, MapVariantMode::Online), MapVariant::new(MapArea::DarkFalz, MapVariantMode::Online), ] }, (Episode::One, 1) => { vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Offline), MapVariant::new(MapArea::Forest1, MapVariantMode::Offline), MapVariant::new(MapArea::Forest2, MapVariantMode::Offline), MapVariant::new(MapArea::Caves1, MapVariantMode::Offline), MapVariant::new(MapArea::Caves2, MapVariantMode::Offline), MapVariant::new(MapArea::Caves3, MapVariantMode::Offline), MapVariant::new(MapArea::Mines1, MapVariantMode::Offline), MapVariant::new(MapArea::Mines2, MapVariantMode::Offline), MapVariant::new(MapArea::Ruins1, MapVariantMode::Offline), MapVariant::new(MapArea::Ruins2, MapVariantMode::Offline), MapVariant::new(MapArea::Ruins3, MapVariantMode::Offline), MapVariant::new(MapArea::Dragon, MapVariantMode::Offline), MapVariant::new(MapArea::DeRolLe, MapVariantMode::Offline), MapVariant::new(MapArea::VolOpt, MapVariantMode::Offline), MapVariant::new(MapArea::DarkFalz, MapVariantMode::Offline), ] }, (Episode::Two, 0) => { vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Online), MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Online), MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Online), MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Online), MapVariant::new(MapArea::VrSpaceshipBeta, MapVariantMode::Online), MapVariant::new(MapArea::Cca, MapVariantMode::Online), MapVariant::new(MapArea::JungleAreaNorth, MapVariantMode::Online), MapVariant::new(MapArea::JungleAreaEast, MapVariantMode::Online), MapVariant::new(MapArea::Mountain, MapVariantMode::Online), MapVariant::new(MapArea::Seaside, MapVariantMode::Online), MapVariant::new(MapArea::SeabedUpper, MapVariantMode::Online), MapVariant::new(MapArea::SeabedLower, MapVariantMode::Online), MapVariant::new(MapArea::GalGryphon, MapVariantMode::Online), MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Online), MapVariant::new(MapArea::BarbaRay, MapVariantMode::Online), MapVariant::new(MapArea::GolDragon, MapVariantMode::Online), ] }, (Episode::Two, 1) => { vec![MapVariant::new(MapArea::Pioneer2Ep2, MapVariantMode::Offline), MapVariant::new(MapArea::VrTempleAlpha, MapVariantMode::Offline), MapVariant::new(MapArea::VrTempleBeta, MapVariantMode::Offline), MapVariant::new(MapArea::VrSpaceshipAlpha, MapVariantMode::Offline), MapVariant::new(MapArea::VrSpaceshipBeta, MapVariantMode::Offline), MapVariant::new(MapArea::Cca, MapVariantMode::Offline), MapVariant::new(MapArea::JungleAreaNorth, MapVariantMode::Offline), MapVariant::new(MapArea::JungleAreaEast, MapVariantMode::Offline), MapVariant::new(MapArea::Mountain, MapVariantMode::Offline), MapVariant::new(MapArea::Seaside, MapVariantMode::Offline), MapVariant::new(MapArea::SeabedUpper, MapVariantMode::Offline), MapVariant::new(MapArea::SeabedLower, MapVariantMode::Offline), MapVariant::new(MapArea::GalGryphon, MapVariantMode::Offline), MapVariant::new(MapArea::OlgaFlow, MapVariantMode::Offline), MapVariant::new(MapArea::BarbaRay, MapVariantMode::Offline), MapVariant::new(MapArea::GolDragon, MapVariantMode::Offline), ] }, (Episode::Four, _) => { vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Online), MapVariant::new(MapArea::CraterEast, MapVariantMode::Online), MapVariant::new(MapArea::CraterWest, MapVariantMode::Online), MapVariant::new(MapArea::CraterSouth, MapVariantMode::Online), MapVariant::new(MapArea::CraterNorth, MapVariantMode::Online), MapVariant::new(MapArea::CraterInterior, MapVariantMode::Online), MapVariant::new(MapArea::SubDesert1, MapVariantMode::Online), MapVariant::new(MapArea::SubDesert2, MapVariantMode::Online), MapVariant::new(MapArea::SubDesert3, MapVariantMode::Online), MapVariant::new(MapArea::SaintMillion, MapVariantMode::Online), ] }, _ => unreachable!() }; let mut maps = Maps { enemy_data: map_variants.iter() .fold(Vec::new(), |mut enemy_data, map_variant| { enemy_data.append(&mut enemy_data_from_map_data(map_variant, &room_mode.episode())); enemy_data }), object_data: map_variants.iter() .flat_map(|map_variant| { objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map) }).collect(), map_variants, }; maps.roll_monster_appearance(rare_monster_table); maps } pub fn enemy_by_id(&self, id: usize) -> Result { self.enemy_data[id].ok_or(MapsError::InvalidMonsterId(id)) } pub fn object_by_id(&self, id: usize) -> Result { self.object_data[id].ok_or(MapsError::InvalidObjectId(id)) } pub fn map_headers(&self) -> [u32; 0x20] { self.map_variants.iter() .enumerate() .fold([0; 0x20], |mut header, (i, map_variant)| { let [major, minor] = map_variant.pkt_header(); header[i*2] = major as u32; header[i*2 + 1] = minor as u32; header }) } pub fn set_quest_data(&mut self, enemies: Vec>, objects: Vec>, rare_monster_appear_table: &RareMonsterAppearTable) { self.enemy_data = enemies; self.roll_monster_appearance(rare_monster_appear_table); self.object_data = objects; } pub fn get_rare_monster_list(&self) -> Vec { let mut rare_monsters = vec![0xFFFF; 16]; let shiny: Vec<(usize, &Option)> = self.enemy_data.iter() .enumerate() .filter(|(_,m)| { if m.is_some() { m.unwrap().shiny } else { false } }) .collect(); for monster in &shiny { if let Some(j) = rare_monsters.iter().position(|&x| x == 0xFFFF) { rare_monsters[j] = monster.0 as u16; } else { break } } rare_monsters } pub fn roll_monster_appearance(&mut self, rare_monster_table: &RareMonsterAppearTable) { self.enemy_data = self.enemy_data .iter() // .map(|&x| if x.is_some() && x.unwrap().has_rare_appearance() { .map(|&x| if let Some(monster) = x { if monster.has_rare_appearance() { Some(monster.roll_appearance_for_mission(rare_monster_table)) } else { Some(monster) } } else { x } ) .collect(); } }