You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

362 lines
17 KiB

// 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<Option<MapObject>> {
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<Option<MapObject>> {
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<Option<MapEnemy>> {
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<Option<MapEnemy>> {
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<Option<MapEnemy>> {
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<MapVariant>,
enemy_data: Vec<Option<MapEnemy>>,
object_data: Vec<Option<MapObject>>,
}
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<MapEnemy, MapsError> {
self.enemy_data[id].ok_or(MapsError::InvalidMonsterId(id))
}
pub fn object_by_id(&self, id: usize) -> Result<MapObject, MapsError> {
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<Option<MapEnemy>>, objects: Vec<Option<MapObject>>, 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<u16> {
let mut rare_monsters = vec![0xFFFF; 16];
let shiny: Vec<(usize, &Option<MapEnemy>)> = 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();
}
}