242 lines
10 KiB
Rust
242 lines
10 KiB
Rust
use std::io::{Cursor, Read, Seek, SeekFrom};
|
|
use futures::stream::{FuturesOrdered, StreamExt};
|
|
use libpso::packet::ship::*;
|
|
use crate::common::serverstate::ClientId;
|
|
use crate::ship::ship::{SendShipPacket, ShipError, Clients};
|
|
use crate::ship::room::Rooms;
|
|
use crate::ship::location::{ClientLocation, ClientLocationError};
|
|
use crate::ship::packet::builder::quest;
|
|
use libpso::util::array_to_utf8;
|
|
|
|
enum QuestFileType {
|
|
Bin,
|
|
Dat
|
|
}
|
|
|
|
fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, QuestFileType), 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_else(|| ShipError::InvalidQuestFilename(filename.to_owned()))?,
|
|
s.next().ok_or_else(|| ShipError::InvalidQuestFilename(filename.to_owned()))?)
|
|
};
|
|
|
|
let datatype = match suffix {
|
|
"bin" => QuestFileType::Bin,
|
|
"dat" => QuestFileType::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_else(|| ShipError::InvalidQuestFilename(filename.to_owned()))?,
|
|
s.next().and_then(|k| k.parse().ok()).ok_or_else(|| ShipError::InvalidQuestFilename(filename.to_owned()))?)
|
|
};
|
|
|
|
Ok((category, quest, datatype))
|
|
}
|
|
|
|
|
|
pub async fn send_quest_category_list(id: ClientId,
|
|
rql: RequestQuestList,
|
|
client_location: &ClientLocation,
|
|
rooms: &Rooms)
|
|
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
|
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
|
|
let rql = rql.clone();
|
|
rooms.with_mut(room_id, |room| Box::pin(async move {
|
|
let qcl = quest::quest_category_list(&room.quests[rql.flag.clamp(0, (room.quests.len() - 1) as u32) as usize]);
|
|
room.set_quest_group(rql.flag as usize);
|
|
Ok(vec![(id, SendShipPacket::QuestCategoryList(qcl))])
|
|
})).await?
|
|
}
|
|
|
|
pub async fn select_quest_category(id: ClientId,
|
|
menuselect: MenuSelect,
|
|
client_location: &ClientLocation,
|
|
rooms: &Rooms)
|
|
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
|
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
|
|
rooms.with(room_id, |room| Box::pin(async move {
|
|
let (_, category_quests) = room.quests[room.quest_group.value()].iter()
|
|
.nth(menuselect.item as usize)
|
|
.ok_or_else(|| ShipError::InvalidQuestCategory(menuselect.item as u16))?;
|
|
|
|
let ql = quest::quest_list(menuselect.item, category_quests);
|
|
Ok(vec![(id, SendShipPacket::QuestOptionList(ql))])
|
|
})).await?
|
|
}
|
|
|
|
|
|
pub async fn quest_detail(id: ClientId,
|
|
questdetailrequest: QuestDetailRequest,
|
|
client_location: &ClientLocation,
|
|
rooms: &Rooms)
|
|
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
|
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
|
|
rooms.with(room_id, |room| Box::pin(async move {
|
|
let (_, category_quests) = room.quests[room.quest_group.value()].iter()
|
|
.nth(questdetailrequest.category as usize)
|
|
.ok_or_else(|| ShipError::InvalidQuestCategory(questdetailrequest.category))?;
|
|
|
|
let quest = category_quests.iter()
|
|
.find(|q| {
|
|
q.id == questdetailrequest.quest
|
|
}).ok_or_else(|| ShipError::InvalidQuest(questdetailrequest.quest))?;
|
|
|
|
let qd = quest::quest_detail(quest);
|
|
|
|
Ok(vec![(id, SendShipPacket::QuestDetail(qd))])
|
|
})).await?
|
|
}
|
|
|
|
pub async fn player_chose_quest(id: ClientId,
|
|
questmenuselect: QuestMenuSelect,
|
|
clients: &Clients,
|
|
client_location: &ClientLocation,
|
|
rooms: &Rooms)
|
|
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
|
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
|
|
|
|
let client_location = client_location.clone();
|
|
let questmenuselect = questmenuselect.clone();
|
|
rooms.with_mut(room_id, |room| {
|
|
let clients = clients.clone();
|
|
Box::pin(async move {
|
|
let quest = room.quests[room.quest_group.value()].iter()
|
|
.nth(questmenuselect.category as usize)
|
|
.ok_or_else(|| ShipError::InvalidQuestCategory(questmenuselect.category))?
|
|
.1
|
|
.iter()
|
|
.find(|q| {
|
|
q.id == questmenuselect.quest
|
|
})
|
|
.ok_or_else(|| ShipError::InvalidQuest(questmenuselect.quest))?
|
|
.clone();
|
|
|
|
let rare_monster_drops = room.rare_monster_table.clone();
|
|
room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &rare_monster_drops);
|
|
room.map_areas = quest.map_areas.clone();
|
|
|
|
let bin = quest::quest_header(&questmenuselect, &quest.bin_blob, "bin");
|
|
let dat = quest::quest_header(&questmenuselect, &quest.dat_blob, "dat");
|
|
|
|
let area_clients = client_location.get_all_clients_by_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
|
|
for client in &area_clients {
|
|
clients.with_mut(client.client, |client| Box::pin(async move {
|
|
client.done_loading_quest = false;
|
|
})).await?;
|
|
}
|
|
Ok(area_clients
|
|
.into_iter()
|
|
.flat_map(move |c| {
|
|
vec![(c.client, SendShipPacket::QuestHeader(bin.clone())), (c.client, SendShipPacket::QuestHeader(dat.clone()))]
|
|
})
|
|
.collect())
|
|
})}).await?
|
|
}
|
|
|
|
pub async fn quest_file_request(id: ClientId,
|
|
quest_file_request: QuestFileRequest,
|
|
client_location: &ClientLocation,
|
|
rooms: &mut Rooms)
|
|
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
|
|
{
|
|
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
|
|
|
|
let quest_file_request = quest_file_request.clone();
|
|
rooms.with(room_id, |room| Box::pin(async move {
|
|
let (category_id, quest_id, datatype) = parse_filename(&quest_file_request.filename)?;
|
|
let (_, category_quests) = room.quests[room.quest_group.value()].iter()
|
|
.nth(category_id as usize)
|
|
.ok_or_else(|| ShipError::InvalidQuestCategory(category_id))?;
|
|
|
|
let quest = category_quests.iter()
|
|
.find(|q| {
|
|
q.id == quest_id
|
|
}).ok_or_else(|| ShipError::InvalidQuest(quest_id))?;
|
|
|
|
let blob = match datatype {
|
|
QuestFileType::Bin => &quest.bin_blob,
|
|
QuestFileType::Dat => &quest.dat_blob,
|
|
};
|
|
let mut blob_cursor = Cursor::new(&**blob);
|
|
|
|
let mut subblob = [0u8; 0x400];
|
|
let blob_length = blob_cursor.read(&mut subblob)?;
|
|
let qc = quest::quest_chunk(0, quest_file_request.filename, subblob, blob_length);
|
|
|
|
Ok(vec![(id, SendShipPacket::QuestChunk(qc))])
|
|
})).await?
|
|
}
|
|
|
|
pub async fn quest_chunk_ack(id: ClientId,
|
|
quest_chunk_ack: QuestChunkAck,
|
|
client_location: &ClientLocation,
|
|
rooms: &Rooms)
|
|
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
|
let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
|
|
|
|
let quest_chunk_ack = quest_chunk_ack.clone();
|
|
rooms.with(room_id, |room| Box::pin(async move {
|
|
let (category_id, quest_id, datatype) = parse_filename(&quest_chunk_ack.filename)?;
|
|
let (_, category_quests) = room.quests[room.quest_group.value()].iter()
|
|
.nth(category_id as usize)
|
|
.ok_or_else(|| ShipError::InvalidQuestCategory(category_id))?;
|
|
|
|
let quest = category_quests.iter()
|
|
.find(|q| {
|
|
q.id == quest_id
|
|
}).ok_or_else(|| ShipError::InvalidQuest(quest_id))?;
|
|
|
|
let blob = match datatype {
|
|
QuestFileType::Bin => &quest.bin_blob,
|
|
QuestFileType::Dat => &quest.dat_blob,
|
|
};
|
|
|
|
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(Vec::new());
|
|
}
|
|
let qc = quest::quest_chunk(quest_chunk_ack.chunk_num + 1, quest_chunk_ack.filename, subblob, blob_length);
|
|
|
|
Ok(vec![(id, SendShipPacket::QuestChunk(qc))])
|
|
})).await?
|
|
}
|
|
|
|
pub async fn done_loading_quest(id: ClientId,
|
|
clients: &Clients,
|
|
client_location: &ClientLocation)
|
|
-> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
|
|
clients.with_mut(id, |client| Box::pin(async move {
|
|
client.done_loading_quest = true;
|
|
})).await?;
|
|
let area_clients = client_location.get_all_clients_by_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
|
|
|
|
let all_loaded = area_clients.iter()
|
|
.map(|client|
|
|
clients.with(client.client, |client| Box::pin(async move {
|
|
client.done_loading_quest
|
|
}))
|
|
)
|
|
.collect::<FuturesOrdered<_>>()
|
|
.all(|c| async move {
|
|
c.unwrap_or(false)
|
|
}).await;
|
|
|
|
if all_loaded {
|
|
Ok(area_clients
|
|
.iter()
|
|
.map(|c| {
|
|
(c.client, SendShipPacket::DoneLoadingQuest(DoneLoadingQuest {}))
|
|
})
|
|
.collect())
|
|
}
|
|
else {
|
|
Ok(Vec::new())
|
|
}
|
|
}
|