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 + 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 + 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 + 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 + 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 + 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 + 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 + 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())) } }