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)]
|
#[derive(Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
|
||||||
pub enum SectionID {
|
pub enum SectionID {
|
||||||
Viridia,
|
Viridia,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// TOOD: `pub(super) for most of these?`
|
// TOOD: `pub(super) for most of these?`
|
||||||
|
use std::collections::HashMap;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use crate::ship::room::Episode;
|
use crate::ship::room::Episode;
|
||||||
|
|
||||||
@ -57,53 +58,55 @@ pub enum MapAreaError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MapArea {
|
impl MapArea {
|
||||||
pub fn from_value(episode: &Episode, area: u16) -> Result<MapArea, MapAreaError> {
|
pub fn from_bb_map_designate(map_number: u8) -> Option<MapArea> {
|
||||||
match (episode, area) {
|
match map_number {
|
||||||
(Episode::One, 0) => Ok(MapArea::Pioneer2Ep1),
|
0 => Some(MapArea::Pioneer2Ep1),
|
||||||
(Episode::One, 1) => Ok(MapArea::Forest1),
|
1 => Some(MapArea::Forest1),
|
||||||
(Episode::One, 2) => Ok(MapArea::Forest2),
|
2 => Some(MapArea::Forest2),
|
||||||
(Episode::One, 3) => Ok(MapArea::Caves1),
|
3 => Some(MapArea::Caves1),
|
||||||
(Episode::One, 4) => Ok(MapArea::Caves2),
|
4 => Some(MapArea::Caves2),
|
||||||
(Episode::One, 5) => Ok(MapArea::Caves3),
|
5 => Some(MapArea::Caves3),
|
||||||
(Episode::One, 6) => Ok(MapArea::Mines1),
|
6 => Some(MapArea::Mines1),
|
||||||
(Episode::One, 7) => Ok(MapArea::Mines2),
|
7 => Some(MapArea::Mines2),
|
||||||
(Episode::One, 8) => Ok(MapArea::Ruins1),
|
8 => Some(MapArea::Ruins1),
|
||||||
(Episode::One, 9) => Ok(MapArea::Ruins2),
|
9 => Some(MapArea::Ruins2),
|
||||||
(Episode::One, 10) => Ok(MapArea::Ruins3),
|
10 => Some(MapArea::Ruins3),
|
||||||
(Episode::One, 11) => Ok(MapArea::Dragon),
|
11 => Some(MapArea::Dragon),
|
||||||
(Episode::One, 12) => Ok(MapArea::DeRolLe),
|
12 => Some(MapArea::DeRolLe),
|
||||||
(Episode::One, 13) => Ok(MapArea::VolOpt),
|
13 => Some(MapArea::VolOpt),
|
||||||
(Episode::One, 14) => Ok(MapArea::DarkFalz),
|
14 => Some(MapArea::DarkFalz),
|
||||||
(Episode::Two, 0) => Ok(MapArea::Pioneer2Ep2),
|
|
||||||
(Episode::Two, 1) => Ok(MapArea::VrTempleAlpha),
|
18 => Some(MapArea::Pioneer2Ep2),
|
||||||
(Episode::Two, 2) => Ok(MapArea::VrTempleBeta),
|
19 => Some(MapArea::VrTempleAlpha),
|
||||||
(Episode::Two, 3) => Ok(MapArea::VrSpaceshipAlpha),
|
20 => Some(MapArea::VrTempleBeta),
|
||||||
(Episode::Two, 4) => Ok(MapArea::VrSpaceshipBeta),
|
21 => Some(MapArea::VrSpaceshipAlpha),
|
||||||
(Episode::Two, 5) => Ok(MapArea::Cca),
|
22 => Some(MapArea::VrSpaceshipBeta),
|
||||||
(Episode::Two, 6) => Ok(MapArea::JungleAreaNorth),
|
23 => Some(MapArea::Cca),
|
||||||
(Episode::Two, 7) => Ok(MapArea::JungleAreaEast),
|
24 => Some(MapArea::JungleAreaNorth),
|
||||||
(Episode::Two, 8) => Ok(MapArea::Mountain),
|
25 => Some(MapArea::JungleAreaEast),
|
||||||
(Episode::Two, 9) => Ok(MapArea::Seaside),
|
26 => Some(MapArea::Mountain),
|
||||||
(Episode::Two, 10) => Ok(MapArea::SeabedUpper),
|
27 => Some(MapArea::Seaside),
|
||||||
(Episode::Two, 11) => Ok(MapArea::SeabedLower),
|
28 => Some(MapArea::SeabedUpper),
|
||||||
(Episode::Two, 12) => Ok(MapArea::GalGryphon),
|
29 => Some(MapArea::SeabedLower),
|
||||||
(Episode::Two, 13) => Ok(MapArea::OlgaFlow),
|
30 => Some(MapArea::GalGryphon),
|
||||||
(Episode::Two, 14) => Ok(MapArea::BarbaRay),
|
31 => Some(MapArea::OlgaFlow),
|
||||||
(Episode::Two, 15) => Ok(MapArea::GolDragon),
|
32 => Some(MapArea::BarbaRay),
|
||||||
(Episode::Two, 16) => Ok(MapArea::SeasideNight),
|
33 => Some(MapArea::GolDragon),
|
||||||
(Episode::Two, 17) => Ok(MapArea::Tower),
|
34 => Some(MapArea::SeasideNight),
|
||||||
(Episode::Four, 0) => Ok(MapArea::Pioneer2Ep4),
|
35 => Some(MapArea::Tower),
|
||||||
(Episode::Four, 1) => Ok(MapArea::CraterEast),
|
|
||||||
(Episode::Four, 2) => Ok(MapArea::CraterWest),
|
45 => Some(MapArea::Pioneer2Ep4),
|
||||||
(Episode::Four, 3) => Ok(MapArea::CraterSouth),
|
36 => Some(MapArea::CraterEast),
|
||||||
(Episode::Four, 4) => Ok(MapArea::CraterNorth),
|
37 => Some(MapArea::CraterWest),
|
||||||
(Episode::Four, 5) => Ok(MapArea::CraterInterior),
|
38 => Some(MapArea::CraterSouth),
|
||||||
(Episode::Four, 6) => Ok(MapArea::SubDesert1),
|
39 => Some(MapArea::CraterNorth),
|
||||||
(Episode::Four, 7) => Ok(MapArea::SubDesert2),
|
40 => Some(MapArea::CraterInterior),
|
||||||
(Episode::Four, 8) => Ok(MapArea::SubDesert3),
|
41 => Some(MapArea::SubDesert1),
|
||||||
(Episode::Four, 9) => Ok(MapArea::SaintMillion),
|
42 => Some(MapArea::SubDesert2),
|
||||||
// (Episode::Four, 10) => Ok(MapArea::TestMapEp4),
|
43 => Some(MapArea::SubDesert3),
|
||||||
_ => Err(MapAreaError::UnknownMapArea(area))
|
44 => Some(MapArea::SaintMillion),
|
||||||
|
|
||||||
|
_=> None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +142,7 @@ impl MapArea {
|
|||||||
MapArea::OlgaFlow => Some(9),
|
MapArea::OlgaFlow => Some(9),
|
||||||
MapArea::BarbaRay => Some(2),
|
MapArea::BarbaRay => Some(2),
|
||||||
MapArea::GolDragon => Some(5),
|
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::Tower => Some(9),
|
||||||
|
|
||||||
MapArea::CraterEast => Some(2),
|
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 enemy;
|
||||||
mod object;
|
mod object;
|
||||||
mod variant;
|
mod variant;
|
||||||
|
@ -63,14 +63,14 @@ pub fn send_player_to_lobby(id: ClientId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn change_lobby<EG: EntityGateway>(id: ClientId,
|
pub async fn change_lobby<EG: EntityGateway>(id: ClientId,
|
||||||
requested_lobby: u32,
|
requested_lobby: u32,
|
||||||
client_location: &mut ClientLocation,
|
client_location: &mut ClientLocation,
|
||||||
clients: &Clients,
|
clients: &Clients,
|
||||||
item_manager: &mut ItemManager,
|
item_manager: &mut ItemManager,
|
||||||
level_table: &CharacterLevelTable,
|
level_table: &CharacterLevelTable,
|
||||||
ship_rooms: &mut Rooms,
|
ship_rooms: &mut Rooms,
|
||||||
entity_gateway: &mut EG)
|
entity_gateway: &mut EG)
|
||||||
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
||||||
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
|
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
|
||||||
let prev_area = client_location.get_area(id).map_err(|err| -> ClientLocationError {err.into()})?;
|
let prev_area = client_location.get_area(id).map_err(|err| -> ClientLocationError {err.into()})?;
|
||||||
match prev_area {
|
match prev_area {
|
||||||
|
@ -82,8 +82,8 @@ where
|
|||||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||||
let area = MapArea::from_value(&room.mode.episode(), player_drop_item.map_area)?;
|
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?;
|
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 clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||||
let pdi = player_drop_item.clone();
|
let pdi = player_drop_item.clone();
|
||||||
Ok(Box::new(clients_in_area.into_iter()
|
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))?;
|
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||||
|
|
||||||
client.item_drop_location = Some(ItemDropLocation {
|
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,
|
x: drop_coordinates.x,
|
||||||
z: drop_coordinates.z,
|
z: drop_coordinates.z,
|
||||||
item_id: ClientItemId(drop_coordinates.item_id),
|
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))?;
|
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||||
|
|
||||||
match &message.msg {
|
match &message.msg {
|
||||||
GameMessage::PlayerChangedMap(p) => {client.x = p.x; client.y = p.y; client.z = p.z;},
|
GameMessage::PlayerChangedMap(p) => {
|
||||||
GameMessage::PlayerChangedMap2(p) => {client.area = MapArea::from_value(&room.mode.episode(), p.map_area).ok();},
|
client.x = p.x;
|
||||||
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();},
|
client.y = p.y;
|
||||||
GameMessage::PlayerWarpingToFloor(p) => {client.area = MapArea::from_value(&room.mode.episode(), p.area as u16).ok();},
|
client.z = p.z;
|
||||||
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::PlayerChangedMap2(p) => {
|
||||||
GameMessage::PlayerLoadedIn(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::PlayerWalking(p) => {client.x = p.x; client.z = p.z;},
|
},
|
||||||
GameMessage::PlayerRunning(p) => {client.x = p.x; client.z = p.z;},
|
GameMessage::TellOtherPlayerMyLocation(p) => {
|
||||||
GameMessage::PlayerWarped(p) => {client.x = p.x; client.y = p.y;},
|
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::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 {}
|
} 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))?.as_mut()
|
||||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||||
room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone());
|
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 bin = quest::quest_header(questmenuselect, &quest.bin_blob, "bin");
|
||||||
let dat = quest::quest_header(questmenuselect, &quest.dat_blob, "dat");
|
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 libpso::util::array_to_utf16;
|
||||||
use crate::ship::map::{MapArea, MapAreaError, MapObject, MapEnemy, enemy_data_from_stream, objects_from_stream};
|
use crate::ship::map::{MapArea, MapAreaError, MapObject, MapEnemy, enemy_data_from_stream, objects_from_stream};
|
||||||
use crate::ship::room::Episode;
|
use crate::ship::room::Episode;
|
||||||
|
use crate::ship::map::area::{MapAreaLookup, MapAreaLookupBuilder};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@ -46,6 +47,7 @@ pub enum ParseDatError {
|
|||||||
MapError(#[from] MapAreaError),
|
MapError(#[from] MapAreaError),
|
||||||
UnknownDatHeader(u32),
|
UnknownDatHeader(u32),
|
||||||
CouldNotDetermineEpisode,
|
CouldNotDetermineEpisode,
|
||||||
|
InvalidMapAreaId(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
const DAT_OBJECT_HEADER_ID: u32 = 1;
|
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 header = cursor.read_u32::<LittleEndian>()?;
|
||||||
let _offset = cursor.read_u32::<LittleEndian>()?;
|
let _offset = cursor.read_u32::<LittleEndian>()?;
|
||||||
let area = cursor.read_u16::<LittleEndian>()?;
|
let area = cursor.read_u16::<LittleEndian>()?;
|
||||||
let _unknown1 = cursor.read_u16::<LittleEndian>()?;
|
let _unknown1 = cursor.read_u16::<LittleEndian>()?;
|
||||||
let length = cursor.read_u32::<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 {
|
match header {
|
||||||
DAT_OBJECT_HEADER_ID => {
|
DAT_OBJECT_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> {
|
fn quest_episode(bin: &[u8]) -> Option<Episode> {
|
||||||
for bytes in bin.windows(3) {
|
for bytes in bin.windows(3) {
|
||||||
|
// set_episode
|
||||||
if bytes[0] == 0xF8 && bytes[1] == 0xBC {
|
if bytes[0] == 0xF8 && bytes[1] == 0xBC {
|
||||||
return Some(Episode::from_quest(bytes[2]).ok()?)
|
return Some(Episode::from_quest(bytes[2]).ok()?)
|
||||||
}
|
}
|
||||||
@ -103,11 +106,26 @@ fn quest_episode(bin: &[u8]) -> Option<Episode> {
|
|||||||
None
|
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 mut cursor = Cursor::new(dat);
|
||||||
|
|
||||||
let header_iter = std::iter::from_fn(move || {
|
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),
|
Ok(dat_block) => Some(dat_block),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("unknown header in dat: {:?}", err);
|
warn!("unknown header in dat: {:?}", err);
|
||||||
@ -151,6 +169,7 @@ pub struct Quest {
|
|||||||
pub dat_blob: Vec<u8>,
|
pub dat_blob: Vec<u8>,
|
||||||
pub enemies: Vec<Option<MapEnemy>>,
|
pub enemies: Vec<Option<MapEnemy>>,
|
||||||
pub objects: Vec<Option<MapObject>>,
|
pub objects: Vec<Option<MapObject>>,
|
||||||
|
pub map_areas: MapAreaLookup,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Quest {
|
impl Quest {
|
||||||
@ -162,7 +181,8 @@ impl Quest {
|
|||||||
let full_description = array_to_utf16(&bin[334..920]);
|
let full_description = array_to_utf16(&bin[334..920]);
|
||||||
|
|
||||||
let episode = quest_episode(&bin).ok_or(ParseDatError::CouldNotDetermineEpisode)?;
|
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());
|
let mut prs_bin = LegacyPrsEncoder::new(Vec::new());
|
||||||
prs_bin.write(&bin)?;
|
prs_bin.write(&bin)?;
|
||||||
@ -179,6 +199,7 @@ impl Quest {
|
|||||||
dat_blob: prs_dat.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?,
|
dat_blob: prs_dat.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?,
|
||||||
enemies: enemies,
|
enemies: enemies,
|
||||||
objects: objects,
|
objects: objects,
|
||||||
|
map_areas: map_areas,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,6 +207,31 @@ impl Quest {
|
|||||||
// QuestCollection
|
// QuestCollection
|
||||||
pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>;
|
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> {
|
pub fn load_quests(quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
|
||||||
let mut f = File::open(quest_path).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
|
let mut f = File::open(quest_path).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
@ -198,31 +244,15 @@ pub fn load_quests(quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
|
|||||||
let quests = category_details.quests
|
let quests = category_details.quests
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|quest| {
|
.filter_map(|quest| {
|
||||||
let dat_file = File::open(format!("data/quests/{}", quest.dat))
|
load_quest(quest.bin.into(), quest.dat.into())
|
||||||
.map_err(|err| {
|
.and_then(|quest | {
|
||||||
warn!("could not load quest file {}: {:?}", quest.dat, err)
|
if used_quest_ids.contains(&quest.id) {
|
||||||
}).ok()?;
|
warn!("quest id already exists: {}", quest.id);
|
||||||
let bin_file = File::open(format!("data/quests/{}", quest.bin))
|
return None;
|
||||||
.map_err(|err| {
|
}
|
||||||
warn!("could not load quest file {}: {:?}", quest.bin, err)
|
used_quest_ids.insert(quest.id);
|
||||||
}).ok()?;
|
Some(quest)
|
||||||
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)
|
|
||||||
});
|
});
|
||||||
(QuestCategory{
|
(QuestCategory{
|
||||||
index: category_details.list_order,
|
index: category_details.list_order,
|
||||||
@ -231,3 +261,31 @@ pub fn load_quests(quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
|
|||||||
}, quests.collect())
|
}, quests.collect())
|
||||||
}).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::ship::drops::DropTable;
|
||||||
use crate::entity::character::SectionID;
|
use crate::entity::character::SectionID;
|
||||||
use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
|
use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
|
||||||
|
use crate::ship::map::area::MapAreaLookup;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RoomCreationError {
|
pub enum RoomCreationError {
|
||||||
@ -164,6 +165,7 @@ pub struct RoomState {
|
|||||||
pub random_seed: u32,
|
pub random_seed: u32,
|
||||||
pub bursting: bool,
|
pub bursting: bool,
|
||||||
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
|
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
|
||||||
|
pub map_areas: MapAreaLookup,
|
||||||
// items on ground
|
// items on ground
|
||||||
// enemy info
|
// enemy info
|
||||||
}
|
}
|
||||||
@ -238,6 +240,7 @@ impl RoomState {
|
|||||||
section_id: section_id,
|
section_id: section_id,
|
||||||
drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)),
|
drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)),
|
||||||
bursting: false,
|
bursting: false,
|
||||||
|
map_areas: MapAreaLookup::new(&room_mode.episode()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user