Browse Source

handle quests remapping floor ids to different areas

pbs
jake 4 years ago
parent
commit
ed1324b811
  1. 198
      src/ship/map/area.rs
  2. 2
      src/ship/map/mod.rs
  3. 6
      src/ship/packet/handler/message.rs
  4. 1
      src/ship/packet/handler/quest.rs
  5. 122
      src/ship/quests.rs
  6. 3
      src/ship/room.rs

198
src/ship/map/area.rs

@ -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> {
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::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,102 @@ impl MapArea {
} }
} }
} }
// TODO: rename this to something less silly
#[derive(Debug, Clone)]
pub struct MapAreaMapper(HashMap<u16, MapArea>);
impl MapAreaMapper {
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() -> MapAreaMapper {
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);
MapAreaMapper(ep1)
}
fn default_ep2_maps() -> MapAreaMapper {
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);
MapAreaMapper(ep2)
}
fn default_ep4_maps() -> MapAreaMapper {
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);
MapAreaMapper(ep4)
}
pub fn new(episode: &Episode) -> MapAreaMapper {
match episode {
Episode::One => MapAreaMapper::default_ep1_maps(),
Episode::Two => MapAreaMapper::default_ep2_maps(),
Episode::Four => MapAreaMapper::default_ep4_maps(),
}
}
}
pub struct MapAreaMapperBuilder {
map_areas: HashMap<u16, MapArea>,
}
impl MapAreaMapperBuilder {
pub fn new() -> MapAreaMapperBuilder {
MapAreaMapperBuilder {
map_areas: HashMap::new()
}
}
pub fn add(mut self, value: u16, map_area: MapArea) -> MapAreaMapperBuilder {
self.map_areas.insert(value, map_area);
self
}
pub fn build(self) -> MapAreaMapper {
MapAreaMapper(self.map_areas)
}
}

2
src/ship/map/mod.rs

@ -1,4 +1,4 @@
mod area;
pub mod area;
mod enemy; mod enemy;
mod object; mod object;
mod variant; mod variant;

6
src/ship/packet/handler/message.rs

@ -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)?;
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 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),

1
src/ship/packet/handler/quest.rs

@ -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");

122
src/ship/quests.rs

@ -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::{MapAreaMapper, MapAreaMapperBuilder};
#[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: &MapAreaMapper) -> 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 => {
@ -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 mut enemy_cursor = Cursor::new(enemy_data);
let enemies = enemy_data_from_stream(&mut enemy_cursor, &map_area, episode); let enemies = enemy_data_from_stream(&mut enemy_cursor, &map_area, episode);
Ok(DatBlock::Enemy(enemies)) Ok(DatBlock::Enemy(enemies))
}, },
DAT_WAVE_HEADER_ID => { 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> { 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]) -> MapAreaMapper {
let mut map_areas = MapAreaMapperBuilder::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: &MapAreaMapper) -> 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);
@ -126,7 +144,7 @@ fn parse_dat(dat: &[u8], episode: &Episode) -> Result<(Vec<Option<MapEnemy>>, Ve
}, },
_ => {} _ => {}
} }
(enemies, objects) (enemies, objects)
})) }))
} }
@ -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: MapAreaMapper,
} }
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))
.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{ (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);
}
}

3
src/ship/room.rs

@ -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::MapAreaMapper;
#[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: MapAreaMapper,
// 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: MapAreaMapper::new(&room_mode.episode()),
}) })
} }
} }
Loading…
Cancel
Save