elseware/src/ship/quests.rs

225 lines
6.9 KiB
Rust
Raw Normal View History

2020-05-24 15:59:48 -06:00
use log::warn;
use std::collections::{HashMap, BTreeMap, BTreeSet};
use std::fs::File;
use std::io::{Read, Write, Cursor, Seek, SeekFrom};
use std::path::PathBuf;
use std::convert::{TryInto, TryFrom};
use thiserror::Error;
use serde::{Serialize, Deserialize};
use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder};
use byteorder::{LittleEndian, ReadBytesExt};
use libpso::packet::ship::QuestChunk;
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;
#[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct QuestCategory {
index: usize,
pub name: String,
pub description: String,
}
#[derive(Debug, Serialize, Deserialize, Hash)]
struct QuestListEntry {
bin: String,
dat: String,
}
#[derive(Debug, Serialize, Deserialize, Hash)]
struct QuestListCategory {
list_order: usize,
description: String,
quests: Vec<QuestListEntry>,
}
#[derive(Debug, Serialize, Deserialize)]
struct QuestListConfig {
questlist: HashMap<String, Vec<QuestListEntry>>,
}
#[derive(Error, Debug)]
#[error("")]
enum ParseDatError {
IoError(#[from] std::io::Error),
MapError(#[from] MapAreaError),
UnknownDatHeader(u32),
CouldNotDetermineEpisode,
}
const DAT_OBJECT_HEADER_ID: u32 = 1;
const DAT_ENEMY_HEADER_ID: u32 = 2;
const DAT_WAVE_HEADER_ID: u32 = 3;
enum DatBlock {
Object(Vec<Option<MapObject>>),
Enemy(Vec<Option<MapEnemy>>),
Wave,
}
fn read_dat_section_header<T: Read + Seek>(cursor: &mut T, episode: &Episode) -> Result<DatBlock, ParseDatError> {
let header = cursor.read_u32::<LittleEndian>()?;
let offset = cursor.read_u32::<LittleEndian>()?;
let area = cursor.read_u32::<LittleEndian>()?;
let length = cursor.read_u32::<LittleEndian>()?;
let map_area = MapArea::from_value(episode, area)?;
match header {
DAT_OBJECT_HEADER_ID => {
let mut obj_data = vec![0u8; length as usize];
cursor.read(&mut obj_data);
let mut obj_cursor = Cursor::new(obj_data);
let objects = objects_from_stream(&mut obj_cursor, episode, &map_area);
Ok(DatBlock::Object(objects))
},
DAT_ENEMY_HEADER_ID => {
let mut enemy_data = vec![0u8; length as usize];
cursor.read(&mut enemy_data);
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 => {
cursor.seek(SeekFrom::Current(length as i64));
Ok(DatBlock::Wave)
},
_ => Err(ParseDatError::UnknownDatHeader(header))
}
}
fn quest_episode(bin: &[u8]) -> Option<Episode> {
for bytes in bin.windows(3) {
if bytes[0] == 0xF8 && bytes[1] == 0xBC {
warn!("ep? {:?}", bytes[2]);
return Some(Episode::from_quest(bytes[2]).ok()?)
}
}
None
}
fn parse_dat(dat: &[u8], episode: &Episode) -> 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) {
Ok(dat_block) => Some(dat_block),
Err(err) => {
warn!("unknown header in dat: {:?}", err);
None
}
}
});
Ok(header_iter.fold((Vec::new(), Vec::new()), |(mut enemies, mut objects), dat_block| {
match dat_block {
DatBlock::Object(mut objs) => {
objects.append(&mut objs)
},
DatBlock::Enemy(mut enemy) => {
enemies.append(&mut enemy)
},
_ => {}
}
(enemies, objects)
}))
}
#[derive(Error, Debug)]
#[error("")]
enum QuestLoadError {
ParseDatError(#[from] ParseDatError),
}
#[derive(Debug)]
pub struct Quest {
pub name: String,
pub description: String,
pub full_description: String,
pub language: u16,
pub id: u16,
pub bin_blob: Vec<u8>,
pub dat_blob: Vec<u8>,
pub enemies: Vec<Option<MapEnemy>>,
pub objects: Vec<Option<MapObject>>,
}
impl Quest {
fn from_bin_dat(bin: Vec<u8>, dat: Vec<u8>) -> Result<Quest, QuestLoadError> {
let id = u16::from_le_bytes(bin[16..18].try_into().unwrap());
let language = u16::from_le_bytes(bin[18..20].try_into().unwrap());
let name = array_to_utf16(&bin[24..88]);
let description = array_to_utf16(&bin[88..334]);
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 mut prs_bin = LegacyPrsEncoder::new(Vec::new());
prs_bin.write(&bin);
let mut prs_dat = LegacyPrsEncoder::new(Vec::new());
prs_dat.write(&dat);
Ok(Quest {
name: name,
description: description,
full_description: full_description,
id: id,
language: language,
bin_blob: prs_bin.into_inner().unwrap(),
dat_blob: prs_dat.into_inner().unwrap(),
enemies: enemies,
objects: objects,
})
}
}
// QuestCollection
pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>;
pub fn load_quests(quest_path: PathBuf) -> QuestList {
let mut f = File::open(quest_path).unwrap();
let mut s = String::new();
f.read_to_string(&mut s);
let mut used_quest_ids = BTreeSet::new();
let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).unwrap();
ql.into_iter().map(|(category, category_details)| {
let quests = category_details.quests
.into_iter()
.filter_map(|quest| {
warn!("{:?}", quest.bin);
let dat_file = File::open(format!("data/quests/{}", quest.dat)).unwrap();
let bin_file = File::open(format!("data/quests/{}", quest.bin)).unwrap();
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).unwrap();
bin_prs.read_to_end(&mut bin).unwrap();
let quest = Quest::from_bin_dat(bin, dat).unwrap();
if used_quest_ids.contains(&quest.id) {
return None;
}
used_quest_ids.insert(quest.id);
Some(quest)
});
(QuestCategory{
index: category_details.list_order,
name: category,
description: category_details.description,
}, quests.collect())
}).collect()
}