|
@ -7,6 +7,7 @@ use std::fs::File; |
|
|
|
|
|
|
|
|
use byteorder::{LittleEndian, ReadBytesExt};
|
|
|
use byteorder::{LittleEndian, ReadBytesExt};
|
|
|
use rand::Rng;
|
|
|
use rand::Rng;
|
|
|
|
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
|
use crate::ship::monster::MonsterType;
|
|
|
use crate::ship::monster::MonsterType;
|
|
|
use crate::ship::room::Episode;
|
|
|
use crate::ship::room::Episode;
|
|
@ -16,7 +17,7 @@ struct RawMapEnemy { |
|
|
id: u32,
|
|
|
id: u32,
|
|
|
_unknown1: u16,
|
|
|
_unknown1: u16,
|
|
|
children: u16,
|
|
|
children: u16,
|
|
|
_unknown3: u16,
|
|
|
|
|
|
|
|
|
map_area: u16,
|
|
|
_unknown4: u16,
|
|
|
_unknown4: u16,
|
|
|
section: u16,
|
|
|
section: u16,
|
|
|
wave_idd: u16,
|
|
|
wave_idd: u16,
|
|
@ -42,7 +43,7 @@ impl RawMapEnemy { |
|
|
id: cursor.read_u32::<LittleEndian>()?,
|
|
|
id: cursor.read_u32::<LittleEndian>()?,
|
|
|
_unknown1: cursor.read_u16::<LittleEndian>()?,
|
|
|
_unknown1: cursor.read_u16::<LittleEndian>()?,
|
|
|
children: cursor.read_u16::<LittleEndian>()?,
|
|
|
children: cursor.read_u16::<LittleEndian>()?,
|
|
|
_unknown3: cursor.read_u16::<LittleEndian>()?,
|
|
|
|
|
|
|
|
|
map_area: cursor.read_u16::<LittleEndian>()?,
|
|
|
_unknown4: cursor.read_u16::<LittleEndian>()?,
|
|
|
_unknown4: cursor.read_u16::<LittleEndian>()?,
|
|
|
section: cursor.read_u16::<LittleEndian>()?,
|
|
|
section: cursor.read_u16::<LittleEndian>()?,
|
|
|
wave_idd: cursor.read_u16::<LittleEndian>()?,
|
|
|
wave_idd: cursor.read_u16::<LittleEndian>()?,
|
|
@ -65,18 +66,22 @@ impl RawMapEnemy { |
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
|
|
#[error("")]
|
|
|
enum MapEnemyError {
|
|
|
enum MapEnemyError {
|
|
|
UnknownEnemyId(u32),
|
|
|
UnknownEnemyId(u32),
|
|
|
|
|
|
MapAreaError(#[from] MapAreaError),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
pub struct MapEnemy {
|
|
|
pub struct MapEnemy {
|
|
|
pub monster: MonsterType,
|
|
|
pub monster: MonsterType,
|
|
|
|
|
|
pub map_area: MapArea,
|
|
|
hp: u32,
|
|
|
hp: u32,
|
|
|
// other stats from bp.n
|
|
|
|
|
|
dead: bool,
|
|
|
|
|
|
|
|
|
// TODO: other stats from battleparam
|
|
|
|
|
|
pub dropped_item: bool,
|
|
|
|
|
|
pub gave_exp: bool,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
impl MapEnemy {
|
|
|
impl MapEnemy {
|
|
@ -169,16 +174,20 @@ impl MapEnemy { |
|
|
|
|
|
|
|
|
Ok(MapEnemy {
|
|
|
Ok(MapEnemy {
|
|
|
monster: monster,
|
|
|
monster: monster,
|
|
|
|
|
|
map_area: MapArea::from_value(&episode, enemy.map_area as u32)?,
|
|
|
hp: 0,
|
|
|
hp: 0,
|
|
|
dead: false,
|
|
|
|
|
|
|
|
|
dropped_item: false,
|
|
|
|
|
|
gave_exp: false,
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
fn new(monster: MonsterType) -> MapEnemy {
|
|
|
|
|
|
|
|
|
fn new(monster: MonsterType, map_area: MapArea) -> MapEnemy {
|
|
|
MapEnemy {
|
|
|
MapEnemy {
|
|
|
monster: monster,
|
|
|
monster: monster,
|
|
|
|
|
|
map_area: map_area,
|
|
|
hp: 0,
|
|
|
hp: 0,
|
|
|
dead: false,
|
|
|
|
|
|
|
|
|
dropped_item: false,
|
|
|
|
|
|
gave_exp: false,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@ -340,7 +349,7 @@ enum MapVariantMode { |
|
|
Offline,
|
|
|
Offline,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
|
pub enum MapArea {
|
|
|
pub enum MapArea {
|
|
|
Pioneer2Ep1,
|
|
|
Pioneer2Ep1,
|
|
|
Forest1,
|
|
|
Forest1,
|
|
@ -359,12 +368,14 @@ pub enum MapArea { |
|
|
DarkFalz,
|
|
|
DarkFalz,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
|
|
#[error("")]
|
|
|
pub enum MapAreaError {
|
|
|
pub enum MapAreaError {
|
|
|
UnknownMapArea(u32),
|
|
|
UnknownMapArea(u32),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
impl MapArea {
|
|
|
impl MapArea {
|
|
|
pub fn from_value(episode: Episode, area: u32) -> Result<MapArea, MapAreaError> {
|
|
|
|
|
|
|
|
|
pub fn from_value(episode: &Episode, area: u32) -> Result<MapArea, MapAreaError> {
|
|
|
match (episode, area) {
|
|
|
match (episode, area) {
|
|
|
(Episode::One, 0) => Ok(MapArea::Pioneer2Ep1),
|
|
|
(Episode::One, 0) => Ok(MapArea::Pioneer2Ep1),
|
|
|
(Episode::One, 1) => Ok(MapArea::Forest1),
|
|
|
(Episode::One, 1) => Ok(MapArea::Forest1),
|
|
@ -404,6 +415,26 @@ impl MapArea { |
|
|
_ => None
|
|
|
_ => None
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn area_value(&self) -> u8 {
|
|
|
|
|
|
match self {
|
|
|
|
|
|
MapArea::Pioneer2Ep1 => 0,
|
|
|
|
|
|
MapArea::Forest1 => 1,
|
|
|
|
|
|
MapArea::Forest2 => 2,
|
|
|
|
|
|
MapArea::Caves1 => 3,
|
|
|
|
|
|
MapArea::Caves2 => 4,
|
|
|
|
|
|
MapArea::Caves3 => 5,
|
|
|
|
|
|
MapArea::Mines1 => 6,
|
|
|
|
|
|
MapArea::Mines2 => 7,
|
|
|
|
|
|
MapArea::Ruins1 => 8,
|
|
|
|
|
|
MapArea::Ruins2 => 9,
|
|
|
|
|
|
MapArea::Ruins3 => 10,
|
|
|
|
|
|
MapArea::Dragon => 11,
|
|
|
|
|
|
MapArea::DeRolLe => 12,
|
|
|
|
|
|
MapArea::VolOpt => 13,
|
|
|
|
|
|
MapArea::DarkFalz => 14,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -516,76 +547,76 @@ fn enemy_data_from_map_data(path: PathBuf, episode: &Episode) -> Vec<Option<MapE |
|
|
match monster.monster {
|
|
|
match monster.monster {
|
|
|
MonsterType::Monest => {
|
|
|
MonsterType::Monest => {
|
|
|
for _ in 0..30 {
|
|
|
for _ in 0..30 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Mothmant, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
MonsterType::PofuillySlime => {
|
|
|
MonsterType::PofuillySlime => {
|
|
|
for _ in 0..4 {
|
|
|
for _ in 0..4 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
MonsterType::PanArms => {
|
|
|
MonsterType::PanArms => {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Hidoom)));
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Migium)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Hidoom, monster.map_area)));
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Migium, monster.map_area)));
|
|
|
},
|
|
|
},
|
|
|
MonsterType::SinowBeat => {
|
|
|
MonsterType::SinowBeat => {
|
|
|
for _ in 0..4 {
|
|
|
for _ in 0..4 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
MonsterType::SinowGold => {
|
|
|
MonsterType::SinowGold => {
|
|
|
for _ in 0..4 {
|
|
|
for _ in 0..4 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::SinowGold)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
MonsterType::Canane => {
|
|
|
MonsterType::Canane => {
|
|
|
for _ in 0..8 {
|
|
|
for _ in 0..8 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
MonsterType::ChaosSorcerer => {
|
|
|
MonsterType::ChaosSorcerer => {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::BeeR)));
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::BeeL)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::BeeR, monster.map_area)));
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::BeeL, monster.map_area)));
|
|
|
},
|
|
|
},
|
|
|
MonsterType::Bulclaw => {
|
|
|
MonsterType::Bulclaw => {
|
|
|
for _ in 0..4 {
|
|
|
for _ in 0..4 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Claw)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Claw, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
MonsterType::DeRolLe => {
|
|
|
MonsterType::DeRolLe => {
|
|
|
for _ in 0..10 {
|
|
|
for _ in 0..10 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
for _ in 0..9 {
|
|
|
for _ in 0..9 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
MonsterType::VolOptPartA => {
|
|
|
MonsterType::VolOptPartA => {
|
|
|
for _ in 0..6 {
|
|
|
for _ in 0..6 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
for _ in 0..24 {
|
|
|
for _ in 0..24 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
for _ in 0..2 {
|
|
|
for _ in 0..2 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp)));
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore)));
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused)));
|
|
|
|
|
|
|
|
|
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?)
|
|
|
// TOOD: this cares about difficulty (theres an ult-specific darvant?)
|
|
|
MonsterType::DarkFalz => {
|
|
|
MonsterType::DarkFalz => {
|
|
|
for _ in 0..509 {
|
|
|
for _ in 0..509 {
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Darvant)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::Darvant, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3)));
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2)));
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1)));
|
|
|
|
|
|
|
|
|
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)));
|
|
|
},
|
|
|
},
|
|
|
_ => {
|
|
|
_ => {
|
|
|
for _ in 0..enemy.children {
|
|
|
for _ in 0..enemy.children {
|
|
|
monsters.push(Some(MapEnemy::new(monster.monster)));
|
|
|
|
|
|
|
|
|
monsters.push(Some(MapEnemy::new(monster.monster, monster.map_area)));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@ -596,6 +627,12 @@ fn enemy_data_from_map_data(path: PathBuf, episode: &Episode) -> Vec<Option<MapE |
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
|
|
#[error("")]
|
|
|
|
|
|
pub enum MapsError {
|
|
|
|
|
|
InvalidMonsterId(usize),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
#[derive(Debug)]
|
|
|
pub struct Maps {
|
|
|
pub struct Maps {
|
|
|
map_variants: [MapVariant; 15],
|
|
|
map_variants: [MapVariant; 15],
|
|
@ -641,8 +678,8 @@ impl Maps { |
|
|
maps
|
|
|
maps
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
pub fn enemy_by_id(&self, id: usize) -> MapEnemy {
|
|
|
|
|
|
self.enemy_data[id].unwrap()
|
|
|
|
|
|
|
|
|
pub fn enemy_by_id(&self, id: usize) -> Result<MapEnemy, MapsError> {
|
|
|
|
|
|
self.enemy_data[id].ok_or(MapsError::InvalidMonsterId(id))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
pub fn map_headers(&self) -> [u32; 0x20] {
|
|
|
pub fn map_headers(&self) -> [u32; 0x20] {
|
|
|