Merge pull request 'mapareamapper' (#197) from mapareamapper into master
This commit is contained in:
commit
1727a859c5
@ -63,7 +63,6 @@ impl Into<u8> for CharacterClass {
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
|
||||
pub enum SectionID {
|
||||
Viridia,
|
||||
|
@ -1,4 +1,5 @@
|
||||
// TOOD: `pub(super) for most of these?`
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
use crate::ship::room::Episode;
|
||||
|
||||
@ -57,53 +58,55 @@ pub enum MapAreaError {
|
||||
}
|
||||
|
||||
impl MapArea {
|
||||
pub fn from_value(episode: &Episode, area: u16) -> Result<MapArea, MapAreaError> {
|
||||
match (episode, area) {
|
||||
(Episode::One, 0) => Ok(MapArea::Pioneer2Ep1),
|
||||
(Episode::One, 1) => Ok(MapArea::Forest1),
|
||||
(Episode::One, 2) => Ok(MapArea::Forest2),
|
||||
(Episode::One, 3) => Ok(MapArea::Caves1),
|
||||
(Episode::One, 4) => Ok(MapArea::Caves2),
|
||||
(Episode::One, 5) => Ok(MapArea::Caves3),
|
||||
(Episode::One, 6) => Ok(MapArea::Mines1),
|
||||
(Episode::One, 7) => Ok(MapArea::Mines2),
|
||||
(Episode::One, 8) => Ok(MapArea::Ruins1),
|
||||
(Episode::One, 9) => Ok(MapArea::Ruins2),
|
||||
(Episode::One, 10) => Ok(MapArea::Ruins3),
|
||||
(Episode::One, 11) => Ok(MapArea::Dragon),
|
||||
(Episode::One, 12) => Ok(MapArea::DeRolLe),
|
||||
(Episode::One, 13) => Ok(MapArea::VolOpt),
|
||||
(Episode::One, 14) => Ok(MapArea::DarkFalz),
|
||||
(Episode::Two, 0) => Ok(MapArea::Pioneer2Ep2),
|
||||
(Episode::Two, 1) => Ok(MapArea::VrTempleAlpha),
|
||||
(Episode::Two, 2) => Ok(MapArea::VrTempleBeta),
|
||||
(Episode::Two, 3) => Ok(MapArea::VrSpaceshipAlpha),
|
||||
(Episode::Two, 4) => Ok(MapArea::VrSpaceshipBeta),
|
||||
(Episode::Two, 5) => Ok(MapArea::Cca),
|
||||
(Episode::Two, 6) => Ok(MapArea::JungleAreaNorth),
|
||||
(Episode::Two, 7) => Ok(MapArea::JungleAreaEast),
|
||||
(Episode::Two, 8) => Ok(MapArea::Mountain),
|
||||
(Episode::Two, 9) => Ok(MapArea::Seaside),
|
||||
(Episode::Two, 10) => Ok(MapArea::SeabedUpper),
|
||||
(Episode::Two, 11) => Ok(MapArea::SeabedLower),
|
||||
(Episode::Two, 12) => Ok(MapArea::GalGryphon),
|
||||
(Episode::Two, 13) => Ok(MapArea::OlgaFlow),
|
||||
(Episode::Two, 14) => Ok(MapArea::BarbaRay),
|
||||
(Episode::Two, 15) => Ok(MapArea::GolDragon),
|
||||
(Episode::Two, 16) => Ok(MapArea::SeasideNight),
|
||||
(Episode::Two, 17) => Ok(MapArea::Tower),
|
||||
(Episode::Four, 0) => Ok(MapArea::Pioneer2Ep4),
|
||||
(Episode::Four, 1) => Ok(MapArea::CraterEast),
|
||||
(Episode::Four, 2) => Ok(MapArea::CraterWest),
|
||||
(Episode::Four, 3) => Ok(MapArea::CraterSouth),
|
||||
(Episode::Four, 4) => Ok(MapArea::CraterNorth),
|
||||
(Episode::Four, 5) => Ok(MapArea::CraterInterior),
|
||||
(Episode::Four, 6) => Ok(MapArea::SubDesert1),
|
||||
(Episode::Four, 7) => Ok(MapArea::SubDesert2),
|
||||
(Episode::Four, 8) => Ok(MapArea::SubDesert3),
|
||||
(Episode::Four, 9) => Ok(MapArea::SaintMillion),
|
||||
// (Episode::Four, 10) => Ok(MapArea::TestMapEp4),
|
||||
_ => Err(MapAreaError::UnknownMapArea(area))
|
||||
pub fn from_bb_map_designate(map_number: u8) -> Option<MapArea> {
|
||||
match map_number {
|
||||
0 => Some(MapArea::Pioneer2Ep1),
|
||||
1 => Some(MapArea::Forest1),
|
||||
2 => Some(MapArea::Forest2),
|
||||
3 => Some(MapArea::Caves1),
|
||||
4 => Some(MapArea::Caves2),
|
||||
5 => Some(MapArea::Caves3),
|
||||
6 => Some(MapArea::Mines1),
|
||||
7 => Some(MapArea::Mines2),
|
||||
8 => Some(MapArea::Ruins1),
|
||||
9 => Some(MapArea::Ruins2),
|
||||
10 => Some(MapArea::Ruins3),
|
||||
11 => Some(MapArea::Dragon),
|
||||
12 => Some(MapArea::DeRolLe),
|
||||
13 => Some(MapArea::VolOpt),
|
||||
14 => Some(MapArea::DarkFalz),
|
||||
|
||||
18 => Some(MapArea::Pioneer2Ep2),
|
||||
19 => Some(MapArea::VrTempleAlpha),
|
||||
20 => Some(MapArea::VrTempleBeta),
|
||||
21 => Some(MapArea::VrSpaceshipAlpha),
|
||||
22 => Some(MapArea::VrSpaceshipBeta),
|
||||
23 => Some(MapArea::Cca),
|
||||
24 => Some(MapArea::JungleAreaNorth),
|
||||
25 => Some(MapArea::JungleAreaEast),
|
||||
26 => Some(MapArea::Mountain),
|
||||
27 => Some(MapArea::Seaside),
|
||||
28 => Some(MapArea::SeabedUpper),
|
||||
29 => Some(MapArea::SeabedLower),
|
||||
30 => Some(MapArea::GalGryphon),
|
||||
31 => Some(MapArea::OlgaFlow),
|
||||
32 => Some(MapArea::BarbaRay),
|
||||
33 => Some(MapArea::GolDragon),
|
||||
34 => Some(MapArea::SeasideNight),
|
||||
35 => Some(MapArea::Tower),
|
||||
|
||||
45 => Some(MapArea::Pioneer2Ep4),
|
||||
36 => Some(MapArea::CraterEast),
|
||||
37 => Some(MapArea::CraterWest),
|
||||
38 => Some(MapArea::CraterSouth),
|
||||
39 => Some(MapArea::CraterNorth),
|
||||
40 => Some(MapArea::CraterInterior),
|
||||
41 => Some(MapArea::SubDesert1),
|
||||
42 => Some(MapArea::SubDesert2),
|
||||
43 => Some(MapArea::SubDesert3),
|
||||
44 => Some(MapArea::SaintMillion),
|
||||
|
||||
_=> None
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +142,7 @@ impl MapArea {
|
||||
MapArea::OlgaFlow => Some(9),
|
||||
MapArea::BarbaRay => Some(2),
|
||||
MapArea::GolDragon => Some(5),
|
||||
MapArea::SeasideNight => Some(7),
|
||||
MapArea::SeasideNight => Some(7), // TODO: this could also be 9? needs research
|
||||
MapArea::Tower => Some(9),
|
||||
|
||||
MapArea::CraterEast => Some(2),
|
||||
@ -205,3 +208,101 @@ impl MapArea {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MapAreaLookup(HashMap<u16, MapArea>);
|
||||
|
||||
impl MapAreaLookup {
|
||||
pub fn get_area_map(&self, map_area: u16) -> Result<&MapArea, MapAreaError> {
|
||||
self.0.get(&map_area).ok_or(MapAreaError::UnknownMapArea(map_area))
|
||||
}
|
||||
|
||||
fn default_ep1_maps() -> MapAreaLookup {
|
||||
let mut ep1 = HashMap::new();
|
||||
ep1.insert(0, MapArea::Pioneer2Ep1);
|
||||
ep1.insert(1, MapArea::Forest1);
|
||||
ep1.insert(2, MapArea::Forest2);
|
||||
ep1.insert(3, MapArea::Caves1);
|
||||
ep1.insert(4, MapArea::Caves2);
|
||||
ep1.insert(5, MapArea::Caves3);
|
||||
ep1.insert(6, MapArea::Mines1);
|
||||
ep1.insert(7, MapArea::Mines2);
|
||||
ep1.insert(8, MapArea::Ruins1);
|
||||
ep1.insert(9, MapArea::Ruins2);
|
||||
ep1.insert(10, MapArea::Ruins3);
|
||||
ep1.insert(11, MapArea::Dragon);
|
||||
ep1.insert(12, MapArea::DeRolLe);
|
||||
ep1.insert(13, MapArea::VolOpt);
|
||||
ep1.insert(14, MapArea::DarkFalz);
|
||||
MapAreaLookup(ep1)
|
||||
}
|
||||
|
||||
fn default_ep2_maps() -> MapAreaLookup {
|
||||
let mut ep2 = HashMap::new();
|
||||
ep2.insert(0, MapArea::Pioneer2Ep2);
|
||||
ep2.insert(1, MapArea::VrTempleAlpha);
|
||||
ep2.insert(2, MapArea::VrTempleBeta);
|
||||
ep2.insert(3, MapArea::VrSpaceshipAlpha);
|
||||
ep2.insert(4, MapArea::VrSpaceshipBeta);
|
||||
ep2.insert(5, MapArea::Cca);
|
||||
ep2.insert(6, MapArea::JungleAreaNorth);
|
||||
ep2.insert(7, MapArea::JungleAreaEast);
|
||||
ep2.insert(8, MapArea::Mountain);
|
||||
ep2.insert(9, MapArea::Seaside);
|
||||
ep2.insert(10, MapArea::SeabedUpper);
|
||||
ep2.insert(11, MapArea::SeabedLower);
|
||||
ep2.insert(12, MapArea::GalGryphon);
|
||||
ep2.insert(13, MapArea::OlgaFlow);
|
||||
ep2.insert(14, MapArea::BarbaRay);
|
||||
ep2.insert(15, MapArea::GolDragon);
|
||||
ep2.insert(16, MapArea::SeasideNight);
|
||||
ep2.insert(17, MapArea::Tower);
|
||||
MapAreaLookup(ep2)
|
||||
}
|
||||
|
||||
fn default_ep4_maps() -> MapAreaLookup {
|
||||
let mut ep4 = HashMap::new();
|
||||
ep4.insert(0, MapArea::Pioneer2Ep4);
|
||||
ep4.insert(1, MapArea::CraterEast);
|
||||
ep4.insert(2, MapArea::CraterWest);
|
||||
ep4.insert(3, MapArea::CraterSouth);
|
||||
ep4.insert(4, MapArea::CraterNorth);
|
||||
ep4.insert(5, MapArea::CraterInterior);
|
||||
ep4.insert(6, MapArea::SubDesert1);
|
||||
ep4.insert(7, MapArea::SubDesert2);
|
||||
ep4.insert(8, MapArea::SubDesert3);
|
||||
ep4.insert(9, MapArea::SaintMillion);
|
||||
MapAreaLookup(ep4)
|
||||
}
|
||||
|
||||
pub fn new(episode: &Episode) -> MapAreaLookup {
|
||||
match episode {
|
||||
Episode::One => MapAreaLookup::default_ep1_maps(),
|
||||
Episode::Two => MapAreaLookup::default_ep2_maps(),
|
||||
Episode::Four => MapAreaLookup::default_ep4_maps(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct MapAreaLookupBuilder {
|
||||
map_areas: HashMap<u16, MapArea>,
|
||||
}
|
||||
|
||||
impl MapAreaLookupBuilder {
|
||||
pub fn new() -> MapAreaLookupBuilder {
|
||||
MapAreaLookupBuilder {
|
||||
map_areas: HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(mut self, value: u16, map_area: MapArea) -> MapAreaLookupBuilder {
|
||||
self.map_areas.insert(value, map_area);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> MapAreaLookup {
|
||||
MapAreaLookup(self.map_areas)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
mod area;
|
||||
pub mod area;
|
||||
mod enemy;
|
||||
mod object;
|
||||
mod variant;
|
||||
|
@ -63,14 +63,14 @@ pub fn send_player_to_lobby(id: ClientId,
|
||||
}
|
||||
|
||||
pub async fn change_lobby<EG: EntityGateway>(id: ClientId,
|
||||
requested_lobby: u32,
|
||||
client_location: &mut ClientLocation,
|
||||
clients: &Clients,
|
||||
item_manager: &mut ItemManager,
|
||||
level_table: &CharacterLevelTable,
|
||||
ship_rooms: &mut Rooms,
|
||||
entity_gateway: &mut EG)
|
||||
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
||||
requested_lobby: u32,
|
||||
client_location: &mut ClientLocation,
|
||||
clients: &Clients,
|
||||
item_manager: &mut ItemManager,
|
||||
level_table: &CharacterLevelTable,
|
||||
ship_rooms: &mut Rooms,
|
||||
entity_gateway: &mut EG)
|
||||
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
||||
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
|
||||
let prev_area = client_location.get_area(id).map_err(|err| -> ClientLocationError {err.into()})?;
|
||||
match prev_area {
|
||||
|
@ -82,8 +82,8 @@ where
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.as_mut()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
let area = MapArea::from_value(&room.mode.episode(), player_drop_item.map_area)?;
|
||||
item_manager.player_drop_item_on_shared_floor(entity_gateway, &client.character, ClientItemId(player_drop_item.item_id), (area, player_drop_item.x, player_drop_item.y, player_drop_item.z)).await?;
|
||||
let area = room.map_areas.get_area_map(player_drop_item.map_area)?;
|
||||
item_manager.player_drop_item_on_shared_floor(entity_gateway, &client.character, ClientItemId(player_drop_item.item_id), (*area, player_drop_item.x, player_drop_item.y, player_drop_item.z)).await?;
|
||||
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let pdi = player_drop_item.clone();
|
||||
Ok(Box::new(clients_in_area.into_iter()
|
||||
@ -107,7 +107,7 @@ pub fn drop_coordinates(id: ClientId,
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
|
||||
client.item_drop_location = Some(ItemDropLocation {
|
||||
map_area: MapArea::from_value(&room.mode.episode(), drop_coordinates.map_area)?,
|
||||
map_area: *room.map_areas.get_area_map(drop_coordinates.map_area)?,
|
||||
x: drop_coordinates.x,
|
||||
z: drop_coordinates.z,
|
||||
item_id: ClientItemId(drop_coordinates.item_id),
|
||||
@ -175,18 +175,55 @@ pub fn update_player_position(id: ClientId,
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
|
||||
match &message.msg {
|
||||
GameMessage::PlayerChangedMap(p) => {client.x = p.x; client.y = p.y; client.z = p.z;},
|
||||
GameMessage::PlayerChangedMap2(p) => {client.area = MapArea::from_value(&room.mode.episode(), p.map_area).ok();},
|
||||
GameMessage::TellOtherPlayerMyLocation(p) => {client.x = p.x; client.y = p.y; client.z = p.z; client.area = MapArea::from_value(&room.mode.episode(), p.map_area).ok();},
|
||||
GameMessage::PlayerWarpingToFloor(p) => {client.area = MapArea::from_value(&room.mode.episode(), p.area as u16).ok();},
|
||||
GameMessage::PlayerTeleported(p) => {client.x = p.x; client.y = p.y; client.z = p.z;},
|
||||
GameMessage::PlayerStopped(p) => {client.x = p.x; client.y = p.y; client.z = p.z;},
|
||||
GameMessage::PlayerLoadedIn(p) => {client.x = p.x; client.y = p.y; client.z = p.z;},
|
||||
GameMessage::PlayerWalking(p) => {client.x = p.x; client.z = p.z;},
|
||||
GameMessage::PlayerRunning(p) => {client.x = p.x; client.z = p.z;},
|
||||
GameMessage::PlayerWarped(p) => {client.x = p.x; client.y = p.y;},
|
||||
GameMessage::PlayerChangedMap(p) => {
|
||||
client.x = p.x;
|
||||
client.y = p.y;
|
||||
client.z = p.z;
|
||||
},
|
||||
GameMessage::PlayerChangedMap2(p) => {
|
||||
client.area = room.map_areas.get_area_map(p.map_area).ok().cloned();
|
||||
},
|
||||
GameMessage::TellOtherPlayerMyLocation(p) => {
|
||||
client.x = p.x;
|
||||
client.y = p.y;
|
||||
client.z = p.z;
|
||||
client.area = room.map_areas.get_area_map(p.map_area).ok().cloned();
|
||||
},
|
||||
GameMessage::PlayerWarpingToFloor(p) => {
|
||||
client.area = room.map_areas.get_area_map(p.area as u16).ok().cloned();
|
||||
},
|
||||
GameMessage::PlayerTeleported(p) => {
|
||||
client.x = p.x;
|
||||
client.y = p.y;
|
||||
client.z = p.z;
|
||||
},
|
||||
GameMessage::PlayerStopped(p) => {
|
||||
client.x = p.x;
|
||||
client.y = p.y;
|
||||
client.z = p.z;
|
||||
},
|
||||
GameMessage::PlayerLoadedIn(p) => {
|
||||
client.x = p.x;
|
||||
client.y = p.y;
|
||||
client.z = p.z;
|
||||
},
|
||||
GameMessage::PlayerWalking(p) => {
|
||||
client.x = p.x;
|
||||
client.z = p.z;
|
||||
},
|
||||
GameMessage::PlayerRunning(p) => {
|
||||
client.x = p.x;
|
||||
client.z = p.z;
|
||||
},
|
||||
GameMessage::PlayerWarped(p) => {
|
||||
client.x = p.x;
|
||||
client.y = p.y;
|
||||
},
|
||||
// GameMessage::PlayerChangedFloor(p) => {client.area = MapArea::from_value(&room.mode.episode(), p.map).ok();},
|
||||
GameMessage::InitializeSpeechNpc(p) => {client.x = p.x; client.z = p.z;}
|
||||
GameMessage::InitializeSpeechNpc(p) => {
|
||||
client.x = p.x;
|
||||
client.z = p.z;
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
} else {}
|
||||
|
@ -84,6 +84,7 @@ pub fn load_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quests: &Ques
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone());
|
||||
room.map_areas = quest.map_areas.clone();
|
||||
|
||||
let bin = quest::quest_header(questmenuselect, &quest.bin_blob, "bin");
|
||||
let dat = quest::quest_header(questmenuselect, &quest.dat_blob, "dat");
|
||||
|
@ -11,6 +11,7 @@ use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use libpso::util::array_to_utf16;
|
||||
use crate::ship::map::{MapArea, MapAreaError, MapObject, MapEnemy, enemy_data_from_stream, objects_from_stream};
|
||||
use crate::ship::room::Episode;
|
||||
use crate::ship::map::area::{MapAreaLookup, MapAreaLookupBuilder};
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@ -46,6 +47,7 @@ pub enum ParseDatError {
|
||||
MapError(#[from] MapAreaError),
|
||||
UnknownDatHeader(u32),
|
||||
CouldNotDetermineEpisode,
|
||||
InvalidMapAreaId(u16),
|
||||
}
|
||||
|
||||
const DAT_OBJECT_HEADER_ID: u32 = 1;
|
||||
@ -59,14 +61,14 @@ enum DatBlock {
|
||||
}
|
||||
|
||||
|
||||
fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode) -> Result<DatBlock, ParseDatError> {
|
||||
fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode, map_areas: &MapAreaLookup) -> Result<DatBlock, ParseDatError> {
|
||||
let header = cursor.read_u32::<LittleEndian>()?;
|
||||
let _offset = cursor.read_u32::<LittleEndian>()?;
|
||||
let area = cursor.read_u16::<LittleEndian>()?;
|
||||
let _unknown1 = cursor.read_u16::<LittleEndian>()?;
|
||||
let length = cursor.read_u32::<LittleEndian>()?;
|
||||
|
||||
let map_area = MapArea::from_value(episode, area)?;
|
||||
let map_area = map_areas.get_area_map(area).map_err(|_| ParseDatError::InvalidMapAreaId(area))?;
|
||||
|
||||
match header {
|
||||
DAT_OBJECT_HEADER_ID => {
|
||||
@ -83,7 +85,7 @@ fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode) ->
|
||||
let mut enemy_cursor = Cursor::new(enemy_data);
|
||||
|
||||
let enemies = enemy_data_from_stream(&mut enemy_cursor, &map_area, episode);
|
||||
|
||||
|
||||
Ok(DatBlock::Enemy(enemies))
|
||||
},
|
||||
DAT_WAVE_HEADER_ID => {
|
||||
@ -96,6 +98,7 @@ fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode) ->
|
||||
|
||||
fn quest_episode(bin: &[u8]) -> Option<Episode> {
|
||||
for bytes in bin.windows(3) {
|
||||
// set_episode
|
||||
if bytes[0] == 0xF8 && bytes[1] == 0xBC {
|
||||
return Some(Episode::from_quest(bytes[2]).ok()?)
|
||||
}
|
||||
@ -103,11 +106,26 @@ fn quest_episode(bin: &[u8]) -> Option<Episode> {
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_dat(dat: &[u8], episode: &Episode) -> Result<(Vec<Option<MapEnemy>>, Vec<Option<MapObject>>), ParseDatError> {
|
||||
fn map_area_mappings(bin: &[u8]) -> MapAreaLookup {
|
||||
let mut map_areas = MapAreaLookupBuilder::new();
|
||||
for bytes in bin.windows(4) {
|
||||
// BB_Map_Designate
|
||||
if bytes[0] == 0xF9 && bytes[1] == 0x51 {
|
||||
//return Some(Episode::from_quest(bytes[2]).ok()?)
|
||||
let floor_value = bytes[2] as u16;
|
||||
if let Some(map_area) = MapArea::from_bb_map_designate(bytes[3]) {
|
||||
map_areas = map_areas.add(floor_value, map_area);
|
||||
}
|
||||
}
|
||||
}
|
||||
map_areas.build()
|
||||
}
|
||||
|
||||
fn parse_dat(dat: &[u8], episode: &Episode, map_areas: &MapAreaLookup) -> Result<(Vec<Option<MapEnemy>>, Vec<Option<MapObject>>), ParseDatError> {
|
||||
let mut cursor = Cursor::new(dat);
|
||||
|
||||
let header_iter = std::iter::from_fn(move || {
|
||||
match read_dat_section_header(&mut cursor, episode) {
|
||||
match read_dat_section_header(&mut cursor, episode, map_areas) {
|
||||
Ok(dat_block) => Some(dat_block),
|
||||
Err(err) => {
|
||||
warn!("unknown header in dat: {:?}", err);
|
||||
@ -126,7 +144,7 @@ fn parse_dat(dat: &[u8], episode: &Episode) -> Result<(Vec<Option<MapEnemy>>, Ve
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
||||
(enemies, objects)
|
||||
}))
|
||||
}
|
||||
@ -151,6 +169,7 @@ pub struct Quest {
|
||||
pub dat_blob: Vec<u8>,
|
||||
pub enemies: Vec<Option<MapEnemy>>,
|
||||
pub objects: Vec<Option<MapObject>>,
|
||||
pub map_areas: MapAreaLookup,
|
||||
}
|
||||
|
||||
impl Quest {
|
||||
@ -162,7 +181,8 @@ impl Quest {
|
||||
let full_description = array_to_utf16(&bin[334..920]);
|
||||
|
||||
let episode = quest_episode(&bin).ok_or(ParseDatError::CouldNotDetermineEpisode)?;
|
||||
let (enemies, objects) = parse_dat(&dat, &episode)?;
|
||||
let map_areas = map_area_mappings(&bin);
|
||||
let (enemies, objects) = parse_dat(&dat, &episode, &map_areas)?;
|
||||
|
||||
let mut prs_bin = LegacyPrsEncoder::new(Vec::new());
|
||||
prs_bin.write(&bin)?;
|
||||
@ -179,6 +199,7 @@ impl Quest {
|
||||
dat_blob: prs_dat.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?,
|
||||
enemies: enemies,
|
||||
objects: objects,
|
||||
map_areas: map_areas,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -186,6 +207,31 @@ impl Quest {
|
||||
// QuestCollection
|
||||
pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>;
|
||||
|
||||
pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf) -> Option<Quest> {
|
||||
let dat_file = File::open(PathBuf::from("data/quests/").join(dat_path.clone()))
|
||||
.map_err(|err| {
|
||||
warn!("could not load quest file {:?}: {:?}", dat_path, err)
|
||||
}).ok()?;
|
||||
//let bin_file = File::open(format!("data/quests/{}", bin_path))
|
||||
let bin_file = File::open(PathBuf::from("data/quests/").join(bin_path.clone()))
|
||||
.map_err(|err| {
|
||||
warn!("could not load quest file {:?}: {:?}", bin_path, err)
|
||||
}).ok()?;
|
||||
let mut dat_prs = LegacyPrsDecoder::new(dat_file);
|
||||
let mut bin_prs = LegacyPrsDecoder::new(bin_file);
|
||||
|
||||
let mut dat = Vec::new();
|
||||
let mut bin = Vec::new();
|
||||
dat_prs.read_to_end(&mut dat).ok()?;
|
||||
bin_prs.read_to_end(&mut bin).ok()?;
|
||||
|
||||
let quest = Quest::from_bin_dat(bin, dat).map_err(|err| {
|
||||
warn!("could not parse quest file {:?}/{:?}: {:?}", bin_path, dat_path, err)
|
||||
}).ok()?;
|
||||
Some(quest)
|
||||
}
|
||||
|
||||
|
||||
pub fn load_quests(quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
|
||||
let mut f = File::open(quest_path).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
|
||||
let mut s = String::new();
|
||||
@ -198,31 +244,15 @@ pub fn load_quests(quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
|
||||
let quests = category_details.quests
|
||||
.into_iter()
|
||||
.filter_map(|quest| {
|
||||
let dat_file = File::open(format!("data/quests/{}", quest.dat))
|
||||
.map_err(|err| {
|
||||
warn!("could not load quest file {}: {:?}", quest.dat, err)
|
||||
}).ok()?;
|
||||
let bin_file = File::open(format!("data/quests/{}", quest.bin))
|
||||
.map_err(|err| {
|
||||
warn!("could not load quest file {}: {:?}", quest.bin, err)
|
||||
}).ok()?;
|
||||
let mut dat_prs = LegacyPrsDecoder::new(dat_file);
|
||||
let mut bin_prs = LegacyPrsDecoder::new(bin_file);
|
||||
|
||||
let mut dat = Vec::new();
|
||||
let mut bin = Vec::new();
|
||||
dat_prs.read_to_end(&mut dat).ok()?;
|
||||
bin_prs.read_to_end(&mut bin).ok()?;
|
||||
|
||||
let quest = Quest::from_bin_dat(bin, dat).map_err(|err| {
|
||||
warn!("could not parse quest file {}/{}: {:?}", quest.bin, quest.dat, err)
|
||||
}).ok()?;
|
||||
if used_quest_ids.contains(&quest.id) {
|
||||
warn!("quest id already exists: {}", quest.id);
|
||||
return None;
|
||||
}
|
||||
used_quest_ids.insert(quest.id);
|
||||
Some(quest)
|
||||
load_quest(quest.bin.into(), quest.dat.into())
|
||||
.and_then(|quest | {
|
||||
if used_quest_ids.contains(&quest.id) {
|
||||
warn!("quest id already exists: {}", quest.id);
|
||||
return None;
|
||||
}
|
||||
used_quest_ids.insert(quest.id);
|
||||
Some(quest)
|
||||
})
|
||||
});
|
||||
(QuestCategory{
|
||||
index: category_details.list_order,
|
||||
@ -231,3 +261,31 @@ pub fn load_quests(quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
|
||||
}, quests.collect())
|
||||
}).collect())
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
// the quest phantasmal world 4 uses the tower map twice, to do this it had to remap
|
||||
// one of the other maps to be a second tower
|
||||
#[test]
|
||||
fn test_quest_with_remapped_floors() {
|
||||
let pw4 = load_quest("q236-ext-bb.bin".into(), "q236-ext-bb.dat".into()).unwrap();
|
||||
let enemies_not_in_tower = pw4.enemies.iter()
|
||||
.filter(|enemy| {
|
||||
enemy.is_some()
|
||||
})
|
||||
.filter(|enemy| {
|
||||
enemy.unwrap().map_area != MapArea::Tower
|
||||
});
|
||||
assert!(enemies_not_in_tower.count() == 0);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use crate::ship::map::Maps;
|
||||
use crate::ship::drops::DropTable;
|
||||
use crate::entity::character::SectionID;
|
||||
use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
|
||||
use crate::ship::map::area::MapAreaLookup;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RoomCreationError {
|
||||
@ -164,6 +165,7 @@ pub struct RoomState {
|
||||
pub random_seed: u32,
|
||||
pub bursting: bool,
|
||||
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
|
||||
pub map_areas: MapAreaLookup,
|
||||
// items on ground
|
||||
// enemy info
|
||||
}
|
||||
@ -238,6 +240,7 @@ impl RoomState {
|
||||
section_id: section_id,
|
||||
drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)),
|
||||
bursting: false,
|
||||
map_areas: MapAreaLookup::new(&room_mode.episode()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user