363 lines
17 KiB
Rust
363 lines
17 KiB
Rust
// 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()
|
|
.map(|map_variant| {
|
|
objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
|
|
}).flatten().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();
|
|
}
|
|
}
|