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, ShipEvent}; use crate::ship::room::Rooms; use crate::ship::map::enemy::RareMonsterAppearTable; use crate::ship::location::{ClientLocation}; 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), anyhow::Error> { 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, _ => 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, anyhow::Error> { let room_id = client_location.get_room(id).await?; 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.quest_group = rql.flag.into(); let qcl = quest::quest_category_list(room.quests()); Ok(vec![(id, SendShipPacket::QuestCategoryList(qcl))]) })).await? } pub async fn select_quest_category(id: ClientId, menuselect: MenuSelect, client_location: &ClientLocation, rooms: &Rooms) -> Result, anyhow::Error> { let room_id = client_location.get_room(id).await?; rooms.with(room_id, |room| Box::pin(async move { let (_, category_quests) = room.quests() .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, anyhow::Error> { let room_id = client_location.get_room(id).await?; rooms.with(room_id, |room| Box::pin(async move { let (_, category_quests) = room.quests().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, event: ShipEvent) -> Result, anyhow::Error> { let room_id = client_location.get_room(id).await?; 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().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_table = RareMonsterAppearTable::new(room.mode.episode()); room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &rare_monster_table, event); 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?; 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, anyhow::Error> { let room_id = client_location.get_room(id).await?; 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().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, anyhow::Error> { let room_id = client_location.get_room(id).await?; 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().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, anyhow::Error> { 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?; let all_loaded = area_clients.iter() .map(|client| clients.with(client.client, |client| Box::pin(async move { client.done_loading_quest })) ) .collect::>() .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()) } }