// 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::ship::ShipEvent; use crate::area::MapArea; use crate::Holiday; use crate::enemy::{MapEnemy, RawMapEnemy, RareMonsterAppearTable}; use crate::monster::MonsterType; use crate::variant::{MapVariant, MapVariantMode}; use crate::object::{MapObject, RawMapObject}; use crate::room::{Episode, RoomMode, PlayerMode}; 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) } pub fn default_map_variants(episode: Episode, player_mode: PlayerMode) -> Vec { match (episode, player_mode) { (Episode::One, PlayerMode::Multi) => { 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, PlayerMode::Single) => { 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, PlayerMode::Multi) => { 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, PlayerMode::Single) => { 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, PlayerMode::Multi) => { 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), ] }, (Episode::Four, PlayerMode::Single) => { vec![MapVariant::new(MapArea::Pioneer2Ep4, MapVariantMode::Offline), MapVariant::new(MapArea::CraterEast, MapVariantMode::Offline), MapVariant::new(MapArea::CraterWest, MapVariantMode::Offline), MapVariant::new(MapArea::CraterSouth, MapVariantMode::Offline), MapVariant::new(MapArea::CraterNorth, MapVariantMode::Offline), MapVariant::new(MapArea::CraterInterior, MapVariantMode::Offline), MapVariant::new(MapArea::SubDesert1, MapVariantMode::Offline), MapVariant::new(MapArea::SubDesert2, MapVariantMode::Offline), MapVariant::new(MapArea::SubDesert3, MapVariantMode::Offline), MapVariant::new(MapArea::SaintMillion, MapVariantMode::Offline), ] }, } } #[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(map_variants: Vec, enemy_data: Vec>, object_data: Vec>) -> Maps { Maps { map_variants, enemy_data, object_data, } } 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_table: &RareMonsterAppearTable, event: Holiday) { self.enemy_data = enemies .into_iter() .map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event))) .collect(); 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)| { match m { Some(m) => { m.shiny }, None => 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 generate_free_roam_maps(room_mode: RoomMode, event: Holiday) -> Maps { let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode()); let map_variants = default_map_variants(room_mode.episode(), room_mode.player_mode()); Maps { enemy_data: map_variants.iter() .flat_map(|map_variant| { enemy_data_from_map_data(map_variant, &room_mode.episode()) }) .map(|enemy| enemy.map(|enemy| rare_monster_table.apply(enemy, event))) .collect(), 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, } }