Browse Source

quests!

pbs
jake 4 years ago
parent
commit
35dd503d14
  1. 1
      Cargo.toml
  2. 204
      src/ship/map.rs
  3. 1
      src/ship/mod.rs
  4. 1
      src/ship/packet/builder/mod.rs
  5. 47
      src/ship/packet/builder/quest.rs
  6. 1
      src/ship/packet/handler/mod.rs
  7. 209
      src/ship/packet/handler/quest.rs
  8. 26
      src/ship/packet/handler/room.rs
  9. 224
      src/ship/quests.rs
  10. 11
      src/ship/room.rs
  11. 88
      src/ship/ship.rs

1
Cargo.toml

@ -39,4 +39,5 @@ byteorder = "1"
enum-utils = "0.1.2"
derive_more = { version = "0.99.3", features = ["display"]}
thiserror = "1.0.15"
ages-prs = "0.1"

204
src/ship/map.rs

@ -524,112 +524,123 @@ impl MapVariant {
}
fn objects_from_map_data(path: PathBuf, episode: &Episode, map_variant: &MapVariant) -> Vec<Option<MapObject>> {
let mut cursor = File::open(path.clone()).unwrap();
pub fn objects_from_stream(cursor: &mut impl Read, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> {
let mut object_data = Vec::new();
while let Ok(raw_object) = RawMapObject::from_byte_stream(&mut cursor) {
let object = MapObject::from_raw(raw_object.clone(), *episode, &map_variant.map);
while let Ok(raw_object) = RawMapObject::from_byte_stream(cursor) {
let object = MapObject::from_raw(raw_object.clone(), *episode, map_area);
object_data.push(object.ok());
}
object_data
}
fn objects_from_map_data(path: PathBuf, episode: &Episode, map_area: &MapArea) -> Vec<Option<MapObject>> {
let mut cursor = File::open(path.clone()).unwrap();
objects_from_stream(&mut cursor, episode, map_area)
}
fn enemy_data_from_map_data(map_variant: &MapVariant, episode: &Episode) -> Vec<Option<MapEnemy>> {
let path = map_variant.dat_file();
let mut cursor = File::open(path).unwrap();
fn parse_enemy(episode: &Episode, map_area: &MapArea, raw_enemy: RawMapEnemy) -> Vec<Option<MapEnemy>> {
let enemy = MapEnemy::from_raw(raw_enemy, episode, map_area);
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, monster.map_area)));
}
},
MonsterType::PofuillySlime => {
for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
}
},
MonsterType::PanArms => {
monsters.push(Some(MapEnemy::new(MonsterType::Hidoom, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::Migium, monster.map_area)));
},
MonsterType::SinowBeat => {
for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area)));
}
},
MonsterType::SinowGold => {
for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area)));
}
},
MonsterType::Canane => {
for _ in 0..8 {
monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area)));
}
},
MonsterType::ChaosSorcerer => {
monsters.push(Some(MapEnemy::new(MonsterType::BeeR, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::BeeL, monster.map_area)));
},
MonsterType::Bulclaw => {
for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::Claw, monster.map_area)));
}
},
MonsterType::DeRolLe => {
for _ in 0..10 {
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody, monster.map_area)));
}
for _ in 0..9 {
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area)));
}
},
MonsterType::VolOptPartA => {
for _ in 0..6 {
monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar, monster.map_area)));
}
for _ in 0..24 {
monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area)));
}
for _ in 0..2 {
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area)));
}
monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area)));
},
// TOOD: this cares about difficulty (theres an ult-specific darvant?)
MonsterType::DarkFalz => {
for _ in 0..509 {
monsters.push(Some(MapEnemy::new(MonsterType::Darvant, monster.map_area)));
}
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1, monster.map_area)));
},
_ => {
for _ in 0..raw_enemy.children {
monsters.push(Some(MapEnemy::new(monster.monster, monster.map_area)));
}
}
}
monsters
})
}
pub fn enemy_data_from_stream(cursor: &mut impl Read, map_area: &MapArea, episode: &Episode) -> Vec<Option<MapEnemy>> {
let mut enemy_data = Vec::new();
while let Ok(enemy) = RawMapEnemy::from_byte_stream(&mut cursor) {
let new_enemy = MapEnemy::from_raw(enemy, episode, &map_variant.map);
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, monster.map_area)));
}
},
MonsterType::PofuillySlime => {
for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::PofuillySlime, monster.map_area)));
}
},
MonsterType::PanArms => {
monsters.push(Some(MapEnemy::new(MonsterType::Hidoom, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::Migium, monster.map_area)));
},
MonsterType::SinowBeat => {
for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::SinowBeat, monster.map_area)));
}
},
MonsterType::SinowGold => {
for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::SinowGold, monster.map_area)));
}
},
MonsterType::Canane => {
for _ in 0..8 {
monsters.push(Some(MapEnemy::new(MonsterType::RingCanadine, monster.map_area)));
}
},
MonsterType::ChaosSorcerer => {
monsters.push(Some(MapEnemy::new(MonsterType::BeeR, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::BeeL, monster.map_area)));
},
MonsterType::Bulclaw => {
for _ in 0..4 {
monsters.push(Some(MapEnemy::new(MonsterType::Claw, monster.map_area)));
}
},
MonsterType::DeRolLe => {
for _ in 0..10 {
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeBody, monster.map_area)));
}
for _ in 0..9 {
monsters.push(Some(MapEnemy::new(MonsterType::DeRolLeMine, monster.map_area)));
}
},
MonsterType::VolOptPartA => {
for _ in 0..6 {
monsters.push(Some(MapEnemy::new(MonsterType::VolOptPillar, monster.map_area)));
}
for _ in 0..24 {
monsters.push(Some(MapEnemy::new(MonsterType::VolOptMonitor, monster.map_area)));
}
for _ in 0..2 {
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area)));
}
monsters.push(Some(MapEnemy::new(MonsterType::VolOptAmp, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptCore, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::VolOptUnused, monster.map_area)));
},
// TOOD: this cares about difficulty (theres an ult-specific darvant?)
MonsterType::DarkFalz => {
for _ in 0..509 {
monsters.push(Some(MapEnemy::new(MonsterType::Darvant, monster.map_area)));
}
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz3, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz2, monster.map_area)));
monsters.push(Some(MapEnemy::new(MonsterType::DarkFalz1, monster.map_area)));
},
_ => {
for _ in 0..enemy.children {
monsters.push(Some(MapEnemy::new(monster.monster, monster.map_area)));
}
}
}
monsters
}));
while let Ok(enemy) = RawMapEnemy::from_byte_stream(cursor) {
enemy_data.append(&mut parse_enemy(episode, map_area, enemy));
}
enemy_data
}
fn enemy_data_from_map_data(map_variant: &MapVariant, episode: &Episode) -> Vec<Option<MapEnemy>> {
let path = map_variant.dat_file();
let mut cursor = File::open(path).unwrap();
enemy_data_from_stream(&mut cursor, &map_variant.map, episode)
}
#[derive(Error, Debug)]
#[error("")]
@ -675,7 +686,7 @@ impl Maps {
enemy_data
}),
object_data: map_variants.iter().map(|map_variant| {
objects_from_map_data(map_variant.obj_file().into(), &episode, &map_variant)
objects_from_map_data(map_variant.obj_file().into(), &episode, &map_variant.map)
}).flatten().collect(),
map_variants: map_variants,
};
@ -702,6 +713,11 @@ impl Maps {
header
})
}
pub fn set_quest_data(&mut self, enemies: Vec<Option<MapEnemy>>, objects: Vec<Option<MapObject>>) {
self.enemy_data = enemies;
self.object_data = objects;
}
}

1
src/ship/mod.rs

@ -8,3 +8,4 @@ pub mod map;
pub mod monster;
pub mod drops;
pub mod packet;
pub mod quests;

1
src/ship/packet/builder/mod.rs

@ -1,6 +1,7 @@
pub mod lobby;
pub mod message;
pub mod room;
pub mod quest;
use libpso::character::character::Inventory;
use libpso::packet::ship::{PlayerHeader, PlayerInfo};

47
src/ship/packet/builder/quest.rs

@ -0,0 +1,47 @@
use crate::ship::quests::{Quest, QuestList};
use crate::ship::ship::{QUEST_CATEGORY_MENU_ID, QUEST_SELECT_MENU_ID};
use libpso::packet::ship::*;
use libpso::utf8_to_utf16_array;
pub fn quest_category_list(quests: &QuestList) -> QuestCategoryList {
let categories = quests.iter()
.enumerate()
.map(|(i, (category, _))| {
QuestCategory {
menu_id: QUEST_CATEGORY_MENU_ID,
option_id: i as u32,
name: utf8_to_utf16_array!(category.name, 32),
description: utf8_to_utf16_array!(category.description, 122),
}
})
.collect();
QuestCategoryList {
quest_categories: categories,
}
}
pub fn quest_list(category_id: u32, quests: &Vec<Quest>) -> QuestOptionList {
let quest_entries = quests.iter()
.map(|quest| {
QuestEntry {
menu_id: QUEST_SELECT_MENU_ID,
category_id: category_id as u16,
quest_id: quest.id,
name: utf8_to_utf16_array!(quest.name, 32),
description: utf8_to_utf16_array!(quest.description, 122),
}
})
.collect();
QuestOptionList {
quests: quest_entries,
}
}
pub fn quest_detail(quest: &Quest) -> QuestDetail {
QuestDetail {
description: utf8_to_utf16_array!(quest.full_description, 288),
}
}

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

@ -5,3 +5,4 @@ pub mod lobby;
pub mod message;
pub mod room;
pub mod settings;
pub mod quest;

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

@ -0,0 +1,209 @@
use std::collections::HashMap;
use std::convert::TryInto;
use std::io::{Cursor, Read, Seek, SeekFrom};
use libpso::packet::ship::*;
use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms};
use crate::ship::quests::QuestList;
use crate::ship::location::{ClientLocation, ClientLocationError};
use crate::ship::packet::builder::quest;
use libpso::utf8_to_array;
use libpso::util::array_to_utf8;
// TOOD: enum
const DATATYPE_BIN: u16 = 1;
const DATATYPE_DAT: u16 = 2;
fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, u16), ShipError> {
let filename = array_to_utf8(*filename_bytes).map_err(|_| ShipError::InvalidQuestFilename("NOT UTF8".to_string()))?;
let (filename, suffix) = {
let mut s = filename.splitn(2, '.');
(s.next().ok_or(ShipError::InvalidQuestFilename(filename.to_owned()))?,
s.next().ok_or(ShipError::InvalidQuestFilename(filename.to_owned()))?)
};
let datatype = match suffix {
"bin" => DATATYPE_BIN,
"dat" => DATATYPE_DAT,
_ => return Err(ShipError::InvalidQuestFilename(filename.to_owned()))
};
let (category, quest) = {
let mut s = filename.splitn(2, '-');
(s.next().and_then(|k| k.parse().ok()).ok_or(ShipError::InvalidQuestFilename(filename.to_owned()))?,
s.next().and_then(|k| k.parse().ok()).ok_or(ShipError::InvalidQuestFilename(filename.to_owned()))?)
};
Ok((category, quest, datatype))
}
pub fn send_quest_category_list(id: ClientId, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let qcl = quest::quest_category_list(quests);
Ok(Box::new(vec![(id, SendShipPacket::QuestCategoryList(qcl))].into_iter()))
}
pub fn select_quest_category(id: ClientId, menuselect: &MenuSelect, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let (_, category_quests) = quests.iter()
.nth(menuselect.item as usize)
.ok_or(ShipError::InvalidQuestCategory(menuselect.item))?;
let ql = quest::quest_list(menuselect.item, category_quests);
Ok(Box::new(vec![(id, SendShipPacket::QuestOptionList(ql))].into_iter()))
}
pub fn quest_detail(id: ClientId, questdetailrequest: &QuestDetailRequest, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let (_, category_quests) = quests.iter()
.nth(questdetailrequest.category as usize)
.ok_or(ShipError::InvalidQuestCategory(questdetailrequest.category as u32))?;
let quest = category_quests.iter()
.find(|q| {
q.id == questdetailrequest.quest as u16
}).ok_or(ShipError::InvalidQuest(questdetailrequest.quest as u32))?;
let qd = quest::quest_detail(&quest);
Ok(Box::new(vec![(id, SendShipPacket::QuestDetail(qd))].into_iter()))
}
pub fn load_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quests: &QuestList, clients: &mut Clients, client_location: &ClientLocation, rooms: &mut Rooms)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let (_, category_quests) = quests.iter()
.nth(questmenuselect.category as usize)
.ok_or(ShipError::InvalidQuestCategory(questmenuselect.category as u32))?;
let quest = category_quests.iter()
.find(|q| {
q.id == questmenuselect.quest as u16
}).ok_or(ShipError::InvalidQuest(questmenuselect.quest as u32))?;
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room = rooms.get_mut(room_id.0)
.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());
let bin_path = format!("{}-{}.bin", questmenuselect.category, questmenuselect.quest);
let bin = QuestHeader {
unknown1: [0; 0x24],
filename: utf8_to_array!(bin_path, 16),
length: quest.bin_blob.len() as u32,
name: utf8_to_array!("quest.bin", 16),
unknown2: [0; 8],
};
let dat_path = format!("{}-{}.dat", questmenuselect.category, questmenuselect.quest);
let dat = QuestHeader {
unknown1: [0; 0x24],
filename: utf8_to_array!(dat_path, 16),
length: quest.dat_blob.len() as u32,
name: utf8_to_array!("quest.dat", 16),
unknown2: [0; 8],
};
let area_clients = client_location.get_all_clients_by_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
area_clients.iter().for_each(|c| {
clients.get_mut(&c.client).map(|client| {
client.done_loading_quest = false;
});
});
Ok(Box::new(area_clients.into_iter().map(move |c| {
vec![(c.client, SendShipPacket::QuestHeader(bin.clone())), (c.client, SendShipPacket::QuestHeader(dat.clone()))]
}).flatten()))
//Ok(Box::new(vec![(id, SendShipPacket::QuestHeader(bin)), (id, SendShipPacket::QuestHeader(dat))].into_iter()))
}
pub fn quest_file_request(id: ClientId, quest_file_request: &QuestFileRequest, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let (category_id, quest_id, datatype) = parse_filename(&quest_file_request.filename)?;
let (_, category_quests) = quests.iter()
.nth(category_id as usize)
.ok_or(ShipError::InvalidQuestCategory(category_id as u32))?;
let quest = category_quests.iter()
.find(|q| {
q.id == quest_id as u16
}).ok_or(ShipError::InvalidQuest(quest_id as u32))?;
// quest.Bin quest.Dat
let blob = match datatype {
DATATYPE_BIN => &quest.bin_blob,
DATATYPE_DAT => &quest.dat_blob,
_ => panic!()
};
let mut blob_cursor = Cursor::new(blob);
let mut subblob = [0u8; 0x400];
let blob_length = blob_cursor.read(&mut subblob)?;
let qc = QuestChunk {
chunk_num: 0,
filename: quest_file_request.filename,
blob: subblob,
blob_length: blob_length as u32,
unknown: 0,
};
Ok(Box::new(vec![(id, SendShipPacket::QuestChunk(qc))].into_iter()))
}
pub fn quest_chunk_ack(id: ClientId, quest_chunk_ack: &QuestChunkAck, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let (category_id, quest_id, datatype) = parse_filename(&quest_chunk_ack.filename)?;
let (_, category_quests) = quests.iter()
.nth(category_id as usize)
.ok_or(ShipError::InvalidQuestCategory(category_id as u32))?;
let quest = category_quests.iter()
.find(|q| {
q.id == quest_id
}).ok_or(ShipError::InvalidQuest(quest_id as u32))?;
let blob = match datatype {
DATATYPE_BIN => &quest.bin_blob,
DATATYPE_DAT => &quest.dat_blob,
_ => panic!()
};
let mut blob_cursor = Cursor::new(blob);
blob_cursor.seek(SeekFrom::Start((quest_chunk_ack.chunk_num as u64 + 1) * 0x400));
let mut subblob = [0u8; 0x400];
let blob_length = blob_cursor.read(&mut subblob)?;
if blob_length == 0 {
return Ok(Box::new(None.into_iter()));
}
let qc = QuestChunk {
chunk_num: quest_chunk_ack.chunk_num + 1,
filename: quest_chunk_ack.filename,
blob: subblob,
blob_length: blob_length as u32,
unknown: 0,
};
Ok(Box::new(vec![(id, SendShipPacket::QuestChunk(qc))].into_iter()))
}
pub fn done_loading_quest(id: ClientId, quests: &QuestList, clients: &mut Clients, client_location: &ClientLocation)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
client.done_loading_quest = true;
let area_clients = client_location.get_all_clients_by_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
let all_loaded = area_clients.iter().all(|c| {
clients.get(&c.client)
.map(|client| {
client.done_loading_quest
})
.unwrap_or(false)
});
if all_loaded {
Ok(Box::new(area_clients.into_iter().map(|c| {
(c.client, SendShipPacket::DoneLoadingQuest(DoneLoadingQuest {}))
})))
}
else {
Ok(Box::new(None.into_iter()))
}
}

26
src/ship/packet/handler/room.rs

@ -1,4 +1,5 @@
use libpso::packet::ship::*;
use libpso::packet::messages::*;
use crate::common::serverstate::ClientId;
use crate::common::leveltable::CharacterLevelTable;
use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients};
@ -110,10 +111,35 @@ pub fn done_bursting(id: ClientId,
let room = rooms.get_mut(room_id.0).unwrap().as_mut().unwrap();
room.bursting = false;
}
Box::new(client_location.get_client_neighbors(id).unwrap().into_iter()
.map(move |client| {
vec![
//(client.client, SendShipPacket::BurstDone72(BurstDone72::new())),
(client.client, SendShipPacket::Message(Message::new(GameMessage::BurstDone(BurstDone {
client: 0,
target: 0
})))),
]
}).flatten())
}
pub fn done_bursting2(id: ClientId,
client_location: &ClientLocation,
rooms: &mut Rooms)
-> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
let area = client_location.get_area(id).unwrap();
if let RoomLobby::Room(room_id) = area {
let room = rooms.get_mut(room_id.0).unwrap().as_mut().unwrap();
room.bursting = false;
}
Box::new(client_location.get_client_neighbors(id).unwrap().into_iter()
.map(move |client| {
vec![
(client.client, SendShipPacket::BurstDone72(BurstDone72::new())),
/*(client.client, SendShipPacket::Message(Message::new(GameMessage::BurstDone(BurstDone {
client: 1,
target: 0
})))),*/
]
}).flatten())
}

224
src/ship/quests.rs

@ -0,0 +1,224 @@
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()
}

11
src/ship/room.rs

@ -46,6 +46,17 @@ impl Into<u8> for Episode {
}
}
impl Episode {
pub fn from_quest(value: u8) -> Result<Episode, RoomCreationError> {
match value {
0 => Ok(Episode::One),
1 => Ok(Episode::Two),
2 => Ok(Episode::Four),
_ => Err(RoomCreationError::InvalidEpisode(value))
}
}
}
#[derive(Debug, Copy, Clone, derive_more::Display)]
pub enum Difficulty {
Normal,

88
src/ship/ship.rs

@ -12,7 +12,6 @@ use libpso::crypto::bb::PSOBBCipher;
use libpso::packet::ship::{BLOCK_MENU_ID, ROOM_MENU_ID};
use crate::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY};
use crate::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId};
use crate::common::leveltable::CharacterLevelTable;
@ -25,10 +24,13 @@ use crate::ship::location::{ClientLocation, RoomLobby, MAX_ROOMS, ClientLocation
use crate::ship::items;
use crate::ship::room;
use crate::ship::quests;
use crate::ship::map::{MapsError, MapAreaError, MapArea};
use crate::ship::packet::handler;
pub const SHIP_PORT: u16 = 23423;
pub const QUEST_CATEGORY_MENU_ID: u32 = 0xA2;
pub const QUEST_SELECT_MENU_ID: u32 = 0xA3;
pub type Rooms = [Option<room::RoomState>; MAX_ROOMS];
pub type Clients = HashMap<ClientId, ClientState>;
@ -51,6 +53,10 @@ pub enum ShipError {
ItemManagerError(#[from] items::ItemManagerError),
ItemDropLocationNotSet,
BoxAlreadyDroppedItem(ClientId, u16),
InvalidQuestCategory(u32),
InvalidQuest(u32),
InvalidQuestFilename(String),
IoError(#[from] std::io::Error),
}
#[derive(Debug)]
@ -71,18 +77,33 @@ pub enum RecvShipPacket {
Like62ButCooler(Like62ButCooler),
ClientCharacterData(ClientCharacterData),
DoneBursting(DoneBursting),
DoneBursting2(DoneBursting2),
LobbySelect(LobbySelect),
RequestQuestList(RequestQuestList),
MenuDetail(MenuDetail),
QuestDetailRequest(QuestDetailRequest),
QuestMenuSelect(QuestMenuSelect),
QuestFileRequest(QuestFileRequest),
QuestChunkAck(QuestChunkAck),
DoneLoadingQuest(DoneLoadingQuest),
}
impl RecvServerPacket for RecvShipPacket {
fn from_bytes(data: &[u8]) -> Result<RecvShipPacket, PacketParseError> {
match u16::from_le_bytes([data[2], data[3]]) {
0x93 => Ok(RecvShipPacket::Login(Login::from_bytes(data)?)),
0x10 => match data[0] {
16 => Ok(RecvShipPacket::MenuSelect(MenuSelect::from_bytes(data)?)),
48 => Ok(RecvShipPacket::RoomPasswordReq(RoomPasswordReq::from_bytes(data)?)),
0x09 => match data[8] as u32 {
QUEST_SELECT_MENU_ID => Ok(RecvShipPacket::QuestDetailRequest(QuestDetailRequest::from_bytes(data)?)),
_ => Ok(RecvShipPacket::MenuDetail(MenuDetail::from_bytes(data)?)),
}
0x10 => match (data[0], data[8] as u32) {
(16, QUEST_SELECT_MENU_ID) => Ok(RecvShipPacket::QuestMenuSelect(QuestMenuSelect::from_bytes(data)?)),
(16, _) => Ok(RecvShipPacket::MenuSelect(MenuSelect::from_bytes(data)?)),
(48, _) => Ok(RecvShipPacket::RoomPasswordReq(RoomPasswordReq::from_bytes(data)?)),
_ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())),
},
0x13 => Ok(RecvShipPacket::QuestChunkAck(QuestChunkAck::from_bytes(data)?)),
0x44 => Ok(RecvShipPacket::QuestFileRequest(QuestFileRequest::from_bytes(data)?)),
0x61 => Ok(RecvShipPacket::CharData(CharData::from_bytes(data)?)),
0x60 => Ok(RecvShipPacket::Message(Message::from_bytes(data)?)),
0x62 => Ok(RecvShipPacket::DirectMessage(DirectMessage::from_bytes(data)?)),
@ -96,7 +117,10 @@ impl RecvServerPacket for RecvShipPacket {
0x6D => Ok(RecvShipPacket::Like62ButCooler(Like62ButCooler::from_bytes(data)?)),
0x98 => Ok(RecvShipPacket::ClientCharacterData(ClientCharacterData::from_bytes(data)?)),
0x6F => Ok(RecvShipPacket::DoneBursting(DoneBursting::from_bytes(data)?)),
0x16F => Ok(RecvShipPacket::DoneBursting2(DoneBursting2::from_bytes(data)?)),
0x84 => Ok(RecvShipPacket::LobbySelect(LobbySelect::from_bytes(data)?)),
0xA2 => Ok(RecvShipPacket::RequestQuestList(RequestQuestList::from_bytes(data)?)),
0xAC => Ok(RecvShipPacket::DoneLoadingQuest(DoneLoadingQuest::from_bytes(data)?)),
_ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec()))
}
}
@ -125,7 +149,14 @@ pub enum SendShipPacket {
Like62ButCooler(Like62ButCooler),
BurstDone72(BurstDone72),
DoneBursting(DoneBursting),
DoneBursting2(DoneBursting2),
LobbyList(LobbyList),
QuestCategoryList(QuestCategoryList),
QuestOptionList(QuestOptionList),
QuestDetail(QuestDetail),
QuestHeader(QuestHeader),
QuestChunk(QuestChunk),
DoneLoadingQuest(DoneLoadingQuest),
}
impl SendServerPacket for SendShipPacket {
@ -152,7 +183,14 @@ impl SendServerPacket for SendShipPacket {
SendShipPacket::Like62ButCooler(pkt) => pkt.as_bytes(),
SendShipPacket::BurstDone72(pkt) => pkt.as_bytes(),
SendShipPacket::DoneBursting(pkt) => pkt.as_bytes(),
SendShipPacket::DoneBursting2(pkt) => pkt.as_bytes(),
SendShipPacket::LobbyList(pkt) => pkt.as_bytes(),
SendShipPacket::QuestCategoryList(pkt) => pkt.as_bytes(),
SendShipPacket::QuestOptionList(pkt) => pkt.as_bytes(),
SendShipPacket::QuestDetail(pkt) => pkt.as_bytes(),
SendShipPacket::QuestHeader(pkt) => pkt.as_bytes(),
SendShipPacket::QuestChunk(pkt) => pkt.as_bytes(),
SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(),
}
}
}
@ -165,6 +203,12 @@ pub struct ItemDropLocation {
pub item_id: items::ClientItemId,
}
pub struct LoadingQuest {
pub header_bin: Option<QuestHeader>,
pub header_dat: Option<QuestHeader>,
//pub quest_chunk_bin: Option<Box<dyn Iterator<Item = >>>,
}
pub struct ClientState {
pub user: UserAccountEntity,
pub settings: UserSettingsEntity,
@ -173,6 +217,8 @@ pub struct ClientState {
//guildcard: GuildCard,
pub block: u32,
pub item_drop_location: Option<ItemDropLocation>,
pub done_loading_quest: bool,
//pub loading_quest: Option<LoadingQuest>,
}
impl ClientState {
@ -184,6 +230,7 @@ impl ClientState {
session: session,
block: 1,
item_drop_location: None,
done_loading_quest: false,
}
}
}
@ -197,6 +244,7 @@ pub struct ShipServerState<EG: EntityGateway> {
name: String,
rooms: Rooms,
item_manager: items::ItemManager,
quests: quests::QuestList,
}
impl<EG: EntityGateway> ShipServerState<EG> {
@ -209,6 +257,7 @@ impl<EG: EntityGateway> ShipServerState<EG> {
name: "Sona-Nyl".into(),
rooms: [None; MAX_ROOMS],
item_manager: items::ItemManager::new(),
quests: quests::load_quests("data/quests.toml".into()),
}
}
@ -288,13 +337,26 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
RecvShipPacket::Login(login) => {
Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager, &self.name)?.into_iter().map(move |pkt| (id, pkt)))
},
RecvShipPacket::QuestDetailRequest(questdetailrequest) => {
match questdetailrequest.menu {
QUEST_SELECT_MENU_ID => handler::quest::quest_detail(id, questdetailrequest, &self.quests)?,
_ => unreachable!(),
}
},
RecvShipPacket::MenuSelect(menuselect) => {
match menuselect.menu {
BLOCK_MENU_ID => Box::new(handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.item_manager, &self.level_table)?.into_iter().map(move |pkt| (id, pkt))),
ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut self.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms)?,
QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &self.quests)?,
_ => unreachable!(),
}
},
RecvShipPacket::QuestMenuSelect(questmenuselect) => {
handler::quest::load_quest(id, questmenuselect, &self.quests, &mut self.clients, &self.client_location, &mut self.rooms)?
},
RecvShipPacket::MenuDetail(_menudetail) => {
unreachable!();
},
RecvShipPacket::RoomPasswordReq(room_password_req) => {
if room_password_req.password == self.rooms[room_password_req.item as usize].as_ref()
.ok_or(ShipError::InvalidRoom(room_password_req.item))?
@ -349,9 +411,25 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
RecvShipPacket::DoneBursting(_) => {
handler::room::done_bursting(id, &self.client_location, &mut self.rooms)
},
RecvShipPacket::DoneBursting2(_) => {
Box::new(None.into_iter())
//handler::room::done_bursting2(id, &self.client_location, &mut self.rooms)
},
RecvShipPacket::LobbySelect(pkt) => {
Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut self.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut self.rooms, &mut self.entity_gateway)?.into_iter())
}
},
RecvShipPacket::RequestQuestList(_) => {
handler::quest::send_quest_category_list(id, &self.quests)?
},
RecvShipPacket::QuestFileRequest(quest_file_request) => {
handler::quest::quest_file_request(id, quest_file_request, &self.quests)?
},
RecvShipPacket::QuestChunkAck(quest_chunk_ack) => {
handler::quest::quest_chunk_ack(id, quest_chunk_ack, &self.quests)?
},
RecvShipPacket::DoneLoadingQuest(_) => {
handler::quest::done_loading_quest(id, &self.quests, &mut self.clients, &self.client_location)?
},
})
}

Loading…
Cancel
Save