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.
378 lines
18 KiB
378 lines
18 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::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<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)
|
|
}
|
|
|
|
|
|
pub fn default_map_variants(episode: Episode, player_mode: PlayerMode) -> Vec<MapVariant> {
|
|
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<MapVariant>,
|
|
enemy_data: Vec<Option<MapEnemy>>,
|
|
object_data: Vec<Option<MapObject>>,
|
|
}
|
|
|
|
impl Maps {
|
|
pub fn new(map_variants: Vec<MapVariant>, enemy_data: Vec<Option<MapEnemy>>, object_data: Vec<Option<MapObject>>) -> Maps {
|
|
Maps {
|
|
map_variants,
|
|
enemy_data,
|
|
object_data,
|
|
}
|
|
}
|
|
|
|
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_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<u16> {
|
|
let mut rare_monsters = vec![0xFFFF; 16];
|
|
let shiny: Vec<(usize, &Option<MapEnemy>)> = 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,
|
|
}
|
|
}
|