use log::warn; use std::io::Cursor; use std::convert::Into; use std::path::PathBuf; use std::io::{Read}; use std::fs::File; use byteorder::{LittleEndian, ReadBytesExt}; use rand::Rng; use crate::ship::monster::MonsterType; use crate::ship::room::Episode; #[derive(Debug, Copy, Clone)] struct RawMapEnemy { id: u32, _unknown1: u16, children: u16, _unknown3: u16, _unknown4: u16, section: u16, wave_idd: u16, wave_id: u32, x: f32, y: f32, z: f32, xrot: u32, yrot: u32, zrot: u32, _field1: u32, field2: u32, _field3: u32, _field4: u32, _field5: u32, skin: u32, _field6: u32 } impl RawMapEnemy { fn from_byte_stream(cursor: &mut R) -> Result { Ok(RawMapEnemy { id: cursor.read_u32::()?, _unknown1: cursor.read_u16::()?, children: cursor.read_u16::()?, _unknown3: cursor.read_u16::()?, _unknown4: cursor.read_u16::()?, section: cursor.read_u16::()?, wave_idd: cursor.read_u16::()?, wave_id: cursor.read_u32::()?, x: cursor.read_f32::()?, y: cursor.read_f32::()?, z: cursor.read_f32::()?, xrot: cursor.read_u32::()?, yrot: cursor.read_u32::()?, zrot: cursor.read_u32::()?, _field1: cursor.read_u32::()?, field2: cursor.read_u32::()?, _field3: cursor.read_u32::()?, _field4: cursor.read_u32::()?, _field5: cursor.read_u32::()?, skin: cursor.read_u32::()?, _field6: cursor.read_u32::()?, }) } } #[derive(Debug)] enum MapEnemyError { UnknownEnemyId(u32), } #[derive(Debug, Copy, Clone)] pub struct MapEnemy { pub monster: MonsterType, hp: u32, // other stats from bp.n dead: bool, } impl MapEnemy { fn from_raw(enemy: RawMapEnemy, episode: &Episode /*, battleparam */) -> Result { let monster = match (enemy, episode) { (RawMapEnemy {id: 64, ..}, _) => MonsterType::Hildebear, (RawMapEnemy {id: 65, ..}, Episode::Four) => MonsterType::SandRappy, (RawMapEnemy {id: 65, ..}, _) => MonsterType::RagRappy, (RawMapEnemy {id: 66, ..}, _) => MonsterType::Monest, (RawMapEnemy {id: 67, field2: 0, ..}, _) => MonsterType::SavageWolf, (RawMapEnemy {id: 67, ..}, _) => MonsterType::BarbarousWolf, (RawMapEnemy {id: 68, skin: 0, ..}, _) => MonsterType::Booma, (RawMapEnemy {id: 68, skin: 1, ..}, _) => MonsterType::Gobooma, (RawMapEnemy {id: 68, skin: 2, ..}, _) => MonsterType::Gigobooma, (RawMapEnemy {id: 96, ..}, _) => MonsterType::GrassAssassin, (RawMapEnemy {id: 97, ..}, Episode::Two) => MonsterType::DelLily, (RawMapEnemy {id: 97, ..}, _) => MonsterType::PoisonLily, (RawMapEnemy {id: 98, ..}, _) => MonsterType::NanoDragon, (RawMapEnemy {id: 99, skin: 0, ..}, _) => MonsterType::EvilShark, (RawMapEnemy {id: 99, skin: 1, ..}, _) => MonsterType::PalShark, (RawMapEnemy {id: 99, skin: 2, ..}, _) => MonsterType::GuilShark, (RawMapEnemy {id: 100, ..}, _) => MonsterType::PofuillySlime, (RawMapEnemy {id: 101, ..}, _) => MonsterType::PanArms, (RawMapEnemy {id: 128, skin: 0, ..}, _) => MonsterType::Dubchic, (RawMapEnemy {id: 128, skin: 1, ..}, _) => MonsterType::Gillchic, (RawMapEnemy {id: 129, ..}, _) => MonsterType::Garanz, (RawMapEnemy {id: 130, field2: 0, ..}, _) => MonsterType::SinowBeat, (RawMapEnemy {id: 130, ..}, _) => MonsterType::SinowGold, (RawMapEnemy {id: 131, ..}, _) => MonsterType::Canadine, (RawMapEnemy {id: 132, ..}, _) => MonsterType::Canane, (RawMapEnemy {id: 133, ..}, _) => MonsterType::DubchicSwitch, (RawMapEnemy {id: 160, ..}, _) => MonsterType::Delsaber, (RawMapEnemy {id: 161, ..}, _) => MonsterType::ChaosSorcerer, (RawMapEnemy {id: 162, ..}, _) => MonsterType::DarkGunner, (RawMapEnemy {id: 164, ..}, _) => MonsterType::ChaosBringer, (RawMapEnemy {id: 165, ..}, _) => MonsterType::DarkBelra, (RawMapEnemy {id: 166, skin: 0, ..}, _) => MonsterType::Dimenian, (RawMapEnemy {id: 166, skin: 1, ..}, _) => MonsterType::LaDimenian, (RawMapEnemy {id: 166, skin: 2, ..}, _) => MonsterType::SoDimenian, (RawMapEnemy {id: 167, ..}, _) => MonsterType::Bulclaw, (RawMapEnemy {id: 168, ..}, _) => MonsterType::Claw, (RawMapEnemy {id: 192, ..}, Episode::One) => MonsterType::Dragon, (RawMapEnemy {id: 192, ..}, Episode::Two) => MonsterType::GalGryphon, (RawMapEnemy {id: 193, ..}, _) => MonsterType::DeRolLe, (RawMapEnemy {id: 194, ..}, _) => MonsterType::VolOptPartA, (RawMapEnemy {id: 197, ..}, _) => MonsterType::VolOpt, (RawMapEnemy {id: 200, ..}, _) => MonsterType::DarkFalz, (RawMapEnemy {id: 202, ..}, _) => MonsterType::Olga, (RawMapEnemy {id: 203, ..}, _) => MonsterType::BarbaRay, (RawMapEnemy {id: 204, ..}, _) => MonsterType::GolDragon, (RawMapEnemy {id: 212, skin: 0, ..}, _) => MonsterType::SinowBeril, (RawMapEnemy {id: 212, skin: 1, ..}, _) => MonsterType::SinowSpigell, (RawMapEnemy {id: 213, skin: 0, ..}, _) => MonsterType::Merillias, (RawMapEnemy {id: 213, skin: 1, ..}, _) => MonsterType::Meriltas, (RawMapEnemy {id: 214, skin: 0, ..}, _) => MonsterType::Mericarol, (RawMapEnemy {id: 214, skin: 1, ..}, _) => MonsterType::Merikle, (RawMapEnemy {id: 214, skin: 2, ..}, _) => MonsterType::Mericus, (RawMapEnemy {id: 215, skin: 0, ..}, _) => MonsterType::UlGibbon, (RawMapEnemy {id: 215, skin: 1, ..}, _) => MonsterType::ZolGibbon, (RawMapEnemy {id: 216, ..}, _) => MonsterType::Gibbles, (RawMapEnemy {id: 217, ..}, _) => MonsterType::Gee, (RawMapEnemy {id: 218, ..}, _) => MonsterType::GiGue, (RawMapEnemy {id: 219, ..}, _) => MonsterType::Deldepth, (RawMapEnemy {id: 220, ..}, _) => MonsterType::Delbiter, (RawMapEnemy {id: 221, skin: 0, ..}, _) => MonsterType::Dolmdarl, (RawMapEnemy {id: 221, skin: 1, ..}, _) => MonsterType::Dolmolm, (RawMapEnemy {id: 222, ..}, _) => MonsterType::Morfos, (RawMapEnemy {id: 223, ..}, _) => MonsterType::ReconBox, (RawMapEnemy {id: 224, ..}, _) => MonsterType::Epsilon, (RawMapEnemy {id: 224, skin: 0, ..}, _) => MonsterType::SinowZoa, (RawMapEnemy {id: 224, skin: 1, ..}, _) => MonsterType::SinowZele, (RawMapEnemy {id: 225, ..}, _) => MonsterType::IllGill, (RawMapEnemy {id: 272, ..}, _) => MonsterType::Astark, (RawMapEnemy {id: 273, field2: 0, ..}, _) => MonsterType::SatelliteLizard, (RawMapEnemy {id: 273, ..}, _) => MonsterType::Yowie, (RawMapEnemy {id: 274, ..}, _) => MonsterType::MerissaA, (RawMapEnemy {id: 275, ..}, _) => MonsterType::Girtablulu, (RawMapEnemy {id: 276, ..}, _) => MonsterType::Zu, (RawMapEnemy {id: 277, skin: 0, ..}, _) => MonsterType::Boota, (RawMapEnemy {id: 277, skin: 1, ..}, _) => MonsterType::ZeBoota, (RawMapEnemy {id: 277, skin: 2, ..}, _) => MonsterType::BaBoota, (RawMapEnemy {id: 278, ..}, _) => MonsterType::Dorphon, (RawMapEnemy {id: 279, skin: 0, ..}, _) => MonsterType::Goran, (RawMapEnemy {id: 279, skin: 1, ..}, _) => MonsterType::PyroGoran, (RawMapEnemy {id: 279, skin: 2, ..}, _) => MonsterType::GoranDetonator, (RawMapEnemy {id: 281, skin: 0, ..}, _) => MonsterType::SaintMillion, (RawMapEnemy {id: 281, skin: 1, ..}, _) => MonsterType::Shambertin, _ => return Err(MapEnemyError::UnknownEnemyId(enemy.id)) }; Ok(MapEnemy { monster: monster, hp: 0, dead: false, }) } fn new(monster: MonsterType) -> MapEnemy { MapEnemy { monster: monster, hp: 0, dead: false, } } } #[derive(Debug)] enum MapVariantMode { Online, Offline, } #[derive(Debug)] enum MapVariantType { Pioneer2Ep1, Forest1, Forest2, Caves1, Caves2, Caves3, Mines1, Mines2, Ruins1, Ruins2, Ruins3, Dragon, DeRolLe, VolOpt, DarkFalz, } #[derive(Debug)] struct MapVariant { map: MapVariantType, mode: MapVariantMode, major: u8, minor: u8, } impl MapVariant { fn new(map: MapVariantType, mode: MapVariantMode) -> MapVariant { let major = match map { MapVariantType::Pioneer2Ep1 => 0, MapVariantType::Forest1 | MapVariantType::Forest2 => 0, MapVariantType::Caves1 | MapVariantType::Caves2 | MapVariantType::Caves3 => rand::thread_rng().gen_range(0, 3), MapVariantType::Mines1 | MapVariantType::Mines2 => rand::thread_rng().gen_range(0, 3), MapVariantType::Ruins1 | MapVariantType::Ruins2 | MapVariantType::Ruins3 => rand::thread_rng().gen_range(0, 3), MapVariantType::Dragon | MapVariantType::DeRolLe | MapVariantType::VolOpt | MapVariantType::DarkFalz => 0, }; let minor = match map { MapVariantType::Pioneer2Ep1 => 0, MapVariantType::Forest1 => rand::thread_rng().gen_range(0, 6), MapVariantType::Forest2 => rand::thread_rng().gen_range(0, 5), MapVariantType::Caves1 | MapVariantType::Caves2 | MapVariantType::Caves3 => rand::thread_rng().gen_range(0, 2), MapVariantType::Mines1 | MapVariantType::Mines2 => rand::thread_rng().gen_range(0, 2), MapVariantType::Ruins1 | MapVariantType::Ruins2 | MapVariantType::Ruins3 => rand::thread_rng().gen_range(0, 2), MapVariantType::Dragon | MapVariantType::DeRolLe | MapVariantType::VolOpt | MapVariantType::DarkFalz => 0, }; MapVariant { map: map, mode: mode, major: major, minor: minor, } } fn dat_file(&self) -> String { match self.map { MapVariantType::Pioneer2Ep1 => "data/maps/map_city00_00e.dat".into(), MapVariantType::Forest1 => format!("data/maps/map_forest01_0{}e.dat", self.minor), MapVariantType::Forest2 => format!("data/maps/map_forest02_0{}e.dat", self.minor), MapVariantType::Caves1 => format!("data/maps/map_cave01_0{}_0{}e.dat", self.major, self.minor), MapVariantType::Caves2 => format!("data/maps/map_cave02_0{}_0{}e.dat", self.major, self.minor), MapVariantType::Caves3 => format!("data/maps/map_cave03_0{}_0{}e.dat", self.major, self.minor), MapVariantType::Mines1 => format!("data/maps/map_machine01_0{}_0{}e.dat", self.major, self.minor), MapVariantType::Mines2 => format!("data/maps/map_machine02_0{}_0{}e.dat", self.major, self.minor), MapVariantType::Ruins1 => format!("data/maps/map_ancient01_0{}_0{}e.dat", self.major, self.minor), MapVariantType::Ruins2 => format!("data/maps/map_ancient02_0{}_0{}e.dat", self.major, self.minor), MapVariantType::Ruins3 => format!("data/maps/map_ancient03_0{}_0{}e.dat", self.major, self.minor), MapVariantType::Dragon => "data/maps/map_boss01e.dat".into(), MapVariantType::DeRolLe => "data/maps/map_boss02e.dat".into(), MapVariantType::VolOpt => "data/maps/map_boss03e.dat".into(), MapVariantType::DarkFalz => "data/maps/map_boss04e.dat".into(), } } fn pkt_header(&self) -> [u8; 2] { [self.major, self.minor] } } fn enemy_data_from_map_data(path: PathBuf, episode: &Episode) -> Vec> { let mut cursor = File::open(path).unwrap(); let mut enemy_data = Vec::new(); while let Ok(enemy) = RawMapEnemy::from_byte_stream(&mut cursor) { let new_enemy = MapEnemy::from_raw(enemy, episode); enemy_data.append(&mut new_enemy .map_or(vec![None], |monster| { let mut monsters = Vec::new(); monsters.push(Some(monster)); match monster.monster { MonsterType::Monest => { for _ in 0..30 { monsters.push(Some(MapEnemy::new(MonsterType::Mothmant))); } }, MonsterType::PofuillySlime => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime))); } }, MonsterType::PanArms => { monsters.push(Some(MapEnemy::new(MonsterType::Hidoom))); monsters.push(Some(MapEnemy::new(MonsterType::Migium))); }, MonsterType::SinowBeat => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat))); } }, MonsterType::SinowGold => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::SinowGold))); } }, MonsterType::Canane => { for _ in 0..8 { monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine))); } }, MonsterType::ChaosSorcerer => { monsters.push(Some(MapEnemy::new(MonsterType::BeeR))); monsters.push(Some(MapEnemy::new(MonsterType::BeeL))); }, MonsterType::Bulclaw => { for _ in 0..4 { monsters.push(Some(MapEnemy::new(MonsterType::Claw))); } }, MonsterType::DeRolLe => { for _ in 0..10 { monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody))); } for _ in 0..9 { monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine))); } }, MonsterType::VolOptPartA => { for _ in 0..6 { monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar))); } for _ in 0..24 { monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor))); } for _ in 0..2 { monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused))); } monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp))); monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore))); monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused))); }, // TOOD: this cares about difficulty (theres an ult-specific darvant?) MonsterType::DarkFalz => { for _ in 0..509 { monsters.push(Some(MapEnemy::new(MonsterType::Darvant))); } monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3))); monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2))); monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1))); }, _ => { warn!("children: {}", enemy.children); for _ in 0..enemy.children { monsters.push(Some(MapEnemy::new(monster.monster))); } } } monsters })); } enemy_data } #[derive(Debug)] pub struct Maps { map_variants: [MapVariant; 15], enemy_data: Vec> } impl Maps { pub fn new(episode: Episode) -> Maps { warn!("new maps ep: {:?}", episode); let map_variants = match episode { Episode::One => { [MapVariant::new(MapVariantType::Pioneer2Ep1, MapVariantMode::Online), MapVariant::new(MapVariantType::Forest1, MapVariantMode::Online), MapVariant::new(MapVariantType::Forest2, MapVariantMode::Online), MapVariant::new(MapVariantType::Caves1, MapVariantMode::Online), MapVariant::new(MapVariantType::Caves2, MapVariantMode::Online), MapVariant::new(MapVariantType::Caves3, MapVariantMode::Online), MapVariant::new(MapVariantType::Mines1, MapVariantMode::Online), MapVariant::new(MapVariantType::Mines2, MapVariantMode::Online), MapVariant::new(MapVariantType::Ruins1, MapVariantMode::Online), MapVariant::new(MapVariantType::Ruins2, MapVariantMode::Online), MapVariant::new(MapVariantType::Ruins3, MapVariantMode::Online), MapVariant::new(MapVariantType::Dragon, MapVariantMode::Online), MapVariant::new(MapVariantType::DeRolLe, MapVariantMode::Online), MapVariant::new(MapVariantType::VolOpt, MapVariantMode::Online), MapVariant::new(MapVariantType::DarkFalz, MapVariantMode::Online), ] }, _ => panic!() }; 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.dat_file().into(), &episode)); enemy_data }), map_variants: map_variants, }; maps } pub fn enemy_by_id(&self, id: usize) -> MapEnemy { self.enemy_data[id].unwrap() } 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 }) } }