210 lines
8.3 KiB
Rust
210 lines
8.3 KiB
Rust
|
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()))
|
||
|
}
|
||
|
}
|