From f0f1b55a381f37adcd640053bdfff402ecd148c6 Mon Sep 17 00:00:00 2001 From: jake Date: Sat, 11 Nov 2023 15:20:43 -0700 Subject: [PATCH] remove some redundant files --- client/src/client.rs | 178 ---------- client/src/lib.rs | 179 ++++++++++- location/src/lib.rs | 677 ++++++++++++++++++++++++++++++++++++++- location/src/location.rs | 676 -------------------------------------- quests/src/lib.rs | 328 ++++++++++++++++++- quests/src/quests.rs | 328 ------------------- room/src/lib.rs | 273 +++++++++++++++- room/src/room.rs | 272 ---------------- trade/src/lib.rs | 132 +++++++- trade/src/trade.rs | 132 -------- 10 files changed, 1579 insertions(+), 1596 deletions(-) delete mode 100644 client/src/client.rs delete mode 100644 location/src/location.rs delete mode 100644 quests/src/quests.rs delete mode 100644 room/src/room.rs delete mode 100644 trade/src/trade.rs diff --git a/client/src/client.rs b/client/src/client.rs deleted file mode 100644 index e75ceaa..0000000 --- a/client/src/client.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::collections::HashMap; -use async_std::sync::{Arc, RwLock, RwLockReadGuard}; - -use futures::future::BoxFuture; - -use libpso::packet::ship::*; -use libpso::packet::login::Session; - -use networking::serverstate::ClientId; -use entity::account::{UserAccountEntity, UserSettingsEntity}; -use entity::character::CharacterEntity; -use entity::item; - -use items; -use maps::area::MapArea; -use shops::{WeaponShopItem, ToolShopItem, ArmorShopItem}; - - -#[derive(thiserror::Error, Debug)] -pub enum ClientError { - #[error("not found {0}")] - NotFound(ClientId), -} - - -#[derive(Clone, Default)] -pub struct Clients(Arc>>>); - -impl Clients { - pub async fn add(&mut self, client_id: ClientId, client_state: ClientState) { - self.0 - .write() - .await - .insert(client_id, RwLock::new(client_state)); - } - - pub async fn remove(&mut self, client_id: &ClientId) -> Option { - Some(self.0 - .write() - .await - .remove(client_id)? - .into_inner()) - } - - pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result - where - T: Send, - F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a, - { - let clients = self.0 - .read() - .await; - let client = clients - .get(&client_id) - .ok_or(ClientError::NotFound(client_id))? - .read() - .await; - - Ok(func(&client).await) - } - - pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result - where - T: Send, - F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a, - { - let clients = self.0 - .read() - .await; - - let mut client_states: [std::mem::MaybeUninit>; N] = unsafe { - std::mem::MaybeUninit::uninit().assume_init() - }; - - for (cindex, client_id) in client_ids.iter().enumerate() { - let c = clients - .get(client_id) - .ok_or(ClientError::NotFound(*client_id))? - .read() - .await; - client_states[cindex].write(c); - } - - let client_states = unsafe { - // TODO: this should just be a normal transmute but due to compiler limitations it - // does not yet work with const generics - // https://github.com/rust-lang/rust/issues/61956 - std::mem::transmute_copy::<_, [RwLockReadGuard; N]>(&client_states) - }; - - Ok(func(client_states).await) - } - - pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result - where - T: Send, - F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a, - { - let clients = self.0 - .read() - .await; - let mut client = clients - .get(&client_id) - .ok_or(ClientError::NotFound(client_id))? - .write() - .await; - - Ok(func(&mut client).await) - } -} - - -#[derive(Debug, Clone, Copy)] -pub struct ItemDropLocation { - pub map_area: MapArea, - pub x: f32, - pub z: f32, - pub item_id: items::ClientItemId, -} - -pub struct LoadingQuest { - pub header_bin: Option, - pub header_dat: Option, -} - - -pub struct ClientState { - pub user: UserAccountEntity, - pub settings: UserSettingsEntity, - pub character: CharacterEntity, - _session: Session, - //guildcard: GuildCard, - pub block: usize, - pub item_drop_location: Option, - pub done_loading_quest: bool, - pub area: Option, - pub x: f32, - pub y: f32, - pub z: f32, - pub weapon_shop: Vec, - pub tool_shop: Vec, - pub armor_shop: Vec, - pub tek: Option<(items::ClientItemId, item::weapon::TekSpecialModifier, item::weapon::TekPercentModifier, i32)>, - pub character_playtime: chrono::Duration, - pub log_on_time: chrono::DateTime, -} - -impl ClientState { - pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, session: Session) -> ClientState { - let character_playtime = chrono::Duration::seconds(character.playtime as i64); - ClientState { - user, - settings, - character, - _session: session, - block: 0, - item_drop_location: None, - done_loading_quest: false, - area: None, - x: 0.0, - y: 0.0, - z: 0.0, - weapon_shop: Vec::new(), - tool_shop: Vec::new(), - armor_shop: Vec::new(), - tek: None, - character_playtime, - log_on_time: chrono::Utc::now(), - } - } - - pub fn update_playtime(&mut self) { - let additional_playtime = chrono::Utc::now() - self.log_on_time; - self.character.playtime = (self.character_playtime + additional_playtime).num_seconds() as u32; - } -} - - diff --git a/client/src/lib.rs b/client/src/lib.rs index c8ab57b..e75ceaa 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,3 +1,178 @@ -pub mod client; +use std::collections::HashMap; +use async_std::sync::{Arc, RwLock, RwLockReadGuard}; + +use futures::future::BoxFuture; + +use libpso::packet::ship::*; +use libpso::packet::login::Session; + +use networking::serverstate::ClientId; +use entity::account::{UserAccountEntity, UserSettingsEntity}; +use entity::character::CharacterEntity; +use entity::item; + +use items; +use maps::area::MapArea; +use shops::{WeaponShopItem, ToolShopItem, ArmorShopItem}; + + +#[derive(thiserror::Error, Debug)] +pub enum ClientError { + #[error("not found {0}")] + NotFound(ClientId), +} + + +#[derive(Clone, Default)] +pub struct Clients(Arc>>>); + +impl Clients { + pub async fn add(&mut self, client_id: ClientId, client_state: ClientState) { + self.0 + .write() + .await + .insert(client_id, RwLock::new(client_state)); + } + + pub async fn remove(&mut self, client_id: &ClientId) -> Option { + Some(self.0 + .write() + .await + .remove(client_id)? + .into_inner()) + } + + pub async fn with<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result + where + T: Send, + F: for<'b> FnOnce(&'b ClientState) -> BoxFuture<'b, T> + Send + 'a, + { + let clients = self.0 + .read() + .await; + let client = clients + .get(&client_id) + .ok_or(ClientError::NotFound(client_id))? + .read() + .await; + + Ok(func(&client).await) + } + + pub async fn with_many<'a, T, F, const N: usize>(&'a self, client_ids: [ClientId; N], func: F) -> Result + where + T: Send, + F: for<'b> FnOnce([RwLockReadGuard<'b, ClientState>; N]) -> BoxFuture<'b, T> + Send + 'a, + { + let clients = self.0 + .read() + .await; + + let mut client_states: [std::mem::MaybeUninit>; N] = unsafe { + std::mem::MaybeUninit::uninit().assume_init() + }; + + for (cindex, client_id) in client_ids.iter().enumerate() { + let c = clients + .get(client_id) + .ok_or(ClientError::NotFound(*client_id))? + .read() + .await; + client_states[cindex].write(c); + } + + let client_states = unsafe { + // TODO: this should just be a normal transmute but due to compiler limitations it + // does not yet work with const generics + // https://github.com/rust-lang/rust/issues/61956 + std::mem::transmute_copy::<_, [RwLockReadGuard; N]>(&client_states) + }; + + Ok(func(client_states).await) + } + + pub async fn with_mut<'a, T, F>(&'a self, client_id: ClientId, func: F) -> Result + where + T: Send, + F: for<'b> FnOnce(&'b mut ClientState) -> BoxFuture<'b, T> + Send + 'a, + { + let clients = self.0 + .read() + .await; + let mut client = clients + .get(&client_id) + .ok_or(ClientError::NotFound(client_id))? + .write() + .await; + + Ok(func(&mut client).await) + } +} + + +#[derive(Debug, Clone, Copy)] +pub struct ItemDropLocation { + pub map_area: MapArea, + pub x: f32, + pub z: f32, + pub item_id: items::ClientItemId, +} + +pub struct LoadingQuest { + pub header_bin: Option, + pub header_dat: Option, +} + + +pub struct ClientState { + pub user: UserAccountEntity, + pub settings: UserSettingsEntity, + pub character: CharacterEntity, + _session: Session, + //guildcard: GuildCard, + pub block: usize, + pub item_drop_location: Option, + pub done_loading_quest: bool, + pub area: Option, + pub x: f32, + pub y: f32, + pub z: f32, + pub weapon_shop: Vec, + pub tool_shop: Vec, + pub armor_shop: Vec, + pub tek: Option<(items::ClientItemId, item::weapon::TekSpecialModifier, item::weapon::TekPercentModifier, i32)>, + pub character_playtime: chrono::Duration, + pub log_on_time: chrono::DateTime, +} + +impl ClientState { + pub fn new(user: UserAccountEntity, settings: UserSettingsEntity, character: CharacterEntity, session: Session) -> ClientState { + let character_playtime = chrono::Duration::seconds(character.playtime as i64); + ClientState { + user, + settings, + character, + _session: session, + block: 0, + item_drop_location: None, + done_loading_quest: false, + area: None, + x: 0.0, + y: 0.0, + z: 0.0, + weapon_shop: Vec::new(), + tool_shop: Vec::new(), + armor_shop: Vec::new(), + tek: None, + character_playtime, + log_on_time: chrono::Utc::now(), + } + } + + pub fn update_playtime(&mut self) { + let additional_playtime = chrono::Utc::now() - self.log_on_time; + self.character.playtime = (self.character_playtime + additional_playtime).num_seconds() as u32; + } +} + -pub use client::*; diff --git a/location/src/lib.rs b/location/src/lib.rs index dd9bd1e..3dd0b2b 100644 --- a/location/src/lib.rs +++ b/location/src/lib.rs @@ -1,3 +1,676 @@ -pub mod location; +#![allow(dead_code, unused_must_use)] +use std::collections::HashMap; +use std::time::SystemTime; +use thiserror::Error; +use networking::serverstate::ClientId; -pub use location::*; +use async_std::sync::{Arc, RwLock}; +use futures::{stream, StreamExt}; +use std::pin::pin; + +pub const MAX_ROOMS: usize = 128; + +pub enum AreaType { + Room, + Lobby, +} + + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct LobbyId(pub usize); + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)] +pub struct RoomId(pub usize); + +impl LobbyId { + pub fn id(&self) -> u8 { + self.0 as u8 + } +} + + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum CreateRoomError { + #[error("no open slots")] + NoOpenSlots, + #[error("client already in area")] + ClientInAreaAlready, + #[error("join error")] + JoinError, +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum JoinRoomError { + #[error("room does not exist")] + RoomDoesNotExist, + #[error("room is full")] + RoomFull, + #[error("client already in area")] + ClientInAreaAlready, +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum JoinLobbyError { + #[error("lobby does not exist")] + LobbyDoesNotExist, + #[error("lobby is full")] + LobbyFull, + #[error("client already in area")] + ClientInAreaAlready, +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum GetAreaError { + #[error("not in a room")] + NotInRoom, + #[error("not in a lobby")] + NotInLobby, + #[error("get area: invalid client")] + InvalidClient, +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum ClientRemovalError { + #[error("client removal: client not in area")] + ClientNotInArea, + #[error("client removal: invalid area")] + InvalidArea, +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum GetClientsError { + #[error("invalid client")] + InvalidClient, + #[error("invalid area")] + InvalidArea, +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum GetNeighborError { + #[error("get neighbor: invalid client")] + InvalidClient, + #[error("get neighbor: invalid area")] + InvalidArea, +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum GetLeaderError { + #[error("get leader: invalid client")] + InvalidClient, + #[error("get leader: invalid area")] + InvalidArea, + #[error("get leader: client not in area")] + NoClientInArea, +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum ClientLocationError { + #[error("create room error {0}")] + CreateRoomError(#[from] CreateRoomError), + #[error("join room error {0}")] + JoinRoomError(#[from] JoinRoomError), + #[error("join lobby error {0}")] + JoinLobbyError(#[from] JoinLobbyError), + #[error("get area error {0}")] + GetAreaError(#[from] GetAreaError), + #[error("client removal error {0}")] + ClientRemovalError(#[from] ClientRemovalError), + #[error("get clients error {0}")] + GetClientsError(#[from] GetClientsError), + #[error("get neighbor error {0}")] + GetNeighborError(#[from] GetNeighborError), + #[error("get leader error {0}")] + GetLeaderError(#[from] GetLeaderError) +} + + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct LocalClientId(usize); + +impl LocalClientId { + pub fn id(&self) -> u8 { + self.0 as u8 + } +} + +impl PartialEq for LocalClientId { + fn eq(&self, other: &u8) -> bool { + self.0 == *other as usize + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct AreaClient { + pub client: ClientId, + pub local_client: LocalClientId, + time_join: SystemTime, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct Lobby([Option; 12]); +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct Room([Option; 4]); + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum RoomLobby { + Room(RoomId), + Lobby(LobbyId), +} + +#[derive(Clone, Debug)] +pub struct ClientLocation { + lobbies: [Arc>; 15], + rooms: [Arc>>; MAX_ROOMS], + client_location: Arc>>, +} + +impl Default for ClientLocation { + fn default() -> ClientLocation { + ClientLocation { + lobbies: core::array::from_fn(|_| Arc::new(RwLock::new(Lobby([None; 12])))), + rooms: core::array::from_fn(|_| Arc::new(RwLock::new(None))), + client_location: Arc::new(RwLock::new(HashMap::new())), + } + } +} + + +impl ClientLocation { + pub async fn add_client_to_lobby(&self, id: ClientId, lobby_id: LobbyId) -> Result<(), JoinLobbyError> { + { + let lobby = self.lobbies + .get(lobby_id.0) + .ok_or(JoinLobbyError::LobbyDoesNotExist)? + .read() + .await; + if lobby.0.iter().all(|c| c.is_some()) { + return Err(JoinLobbyError::LobbyFull); + } + } + self.remove_client_from_area(id).await; + + let mut lobby = self.lobbies + .get(lobby_id.0) + .ok_or(JoinLobbyError::LobbyDoesNotExist)? + .write() + .await; + let (index, empty_slot) = lobby.0.iter_mut() + .enumerate() + .find(|(_, k)| k.is_none()) + .ok_or(JoinLobbyError::LobbyFull)?; + *empty_slot = Some(AreaClient { + client: id, + local_client: LocalClientId(index), + time_join: SystemTime::now(), + }); + self.client_location + .write() + .await + .insert(id, RoomLobby::Lobby(lobby_id)); + Ok(()) + } + + pub async fn add_client_to_next_available_lobby(&self, id: ClientId, lobby: LobbyId) -> Result { + pin!(stream::iter(0..15) + .filter_map(|lobby_index| async move { + let new_lobby = LobbyId((lobby.0 + lobby_index) % 15); + Some((new_lobby, self.add_client_to_lobby(id, new_lobby).await.ok()?)) + })) + .next() + .await + .map(|l| l.0) + .ok_or(JoinLobbyError::LobbyFull) + } + + pub async fn create_new_room(&mut self, id: ClientId) -> Result { + let (index, empty_slot) = Box::pin(stream::iter(self.rooms.iter()) + .enumerate() + .filter(|(_, r)| async {r.read().await.is_none()})) + .next() + .await + .ok_or(CreateRoomError::NoOpenSlots)?; + *empty_slot.write().await = Some(Room([None; 4])); + self.add_client_to_room(id, RoomId(index)) + .await + .map_err(|_err| CreateRoomError::JoinError)?; + Ok(RoomId(index)) + } + + pub async fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> { + let mut r = self.rooms.get(room.0) + .ok_or(JoinRoomError::RoomDoesNotExist)? + .as_ref() + .write() + .await; + let r = r.as_mut() + .ok_or(JoinRoomError::RoomDoesNotExist)?; + let (index, empty_slot) = r.0.iter_mut() + .enumerate() + .find(|(_, k)| k.is_none()) + .ok_or(JoinRoomError::RoomFull)?; + *empty_slot = Some(AreaClient { + client: id, + local_client: LocalClientId(index), + time_join: SystemTime::now(), + }); + self.remove_client_from_area(id).await; + self.client_location + .write() + .await + .insert(id, RoomLobby::Room(room)); + Ok(()) + } + + pub async fn get_all_clients_by_client(&self, id: ClientId) -> Result, GetNeighborError> { + let area = self.client_location + .read() + .await; + let area = area + .get(&id) + .ok_or(GetNeighborError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .collect()) + }, + RoomLobby::Lobby(lobby) => { + Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .collect()) + } + } + } + + pub async fn get_client_neighbors(&self, id: ClientId) -> Result, GetNeighborError> { + let area = self.client_location + .read() + .await; + let area = area + .get(&id) + .ok_or(GetNeighborError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .filter(|c| c.client != id) + .collect()) + }, + RoomLobby::Lobby(lobby) => { + Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .filter(|c| c.client != id) + .collect()) + } + } + } + + pub async fn get_room_leader(&self, room: RoomId) -> Result { + let r = self.rooms[room.0] + .as_ref() + .read() + .await + .ok_or(GetLeaderError::InvalidArea)?; + let mut r = r + .0 + .iter() + .flatten() + .collect::>(); + r.sort_by_key(|k| k.time_join); + let c = r.get(0) + .ok_or(GetLeaderError::NoClientInArea)?; + Ok(**c) + } + + pub async fn get_lobby_leader(&self, lobby: LobbyId) -> Result { + let l = self.lobbies[lobby.0] + .read() + .await; + let mut l = l + .0 + .iter() + .flatten() + .collect::>(); + l.sort_by_key(|k| k.time_join); + let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?; + Ok(**c) + } + + pub async fn get_area_leader(&self, roomlobby: RoomLobby) -> Result { + match roomlobby { + RoomLobby::Room(room) => { + self.get_room_leader(room).await + }, + RoomLobby::Lobby(lobby) => { + self.get_lobby_leader(lobby).await + } + } + } + + pub async fn get_leader_by_client(&self, id: ClientId) -> Result { + let area = self.client_location + .read() + .await; + let area = area + .get(&id) + .ok_or(GetLeaderError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + self.get_room_leader(*room).await + }, + RoomLobby::Lobby(lobby) => { + self.get_lobby_leader(*lobby).await + } + } + } + + pub async fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result, GetClientsError> { + Ok(self.lobbies + .get(lobby.0) + .ok_or(GetClientsError::InvalidArea)? + .read() + .await + .0 + .iter() + .filter_map(|client| { + client.map(|c| { + c + }) + }).collect()) + } + + pub async fn get_clients_in_room(&self, room: RoomId) -> Result, GetClientsError> { + Ok(self.rooms.get(room.0) + .ok_or(GetClientsError::InvalidArea)? + .as_ref() + .read() + .await + .ok_or(GetClientsError::InvalidArea)? + .0 + .iter() + .filter_map(|client| { + client.map(|c| { + c + }) + }).collect()) + } + + pub async fn get_local_client(&self, id: ClientId) -> Result { + let area = self.client_location + .read() + .await; + let area = area + .get(&id) + .ok_or(GetClientsError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + self.get_clients_in_room(*room) + .await + .map_err(|_| GetClientsError::InvalidArea)? + .into_iter() + .find(|c| c.client == id) + .ok_or(GetClientsError::InvalidClient) + }, + RoomLobby::Lobby(lobby) => { + self.get_clients_in_lobby(*lobby) + .await + .map_err(|_| GetClientsError::InvalidArea)? + .into_iter() + .find(|c| c.client == id) + .ok_or(GetClientsError::InvalidClient) + } + } + } + + pub async fn get_area(&self, id: ClientId) -> Result { + self.client_location + .read() + .await + .get(&id) + .ok_or(GetAreaError::InvalidClient) + .map(Clone::clone) + } + + pub async fn get_room(&self, id: ClientId) -> Result { + if let RoomLobby::Room(room) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? { + Ok(*room) + } + else { + Err(GetAreaError::NotInRoom) + } + } + + pub async fn get_lobby(&self, id: ClientId) -> Result { + if let RoomLobby::Lobby(lobby) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? { + Ok(*lobby) + } + else { + Err(GetAreaError::NotInLobby) + } + } + + pub async fn remove_client_from_area(&self, id: ClientId) -> Result<(), ClientRemovalError> { + fn remove_client(id: ClientId, client_list : &mut [Option; N]) { + client_list + .iter_mut() + .filter(|client| { + client.map_or(false, |c| { + c.client == id + }) + }) + .for_each(|client| { + *client = None + }); + } + + let area = self.client_location + .read() + .await; + let area = area + .get(&id) + .ok_or(ClientRemovalError::ClientNotInArea)?; + match area { + RoomLobby::Room(room) => { + let mut r = self.rooms.get(room.0) + .ok_or(ClientRemovalError::InvalidArea)? + .as_ref() + .write() + .await; + if let Some(r) = r.as_mut() { + remove_client(id, &mut r.0) + } + else { + return Err(ClientRemovalError::InvalidArea) + } + }, + RoomLobby::Lobby(lobby) => { + remove_client(id, &mut self.lobbies[lobby.0].write().await.0) + } + }; + + Ok(()) + } +} + + + + + + + + +#[cfg(test)] +mod test { + use super::*; + + #[async_std::test] + async fn test_add_client_to_lobby() { + let cl = ClientLocation::default(); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap(); + cl.add_client_to_lobby(ClientId(13), LobbyId(1)).await.unwrap(); + cl.add_client_to_lobby(ClientId(14), LobbyId(0)).await.unwrap(); + + assert!(cl.get_clients_in_lobby(LobbyId(0)).await.into_iter().flatten().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(12), LocalClientId(0)), + (ClientId(14), LocalClientId(1)), + ]); + } + + #[async_std::test] + async fn test_add_client_to_full_lobby() { + let cl = ClientLocation::default(); + for i in 0..12 { + cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap(); + } + assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await == Err(JoinLobbyError::LobbyFull)); + } + + #[async_std::test] + async fn test_add_client_to_next_available_lobby() { + let cl = ClientLocation::default(); + for lobby in 1..4 { + for i in 0..12 { + cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap(); + } + } + assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await == Ok(LobbyId(4))); + } + + #[async_std::test] + async fn test_add_to_lobby_when_all_are_full() { + let cl = ClientLocation::default(); + for lobby in 0..15 { + for i in 0..12 { + cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap(); + } + } + assert_eq!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await, Err(JoinLobbyError::LobbyFull)); + } + + #[async_std::test] + async fn test_new_room() { + let mut cl = ClientLocation::default(); + assert!(cl.create_new_room(ClientId(12)).await == Ok(RoomId(0))); + } + + #[async_std::test] + async fn test_add_client_to_room() { + let mut cl = ClientLocation::default(); + let room = cl.create_new_room(ClientId(12)).await.unwrap(); + assert!(cl.add_client_to_room(ClientId(234), room).await == Ok(())); + assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(12), LocalClientId(0)), + (ClientId(234), LocalClientId(1)), + ]); + } + + #[async_std::test] + async fn test_no_new_room_slots() { + let mut cl = ClientLocation::default(); + for i in 0..128 { + cl.create_new_room(ClientId(i)).await; + } + assert!(cl.create_new_room(ClientId(234)).await == Err(CreateRoomError::NoOpenSlots)); + } + + #[async_std::test] + async fn test_joining_full_room() { + let mut cl = ClientLocation::default(); + let room = cl.create_new_room(ClientId(0)).await.unwrap(); + assert!(cl.add_client_to_room(ClientId(1), room).await == Ok(())); + assert!(cl.add_client_to_room(ClientId(2), room).await == Ok(())); + assert!(cl.add_client_to_room(ClientId(3), room).await == Ok(())); + assert!(cl.add_client_to_room(ClientId(234), room).await == Err(JoinRoomError::RoomFull)); + } + + #[async_std::test] + async fn test_adding_client_to_room_removes_from_lobby() { + let mut cl = ClientLocation::default(); + cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await; + cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await; + cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await; + cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await; + + let room = cl.create_new_room(ClientId(51)).await.unwrap(); + assert!(cl.add_client_to_room(ClientId(93), room).await == Ok(())); + assert!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(23), LocalClientId(1)), + (ClientId(12), LocalClientId(3)), + ]); + assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(51), LocalClientId(0)), + (ClientId(93), LocalClientId(1)), + ]); + } + + #[async_std::test] + async fn test_getting_neighbors() { + let cl = ClientLocation::default(); + cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await.unwrap(); + cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await.unwrap(); + cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await.unwrap(); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap(); + + assert!(cl.get_client_neighbors(ClientId(23)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(93), LocalClientId(0)), + (ClientId(51), LocalClientId(2)), + (ClientId(12), LocalClientId(3)), + ]); + } + + #[async_std::test] + async fn test_failing_to_join_lobby_does_not_remove_from_current_area() { + let cl = ClientLocation::default(); + for i in 0..12 { + cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap(); + } + assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(1)).await.is_ok()); + assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await.is_err()); + assert_eq!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().len(), 12); + assert_eq!( + cl.get_clients_in_lobby(LobbyId(1)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>(), + vec![(ClientId(99), LocalClientId(0))] + ); + } + + #[async_std::test] + async fn test_get_leader() { + let cl = ClientLocation::default(); + cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await; + cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await; + cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await; + cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await; + + assert!(cl.get_leader_by_client(ClientId(51)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(0)))); + } + + #[async_std::test] + async fn test_remove_client_from_room() { + let mut cl = ClientLocation::default(); + let room = cl.create_new_room(ClientId(51)).await.unwrap(); + cl.add_client_to_room(ClientId(93), room).await; + cl.add_client_to_room(ClientId(23), room).await; + cl.remove_client_from_area(ClientId(51)).await; + cl.add_client_to_room(ClientId(12), room).await; + + assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ + (ClientId(12), LocalClientId(0)), + (ClientId(93), LocalClientId(1)), + (ClientId(23), LocalClientId(2)), + ]); + } + + #[async_std::test] + async fn test_leader_changes_on_leader_leaving() { + let mut cl = ClientLocation::default(); + let room = cl.create_new_room(ClientId(51)).await.unwrap(); + cl.add_client_to_room(ClientId(93), room).await.unwrap(); + cl.add_client_to_room(ClientId(23), room).await.unwrap(); + cl.remove_client_from_area(ClientId(51)).await.unwrap(); + cl.add_client_to_room(ClientId(12), room).await.unwrap(); + cl.remove_client_from_area(ClientId(23)).await.unwrap(); + cl.add_client_to_room(ClientId(99), room).await.unwrap(); + + assert!(cl.get_leader_by_client(ClientId(12)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(1)))); + } +} diff --git a/location/src/location.rs b/location/src/location.rs deleted file mode 100644 index 3dd0b2b..0000000 --- a/location/src/location.rs +++ /dev/null @@ -1,676 +0,0 @@ -#![allow(dead_code, unused_must_use)] -use std::collections::HashMap; -use std::time::SystemTime; -use thiserror::Error; -use networking::serverstate::ClientId; - -use async_std::sync::{Arc, RwLock}; -use futures::{stream, StreamExt}; -use std::pin::pin; - -pub const MAX_ROOMS: usize = 128; - -pub enum AreaType { - Room, - Lobby, -} - - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct LobbyId(pub usize); - -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)] -pub struct RoomId(pub usize); - -impl LobbyId { - pub fn id(&self) -> u8 { - self.0 as u8 - } -} - - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum CreateRoomError { - #[error("no open slots")] - NoOpenSlots, - #[error("client already in area")] - ClientInAreaAlready, - #[error("join error")] - JoinError, -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum JoinRoomError { - #[error("room does not exist")] - RoomDoesNotExist, - #[error("room is full")] - RoomFull, - #[error("client already in area")] - ClientInAreaAlready, -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum JoinLobbyError { - #[error("lobby does not exist")] - LobbyDoesNotExist, - #[error("lobby is full")] - LobbyFull, - #[error("client already in area")] - ClientInAreaAlready, -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum GetAreaError { - #[error("not in a room")] - NotInRoom, - #[error("not in a lobby")] - NotInLobby, - #[error("get area: invalid client")] - InvalidClient, -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum ClientRemovalError { - #[error("client removal: client not in area")] - ClientNotInArea, - #[error("client removal: invalid area")] - InvalidArea, -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum GetClientsError { - #[error("invalid client")] - InvalidClient, - #[error("invalid area")] - InvalidArea, -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum GetNeighborError { - #[error("get neighbor: invalid client")] - InvalidClient, - #[error("get neighbor: invalid area")] - InvalidArea, -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum GetLeaderError { - #[error("get leader: invalid client")] - InvalidClient, - #[error("get leader: invalid area")] - InvalidArea, - #[error("get leader: client not in area")] - NoClientInArea, -} - -#[derive(Error, Debug, PartialEq, Eq)] -pub enum ClientLocationError { - #[error("create room error {0}")] - CreateRoomError(#[from] CreateRoomError), - #[error("join room error {0}")] - JoinRoomError(#[from] JoinRoomError), - #[error("join lobby error {0}")] - JoinLobbyError(#[from] JoinLobbyError), - #[error("get area error {0}")] - GetAreaError(#[from] GetAreaError), - #[error("client removal error {0}")] - ClientRemovalError(#[from] ClientRemovalError), - #[error("get clients error {0}")] - GetClientsError(#[from] GetClientsError), - #[error("get neighbor error {0}")] - GetNeighborError(#[from] GetNeighborError), - #[error("get leader error {0}")] - GetLeaderError(#[from] GetLeaderError) -} - - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct LocalClientId(usize); - -impl LocalClientId { - pub fn id(&self) -> u8 { - self.0 as u8 - } -} - -impl PartialEq for LocalClientId { - fn eq(&self, other: &u8) -> bool { - self.0 == *other as usize - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct AreaClient { - pub client: ClientId, - pub local_client: LocalClientId, - time_join: SystemTime, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -struct Lobby([Option; 12]); -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -struct Room([Option; 4]); - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum RoomLobby { - Room(RoomId), - Lobby(LobbyId), -} - -#[derive(Clone, Debug)] -pub struct ClientLocation { - lobbies: [Arc>; 15], - rooms: [Arc>>; MAX_ROOMS], - client_location: Arc>>, -} - -impl Default for ClientLocation { - fn default() -> ClientLocation { - ClientLocation { - lobbies: core::array::from_fn(|_| Arc::new(RwLock::new(Lobby([None; 12])))), - rooms: core::array::from_fn(|_| Arc::new(RwLock::new(None))), - client_location: Arc::new(RwLock::new(HashMap::new())), - } - } -} - - -impl ClientLocation { - pub async fn add_client_to_lobby(&self, id: ClientId, lobby_id: LobbyId) -> Result<(), JoinLobbyError> { - { - let lobby = self.lobbies - .get(lobby_id.0) - .ok_or(JoinLobbyError::LobbyDoesNotExist)? - .read() - .await; - if lobby.0.iter().all(|c| c.is_some()) { - return Err(JoinLobbyError::LobbyFull); - } - } - self.remove_client_from_area(id).await; - - let mut lobby = self.lobbies - .get(lobby_id.0) - .ok_or(JoinLobbyError::LobbyDoesNotExist)? - .write() - .await; - let (index, empty_slot) = lobby.0.iter_mut() - .enumerate() - .find(|(_, k)| k.is_none()) - .ok_or(JoinLobbyError::LobbyFull)?; - *empty_slot = Some(AreaClient { - client: id, - local_client: LocalClientId(index), - time_join: SystemTime::now(), - }); - self.client_location - .write() - .await - .insert(id, RoomLobby::Lobby(lobby_id)); - Ok(()) - } - - pub async fn add_client_to_next_available_lobby(&self, id: ClientId, lobby: LobbyId) -> Result { - pin!(stream::iter(0..15) - .filter_map(|lobby_index| async move { - let new_lobby = LobbyId((lobby.0 + lobby_index) % 15); - Some((new_lobby, self.add_client_to_lobby(id, new_lobby).await.ok()?)) - })) - .next() - .await - .map(|l| l.0) - .ok_or(JoinLobbyError::LobbyFull) - } - - pub async fn create_new_room(&mut self, id: ClientId) -> Result { - let (index, empty_slot) = Box::pin(stream::iter(self.rooms.iter()) - .enumerate() - .filter(|(_, r)| async {r.read().await.is_none()})) - .next() - .await - .ok_or(CreateRoomError::NoOpenSlots)?; - *empty_slot.write().await = Some(Room([None; 4])); - self.add_client_to_room(id, RoomId(index)) - .await - .map_err(|_err| CreateRoomError::JoinError)?; - Ok(RoomId(index)) - } - - pub async fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> { - let mut r = self.rooms.get(room.0) - .ok_or(JoinRoomError::RoomDoesNotExist)? - .as_ref() - .write() - .await; - let r = r.as_mut() - .ok_or(JoinRoomError::RoomDoesNotExist)?; - let (index, empty_slot) = r.0.iter_mut() - .enumerate() - .find(|(_, k)| k.is_none()) - .ok_or(JoinRoomError::RoomFull)?; - *empty_slot = Some(AreaClient { - client: id, - local_client: LocalClientId(index), - time_join: SystemTime::now(), - }); - self.remove_client_from_area(id).await; - self.client_location - .write() - .await - .insert(id, RoomLobby::Room(room)); - Ok(()) - } - - pub async fn get_all_clients_by_client(&self, id: ClientId) -> Result, GetNeighborError> { - let area = self.client_location - .read() - .await; - let area = area - .get(&id) - .ok_or(GetNeighborError::InvalidClient)?; - match area { - RoomLobby::Room(room) => { - Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)? - .into_iter() - .collect()) - }, - RoomLobby::Lobby(lobby) => { - Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)? - .into_iter() - .collect()) - } - } - } - - pub async fn get_client_neighbors(&self, id: ClientId) -> Result, GetNeighborError> { - let area = self.client_location - .read() - .await; - let area = area - .get(&id) - .ok_or(GetNeighborError::InvalidClient)?; - match area { - RoomLobby::Room(room) => { - Ok(self.get_clients_in_room(*room).await.map_err(|_| GetNeighborError::InvalidArea)? - .into_iter() - .filter(|c| c.client != id) - .collect()) - }, - RoomLobby::Lobby(lobby) => { - Ok(self.get_clients_in_lobby(*lobby).await.map_err(|_| GetNeighborError::InvalidArea)? - .into_iter() - .filter(|c| c.client != id) - .collect()) - } - } - } - - pub async fn get_room_leader(&self, room: RoomId) -> Result { - let r = self.rooms[room.0] - .as_ref() - .read() - .await - .ok_or(GetLeaderError::InvalidArea)?; - let mut r = r - .0 - .iter() - .flatten() - .collect::>(); - r.sort_by_key(|k| k.time_join); - let c = r.get(0) - .ok_or(GetLeaderError::NoClientInArea)?; - Ok(**c) - } - - pub async fn get_lobby_leader(&self, lobby: LobbyId) -> Result { - let l = self.lobbies[lobby.0] - .read() - .await; - let mut l = l - .0 - .iter() - .flatten() - .collect::>(); - l.sort_by_key(|k| k.time_join); - let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?; - Ok(**c) - } - - pub async fn get_area_leader(&self, roomlobby: RoomLobby) -> Result { - match roomlobby { - RoomLobby::Room(room) => { - self.get_room_leader(room).await - }, - RoomLobby::Lobby(lobby) => { - self.get_lobby_leader(lobby).await - } - } - } - - pub async fn get_leader_by_client(&self, id: ClientId) -> Result { - let area = self.client_location - .read() - .await; - let area = area - .get(&id) - .ok_or(GetLeaderError::InvalidClient)?; - match area { - RoomLobby::Room(room) => { - self.get_room_leader(*room).await - }, - RoomLobby::Lobby(lobby) => { - self.get_lobby_leader(*lobby).await - } - } - } - - pub async fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result, GetClientsError> { - Ok(self.lobbies - .get(lobby.0) - .ok_or(GetClientsError::InvalidArea)? - .read() - .await - .0 - .iter() - .filter_map(|client| { - client.map(|c| { - c - }) - }).collect()) - } - - pub async fn get_clients_in_room(&self, room: RoomId) -> Result, GetClientsError> { - Ok(self.rooms.get(room.0) - .ok_or(GetClientsError::InvalidArea)? - .as_ref() - .read() - .await - .ok_or(GetClientsError::InvalidArea)? - .0 - .iter() - .filter_map(|client| { - client.map(|c| { - c - }) - }).collect()) - } - - pub async fn get_local_client(&self, id: ClientId) -> Result { - let area = self.client_location - .read() - .await; - let area = area - .get(&id) - .ok_or(GetClientsError::InvalidClient)?; - match area { - RoomLobby::Room(room) => { - self.get_clients_in_room(*room) - .await - .map_err(|_| GetClientsError::InvalidArea)? - .into_iter() - .find(|c| c.client == id) - .ok_or(GetClientsError::InvalidClient) - }, - RoomLobby::Lobby(lobby) => { - self.get_clients_in_lobby(*lobby) - .await - .map_err(|_| GetClientsError::InvalidArea)? - .into_iter() - .find(|c| c.client == id) - .ok_or(GetClientsError::InvalidClient) - } - } - } - - pub async fn get_area(&self, id: ClientId) -> Result { - self.client_location - .read() - .await - .get(&id) - .ok_or(GetAreaError::InvalidClient) - .map(Clone::clone) - } - - pub async fn get_room(&self, id: ClientId) -> Result { - if let RoomLobby::Room(room) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? { - Ok(*room) - } - else { - Err(GetAreaError::NotInRoom) - } - } - - pub async fn get_lobby(&self, id: ClientId) -> Result { - if let RoomLobby::Lobby(lobby) = self.client_location.read().await.get(&id).ok_or(GetAreaError::InvalidClient)? { - Ok(*lobby) - } - else { - Err(GetAreaError::NotInLobby) - } - } - - pub async fn remove_client_from_area(&self, id: ClientId) -> Result<(), ClientRemovalError> { - fn remove_client(id: ClientId, client_list : &mut [Option; N]) { - client_list - .iter_mut() - .filter(|client| { - client.map_or(false, |c| { - c.client == id - }) - }) - .for_each(|client| { - *client = None - }); - } - - let area = self.client_location - .read() - .await; - let area = area - .get(&id) - .ok_or(ClientRemovalError::ClientNotInArea)?; - match area { - RoomLobby::Room(room) => { - let mut r = self.rooms.get(room.0) - .ok_or(ClientRemovalError::InvalidArea)? - .as_ref() - .write() - .await; - if let Some(r) = r.as_mut() { - remove_client(id, &mut r.0) - } - else { - return Err(ClientRemovalError::InvalidArea) - } - }, - RoomLobby::Lobby(lobby) => { - remove_client(id, &mut self.lobbies[lobby.0].write().await.0) - } - }; - - Ok(()) - } -} - - - - - - - - -#[cfg(test)] -mod test { - use super::*; - - #[async_std::test] - async fn test_add_client_to_lobby() { - let cl = ClientLocation::default(); - cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap(); - cl.add_client_to_lobby(ClientId(13), LobbyId(1)).await.unwrap(); - cl.add_client_to_lobby(ClientId(14), LobbyId(0)).await.unwrap(); - - assert!(cl.get_clients_in_lobby(LobbyId(0)).await.into_iter().flatten().map(|c| (c.client, c.local_client)).collect::>() == vec![ - (ClientId(12), LocalClientId(0)), - (ClientId(14), LocalClientId(1)), - ]); - } - - #[async_std::test] - async fn test_add_client_to_full_lobby() { - let cl = ClientLocation::default(); - for i in 0..12 { - cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap(); - } - assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await == Err(JoinLobbyError::LobbyFull)); - } - - #[async_std::test] - async fn test_add_client_to_next_available_lobby() { - let cl = ClientLocation::default(); - for lobby in 1..4 { - for i in 0..12 { - cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap(); - } - } - assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await == Ok(LobbyId(4))); - } - - #[async_std::test] - async fn test_add_to_lobby_when_all_are_full() { - let cl = ClientLocation::default(); - for lobby in 0..15 { - for i in 0..12 { - cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)).await.unwrap(); - } - } - assert_eq!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)).await, Err(JoinLobbyError::LobbyFull)); - } - - #[async_std::test] - async fn test_new_room() { - let mut cl = ClientLocation::default(); - assert!(cl.create_new_room(ClientId(12)).await == Ok(RoomId(0))); - } - - #[async_std::test] - async fn test_add_client_to_room() { - let mut cl = ClientLocation::default(); - let room = cl.create_new_room(ClientId(12)).await.unwrap(); - assert!(cl.add_client_to_room(ClientId(234), room).await == Ok(())); - assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ - (ClientId(12), LocalClientId(0)), - (ClientId(234), LocalClientId(1)), - ]); - } - - #[async_std::test] - async fn test_no_new_room_slots() { - let mut cl = ClientLocation::default(); - for i in 0..128 { - cl.create_new_room(ClientId(i)).await; - } - assert!(cl.create_new_room(ClientId(234)).await == Err(CreateRoomError::NoOpenSlots)); - } - - #[async_std::test] - async fn test_joining_full_room() { - let mut cl = ClientLocation::default(); - let room = cl.create_new_room(ClientId(0)).await.unwrap(); - assert!(cl.add_client_to_room(ClientId(1), room).await == Ok(())); - assert!(cl.add_client_to_room(ClientId(2), room).await == Ok(())); - assert!(cl.add_client_to_room(ClientId(3), room).await == Ok(())); - assert!(cl.add_client_to_room(ClientId(234), room).await == Err(JoinRoomError::RoomFull)); - } - - #[async_std::test] - async fn test_adding_client_to_room_removes_from_lobby() { - let mut cl = ClientLocation::default(); - cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await; - cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await; - cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await; - cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await; - - let room = cl.create_new_room(ClientId(51)).await.unwrap(); - assert!(cl.add_client_to_room(ClientId(93), room).await == Ok(())); - assert!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ - (ClientId(23), LocalClientId(1)), - (ClientId(12), LocalClientId(3)), - ]); - assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ - (ClientId(51), LocalClientId(0)), - (ClientId(93), LocalClientId(1)), - ]); - } - - #[async_std::test] - async fn test_getting_neighbors() { - let cl = ClientLocation::default(); - cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await.unwrap(); - cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await.unwrap(); - cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await.unwrap(); - cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await.unwrap(); - - assert!(cl.get_client_neighbors(ClientId(23)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ - (ClientId(93), LocalClientId(0)), - (ClientId(51), LocalClientId(2)), - (ClientId(12), LocalClientId(3)), - ]); - } - - #[async_std::test] - async fn test_failing_to_join_lobby_does_not_remove_from_current_area() { - let cl = ClientLocation::default(); - for i in 0..12 { - cl.add_client_to_lobby(ClientId(i), LobbyId(0)).await.unwrap(); - } - assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(1)).await.is_ok()); - assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)).await.is_err()); - assert_eq!(cl.get_clients_in_lobby(LobbyId(0)).await.unwrap().len(), 12); - assert_eq!( - cl.get_clients_in_lobby(LobbyId(1)).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>(), - vec![(ClientId(99), LocalClientId(0))] - ); - } - - #[async_std::test] - async fn test_get_leader() { - let cl = ClientLocation::default(); - cl.add_client_to_lobby(ClientId(93), LobbyId(0)).await; - cl.add_client_to_lobby(ClientId(23), LobbyId(0)).await; - cl.add_client_to_lobby(ClientId(51), LobbyId(0)).await; - cl.add_client_to_lobby(ClientId(12), LobbyId(0)).await; - - assert!(cl.get_leader_by_client(ClientId(51)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(0)))); - } - - #[async_std::test] - async fn test_remove_client_from_room() { - let mut cl = ClientLocation::default(); - let room = cl.create_new_room(ClientId(51)).await.unwrap(); - cl.add_client_to_room(ClientId(93), room).await; - cl.add_client_to_room(ClientId(23), room).await; - cl.remove_client_from_area(ClientId(51)).await; - cl.add_client_to_room(ClientId(12), room).await; - - assert!(cl.get_clients_in_room(room).await.unwrap().into_iter().map(|c| (c.client, c.local_client)).collect::>() == vec![ - (ClientId(12), LocalClientId(0)), - (ClientId(93), LocalClientId(1)), - (ClientId(23), LocalClientId(2)), - ]); - } - - #[async_std::test] - async fn test_leader_changes_on_leader_leaving() { - let mut cl = ClientLocation::default(); - let room = cl.create_new_room(ClientId(51)).await.unwrap(); - cl.add_client_to_room(ClientId(93), room).await.unwrap(); - cl.add_client_to_room(ClientId(23), room).await.unwrap(); - cl.remove_client_from_area(ClientId(51)).await.unwrap(); - cl.add_client_to_room(ClientId(12), room).await.unwrap(); - cl.remove_client_from_area(ClientId(23)).await.unwrap(); - cl.add_client_to_room(ClientId(99), room).await.unwrap(); - - assert!(cl.get_leader_by_client(ClientId(12)).await.map(|c| (c.client, c.local_client)) == Ok((ClientId(93), LocalClientId(1)))); - } -} diff --git a/quests/src/lib.rs b/quests/src/lib.rs index de40457..91e5d4d 100644 --- a/quests/src/lib.rs +++ b/quests/src/lib.rs @@ -1,4 +1,328 @@ -pub mod quests; +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; +use async_std::sync::Arc; +use thiserror::Error; +use serde::{Serialize, Deserialize}; +use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder}; +use byteorder::{LittleEndian, ReadBytesExt}; +use libpso::util::array_to_utf16; +use maps::area::{MapArea, MapAreaError}; +use maps::object::MapObject; +use maps::enemy::MapEnemy; +use maps::maps::{objects_from_stream, enemy_data_from_stream}; +use maps::room::{Episode, RoomMode}; +use maps::area::{MapAreaLookup, MapAreaLookupBuilder}; -pub use quests::*; +#[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, +} + +#[derive(Debug, Serialize, Deserialize)] +struct QuestListConfig { + questlist: HashMap>, +} + +#[derive(Error, Debug)] +#[error("")] +pub enum ParseDatError { + IoError(#[from] std::io::Error), + MapError(#[from] MapAreaError), + UnknownDatHeader(u32), + CouldNotDetermineEpisode, + InvalidMapAreaId(u16), +} + +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>), + Enemy(Vec>), + Wave, +} + + +fn read_dat_section_header(cursor: &mut T, episode: &Episode, map_areas: &MapAreaLookup) -> Result { + let header = cursor.read_u32::()?; + let _offset = cursor.read_u32::()?; + let area = cursor.read_u16::()?; + let _unknown1 = cursor.read_u16::()?; + let length = cursor.read_u32::()?; + + let map_area = map_areas.get_area_map(area).map_err(|_| ParseDatError::InvalidMapAreaId(area))?; + + match header { + DAT_OBJECT_HEADER_ID => { + let mut obj_data = vec![0u8; length as usize]; + cursor.read_exact(&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_exact(&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 { + for bytes in bin.windows(3) { + // set_episode + if bytes[0] == 0xF8 && bytes[1] == 0xBC { + return Episode::from_quest(bytes[2]) + } + } + None +} + +fn map_area_mappings(bin: &[u8]) -> MapAreaLookup { + let mut map_areas = MapAreaLookupBuilder::default(); + for bytes in bin.windows(4) { + // BB_Map_Designate + if bytes[0] == 0xF9 && bytes[1] == 0x51 { + //return Some(Episode::from_quest(bytes[2]).ok()?) + let floor_value = bytes[2] as u16; + if let Some(map_area) = MapArea::from_bb_map_designate(bytes[3]) { + map_areas = map_areas.add(floor_value, map_area); + } + } + } + map_areas.build() +} + +#[allow(clippy::type_complexity)] +fn parse_dat(dat: &[u8], episode: &Episode, map_areas: &MapAreaLookup) -> Result<(Vec>, Vec>), ParseDatError> { + let mut cursor = Cursor::new(dat); + + let header_iter = std::iter::from_fn(move || { + match read_dat_section_header(&mut cursor, episode, map_areas) { + 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)] +pub enum QuestLoadError { + #[error("io error {0}")] + IoError(#[from] std::io::Error), + #[error("parse dat error {0}")] + ParseDatError(#[from] ParseDatError), + #[error("could not read metadata")] + CouldNotReadMetadata, + #[error("could not load config file")] + CouldNotLoadConfigFile, +} + +#[derive(Debug, Clone)] +pub struct Quest { + pub name: String, + pub description: String, + pub full_description: String, + pub language: u16, + pub id: u16, + pub bin_blob: Arc>, + pub dat_blob: Arc>, + pub enemies: Vec>, // TODO: Arc? + pub objects: Vec>, // TODO: Arc? + pub map_areas: MapAreaLookup, // TODO: Arc? +} + +impl Quest { + fn from_bin_dat(bin: Vec, dat: Vec) -> Result { + let id = u16::from_le_bytes(bin[16..18].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?); + let language = u16::from_le_bytes(bin[18..20].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?); + 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 map_areas = map_area_mappings(&bin); + let (enemies, objects) = parse_dat(&dat, &episode, &map_areas)?; + + let mut prs_bin = LegacyPrsEncoder::new(Vec::new()); + prs_bin.write_all(&bin)?; + let mut prs_dat = LegacyPrsEncoder::new(Vec::new()); + prs_dat.write_all(&dat)?; + + Ok(Quest { + name, + description, + full_description, + id, + language, + bin_blob: Arc::new(prs_bin.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?), + dat_blob: Arc::new(prs_dat.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?), + enemies, + objects, + map_areas, + }) + } +} + +// QuestCollection +pub type QuestList = BTreeMap>; + +pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf, quest_path: PathBuf) -> Option { + let dat_file = File::open(quest_path.join(dat_path.clone())) + .map_err(|err| { + warn!("could not load quest file {:?}: {:?}", dat_path, err) + }).ok()?; + let bin_file = File::open(quest_path.join(bin_path.clone())) + .map_err(|err| { + warn!("could not load quest file {:?}: {:?}", bin_path, err) + }).ok()?; + 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).ok()?; + bin_prs.read_to_end(&mut bin).ok()?; + + let quest = Quest::from_bin_dat(bin, dat).map_err(|err| { + warn!("could not parse quest file {:?}/{:?}: {:?}", bin_path, dat_path, err) + }).ok()?; + Some(quest) +} + + +pub fn load_quests_path(mut quest_path: PathBuf) -> Result { + let mut f = File::open(quest_path.clone()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + quest_path.pop(); // remove quests.toml from the path + let mut used_quest_ids = BTreeSet::new(); + let ql: BTreeMap = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?; + + Ok(ql.into_iter().map(|(category, category_details)| { + ( + QuestCategory { + index: category_details.list_order, + name: category, + description: category_details.description, + }, + category_details.quests + .into_iter() + .filter_map(|quest| { + load_quest(quest.bin.into(), quest.dat.into(), quest_path.to_path_buf()) + .and_then(|quest | { + if used_quest_ids.contains(&quest.id) { + warn!("quest id already exists: {}", quest.id); + return None; + } + used_quest_ids.insert(quest.id); + Some(quest) + }) + }) + .collect() + ) + }).collect()) +} + +pub fn load_standard_quests(mode: RoomMode) -> Result { + match mode { + RoomMode::Single {episode, .. } => { + load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "single", "quests.toml"])) + }, + RoomMode::Multi {episode, .. } => { + load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "multi", "quests.toml"])) + }, + _ => { + Ok(BTreeMap::new()) + } + } +} + + +pub fn load_government_quests(mode: RoomMode) -> Result { + match mode { + RoomMode::Single {episode, .. } => { + load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"])) + }, + RoomMode::Multi {episode, .. } => { + load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"])) + }, + _ => { + Ok(BTreeMap::new()) + } + } +} + + + + + + +#[cfg(test)] +mod tests { + use super::*; + + // the quest phantasmal world 4 uses the tower map twice, to do this it had to remap + // one of the other maps to be a second tower + #[test] + fn test_quest_with_remapped_floors() { + let pw4 = load_quest("q236-ext-bb.bin".into(), "q236-ext-bb.dat".into(), "data/quests/bb/ep2/multi".into()).unwrap(); + let enemies_not_in_tower = pw4.enemies.iter() + .filter(|enemy| { + enemy.is_some() + }) + .filter(|enemy| { + enemy.unwrap().map_area != MapArea::Tower + }); + assert!(enemies_not_in_tower.count() == 0); + + } +} diff --git a/quests/src/quests.rs b/quests/src/quests.rs deleted file mode 100644 index 91e5d4d..0000000 --- a/quests/src/quests.rs +++ /dev/null @@ -1,328 +0,0 @@ -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; -use async_std::sync::Arc; -use thiserror::Error; -use serde::{Serialize, Deserialize}; -use ages_prs::{LegacyPrsDecoder, LegacyPrsEncoder}; -use byteorder::{LittleEndian, ReadBytesExt}; -use libpso::util::array_to_utf16; -use maps::area::{MapArea, MapAreaError}; -use maps::object::MapObject; -use maps::enemy::MapEnemy; -use maps::maps::{objects_from_stream, enemy_data_from_stream}; -use maps::room::{Episode, RoomMode}; -use maps::area::{MapAreaLookup, MapAreaLookupBuilder}; - - -#[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, -} - -#[derive(Debug, Serialize, Deserialize)] -struct QuestListConfig { - questlist: HashMap>, -} - -#[derive(Error, Debug)] -#[error("")] -pub enum ParseDatError { - IoError(#[from] std::io::Error), - MapError(#[from] MapAreaError), - UnknownDatHeader(u32), - CouldNotDetermineEpisode, - InvalidMapAreaId(u16), -} - -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>), - Enemy(Vec>), - Wave, -} - - -fn read_dat_section_header(cursor: &mut T, episode: &Episode, map_areas: &MapAreaLookup) -> Result { - let header = cursor.read_u32::()?; - let _offset = cursor.read_u32::()?; - let area = cursor.read_u16::()?; - let _unknown1 = cursor.read_u16::()?; - let length = cursor.read_u32::()?; - - let map_area = map_areas.get_area_map(area).map_err(|_| ParseDatError::InvalidMapAreaId(area))?; - - match header { - DAT_OBJECT_HEADER_ID => { - let mut obj_data = vec![0u8; length as usize]; - cursor.read_exact(&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_exact(&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 { - for bytes in bin.windows(3) { - // set_episode - if bytes[0] == 0xF8 && bytes[1] == 0xBC { - return Episode::from_quest(bytes[2]) - } - } - None -} - -fn map_area_mappings(bin: &[u8]) -> MapAreaLookup { - let mut map_areas = MapAreaLookupBuilder::default(); - for bytes in bin.windows(4) { - // BB_Map_Designate - if bytes[0] == 0xF9 && bytes[1] == 0x51 { - //return Some(Episode::from_quest(bytes[2]).ok()?) - let floor_value = bytes[2] as u16; - if let Some(map_area) = MapArea::from_bb_map_designate(bytes[3]) { - map_areas = map_areas.add(floor_value, map_area); - } - } - } - map_areas.build() -} - -#[allow(clippy::type_complexity)] -fn parse_dat(dat: &[u8], episode: &Episode, map_areas: &MapAreaLookup) -> Result<(Vec>, Vec>), ParseDatError> { - let mut cursor = Cursor::new(dat); - - let header_iter = std::iter::from_fn(move || { - match read_dat_section_header(&mut cursor, episode, map_areas) { - 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)] -pub enum QuestLoadError { - #[error("io error {0}")] - IoError(#[from] std::io::Error), - #[error("parse dat error {0}")] - ParseDatError(#[from] ParseDatError), - #[error("could not read metadata")] - CouldNotReadMetadata, - #[error("could not load config file")] - CouldNotLoadConfigFile, -} - -#[derive(Debug, Clone)] -pub struct Quest { - pub name: String, - pub description: String, - pub full_description: String, - pub language: u16, - pub id: u16, - pub bin_blob: Arc>, - pub dat_blob: Arc>, - pub enemies: Vec>, // TODO: Arc? - pub objects: Vec>, // TODO: Arc? - pub map_areas: MapAreaLookup, // TODO: Arc? -} - -impl Quest { - fn from_bin_dat(bin: Vec, dat: Vec) -> Result { - let id = u16::from_le_bytes(bin[16..18].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?); - let language = u16::from_le_bytes(bin[18..20].try_into().map_err(|_| QuestLoadError::CouldNotReadMetadata)?); - 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 map_areas = map_area_mappings(&bin); - let (enemies, objects) = parse_dat(&dat, &episode, &map_areas)?; - - let mut prs_bin = LegacyPrsEncoder::new(Vec::new()); - prs_bin.write_all(&bin)?; - let mut prs_dat = LegacyPrsEncoder::new(Vec::new()); - prs_dat.write_all(&dat)?; - - Ok(Quest { - name, - description, - full_description, - id, - language, - bin_blob: Arc::new(prs_bin.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?), - dat_blob: Arc::new(prs_dat.into_inner().map_err(|_| QuestLoadError::CouldNotReadMetadata)?), - enemies, - objects, - map_areas, - }) - } -} - -// QuestCollection -pub type QuestList = BTreeMap>; - -pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf, quest_path: PathBuf) -> Option { - let dat_file = File::open(quest_path.join(dat_path.clone())) - .map_err(|err| { - warn!("could not load quest file {:?}: {:?}", dat_path, err) - }).ok()?; - let bin_file = File::open(quest_path.join(bin_path.clone())) - .map_err(|err| { - warn!("could not load quest file {:?}: {:?}", bin_path, err) - }).ok()?; - 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).ok()?; - bin_prs.read_to_end(&mut bin).ok()?; - - let quest = Quest::from_bin_dat(bin, dat).map_err(|err| { - warn!("could not parse quest file {:?}/{:?}: {:?}", bin_path, dat_path, err) - }).ok()?; - Some(quest) -} - - -pub fn load_quests_path(mut quest_path: PathBuf) -> Result { - let mut f = File::open(quest_path.clone()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?; - let mut s = String::new(); - f.read_to_string(&mut s)?; - quest_path.pop(); // remove quests.toml from the path - let mut used_quest_ids = BTreeSet::new(); - let ql: BTreeMap = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?; - - Ok(ql.into_iter().map(|(category, category_details)| { - ( - QuestCategory { - index: category_details.list_order, - name: category, - description: category_details.description, - }, - category_details.quests - .into_iter() - .filter_map(|quest| { - load_quest(quest.bin.into(), quest.dat.into(), quest_path.to_path_buf()) - .and_then(|quest | { - if used_quest_ids.contains(&quest.id) { - warn!("quest id already exists: {}", quest.id); - return None; - } - used_quest_ids.insert(quest.id); - Some(quest) - }) - }) - .collect() - ) - }).collect()) -} - -pub fn load_standard_quests(mode: RoomMode) -> Result { - match mode { - RoomMode::Single {episode, .. } => { - load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "single", "quests.toml"])) - }, - RoomMode::Multi {episode, .. } => { - load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "multi", "quests.toml"])) - }, - _ => { - Ok(BTreeMap::new()) - } - } -} - - -pub fn load_government_quests(mode: RoomMode) -> Result { - match mode { - RoomMode::Single {episode, .. } => { - load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"])) - }, - RoomMode::Multi {episode, .. } => { - load_quests_path(PathBuf::from_iter(["data", "quests", "bb", &episode.to_string(), "government", "quests.toml"])) - }, - _ => { - Ok(BTreeMap::new()) - } - } -} - - - - - - -#[cfg(test)] -mod tests { - use super::*; - - // the quest phantasmal world 4 uses the tower map twice, to do this it had to remap - // one of the other maps to be a second tower - #[test] - fn test_quest_with_remapped_floors() { - let pw4 = load_quest("q236-ext-bb.bin".into(), "q236-ext-bb.dat".into(), "data/quests/bb/ep2/multi".into()).unwrap(); - let enemies_not_in_tower = pw4.enemies.iter() - .filter(|enemy| { - enemy.is_some() - }) - .filter(|enemy| { - enemy.unwrap().map_area != MapArea::Tower - }); - assert!(enemies_not_in_tower.count() == 0); - - } -} diff --git a/room/src/lib.rs b/room/src/lib.rs index 4f8d063..5110d22 100644 --- a/room/src/lib.rs +++ b/room/src/lib.rs @@ -1,3 +1,272 @@ -pub mod room; +use std::collections::HashMap; +use std::convert::{From, Into}; +use async_std::sync::{Arc, RwLock, RwLockReadGuard}; +use futures::future::BoxFuture; +use futures::stream::{FuturesOrdered, Stream}; -pub use room::*; +use thiserror::Error; +use rand::Rng; + +use maps::maps::Maps; +use drops::DropTable; +use entity::character::SectionID; +use entity::room::{RoomEntityId, RoomEntityMode}; +use maps::monster::{load_monster_stats_table, MonsterType, MonsterStats}; +use maps::area::MapAreaLookup; +use quests; +use maps::Holiday; +use location::{MAX_ROOMS, RoomId}; + +use maps::room::{Episode, Difficulty, RoomMode}; + +#[derive(Error, Debug)] +pub enum RoomError { + #[error("invalid room id {0}")] + Invalid(u32), +} + + +#[derive(Clone)] +pub struct Rooms([Arc>>; MAX_ROOMS]); + + +impl Default for Rooms { + fn default() -> Rooms { + Rooms(core::array::from_fn(|_| Arc::new(RwLock::new(None)))) + } +} + +impl Rooms { + pub async fn add(&self, room_id: RoomId, room: RoomState) -> Result<(), anyhow::Error> { + *self.0 + .get(room_id.0) + .ok_or(RoomError::Invalid(room_id.0 as u32))? + .write() + .await = Some(room); + Ok(()) + } + + pub async fn remove(&self, room_id: RoomId) { + if let Some(room) = self.0.get(room_id.0) { + *room + .write() + .await = None; + } + } + + pub async fn exists(&self, room_id: RoomId) -> bool { + match self.0.get(room_id.0) { + Some(room) => { + room + .read() + .await + .is_some() + }, + None => false, + } + } + + pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result + where + T: Send, + F: for<'b> FnOnce(&'b RoomState) -> BoxFuture<'b, T> + Send + 'a + { + let room = self.0 + .get(room_id.0) + .ok_or(RoomError::Invalid(room_id.0 as u32))? + .read() + .await; + if let Some(room) = room.as_ref() { + Ok(func(room).await) + } + else { + Err(RoomError::Invalid(room_id.0 as u32).into()) + } + } + + pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result + where + T: Send, + F: for<'b> FnOnce(&'b mut RoomState) -> BoxFuture<'b, T> + Send + 'a + { + let mut room = self.0 + .get(room_id.0) + .ok_or(RoomError::Invalid(room_id.0 as u32))? + .write() + .await; + + if let Some(room) = room.as_mut() { + Ok(func(room).await) + } + else { + Err(RoomError::Invalid(room_id.0 as u32).into()) + } + } + + pub async fn get(&self, room_id: RoomId) -> RwLockReadGuard> { + self.0 + .get(room_id.0) + .unwrap() + .read() + .await + } + + pub fn stream(&self) -> impl Stream>> { + self.0 + .iter() + .map(|room| async move { + room + .read() + .await + }) + .collect::>() + } +} + +#[derive(Debug, Error)] +#[error("")] +pub enum RoomCreationError { + InvalidMode, + InvalidEpisode(u8), + InvalidDifficulty(u8), + CouldNotLoadMonsterStats(RoomMode), + CouldNotLoadQuests, +} + + +pub enum QuestCategoryType { + Standard, + Government, +} + +impl From for QuestCategoryType { + fn from(f: usize) -> QuestCategoryType { + match f { + 0 => QuestCategoryType::Standard, + _ => QuestCategoryType::Government, + } + } +} +impl From for QuestCategoryType { + fn from(f: u32) -> QuestCategoryType { + match f { + 0 => QuestCategoryType::Standard, + _ => QuestCategoryType::Government, + } + } +} + +impl QuestCategoryType { + pub fn value(&self) -> usize { + match self { + QuestCategoryType::Standard => 0, + QuestCategoryType::Government => 1, + } + } +} + +pub struct RoomState { + pub room_id: RoomEntityId, + pub mode: RoomMode, + pub name: String, + pub password: [u16; 16], + pub maps: Maps, + pub drop_table: Box, + pub section_id: SectionID, + pub random_seed: u32, + pub bursting: bool, + pub monster_stats: Box>, + pub map_areas: MapAreaLookup, + pub quest_group: QuestCategoryType, + pub standard_quests: quests::QuestList, + pub government_quests: quests::QuestList, + // enemy info +} + +impl RoomState { + pub fn get_flags_for_room_list(&self) -> u8 { + let mut flags = 0u8; + + match self.mode { + RoomMode::Single {..} => {flags += 0x04} + RoomMode::Battle {..} => {flags += 0x10}, + RoomMode::Challenge {..} => {flags += 0x20}, + _ => {flags += 0x40}, + }; + + if self.password[0] > 0 { + flags += 0x02; + } + flags + } + + pub fn get_episode_for_room_list(&self) -> u8 { + let episode: u8 = self.mode.episode().into(); + + match self.mode { + RoomMode::Single {..} => episode + 0x10, + _ => episode + 0x40, + } + } + + pub fn get_difficulty_for_room_list(&self) -> u8 { + let difficulty: u8 = self.mode.difficulty().into(); + difficulty + 0x22 + } + + pub fn quests(&self) -> &quests::QuestList { + match self.quest_group { + QuestCategoryType::Standard => &self.standard_quests, + QuestCategoryType::Government => &self.government_quests, + } + } + + #[allow(clippy::too_many_arguments, clippy::type_complexity)] + pub fn new (room_id: RoomEntityId, + mode: RoomEntityMode, + episode: Episode, + difficulty: Difficulty, + section_id: SectionID, + name: String, + password: [u16; 16], + event: Holiday, + map_builder: Arc Maps + Send + Sync>>, + drop_table_builder: Arc DropTable + Send + Sync>>, + ) -> Result { + let mode = match mode { + RoomEntityMode::Single => RoomMode::Single { + episode, + difficulty, + }, + RoomEntityMode::Multi => RoomMode::Multi { + episode, + difficulty, + }, + RoomEntityMode::Challenge => RoomMode::Challenge { + episode, + }, + RoomEntityMode::Battle => RoomMode::Battle { + episode, + difficulty, + }, + }; + + Ok(RoomState { + room_id, + monster_stats: Box::new(load_monster_stats_table(&mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(mode))?), + mode, + random_seed: rand::thread_rng().gen(), + name, + password, + maps: map_builder(mode, event), + section_id, + drop_table: Box::new(drop_table_builder(episode, difficulty, section_id)), + bursting: false, + map_areas: MapAreaLookup::new(&episode), + quest_group: QuestCategoryType::Standard, + standard_quests: quests::load_standard_quests(mode)?, + government_quests: quests::load_government_quests(mode)?, + }) + + } +} diff --git a/room/src/room.rs b/room/src/room.rs deleted file mode 100644 index 5110d22..0000000 --- a/room/src/room.rs +++ /dev/null @@ -1,272 +0,0 @@ -use std::collections::HashMap; -use std::convert::{From, Into}; -use async_std::sync::{Arc, RwLock, RwLockReadGuard}; -use futures::future::BoxFuture; -use futures::stream::{FuturesOrdered, Stream}; - -use thiserror::Error; -use rand::Rng; - -use maps::maps::Maps; -use drops::DropTable; -use entity::character::SectionID; -use entity::room::{RoomEntityId, RoomEntityMode}; -use maps::monster::{load_monster_stats_table, MonsterType, MonsterStats}; -use maps::area::MapAreaLookup; -use quests; -use maps::Holiday; -use location::{MAX_ROOMS, RoomId}; - -use maps::room::{Episode, Difficulty, RoomMode}; - -#[derive(Error, Debug)] -pub enum RoomError { - #[error("invalid room id {0}")] - Invalid(u32), -} - - -#[derive(Clone)] -pub struct Rooms([Arc>>; MAX_ROOMS]); - - -impl Default for Rooms { - fn default() -> Rooms { - Rooms(core::array::from_fn(|_| Arc::new(RwLock::new(None)))) - } -} - -impl Rooms { - pub async fn add(&self, room_id: RoomId, room: RoomState) -> Result<(), anyhow::Error> { - *self.0 - .get(room_id.0) - .ok_or(RoomError::Invalid(room_id.0 as u32))? - .write() - .await = Some(room); - Ok(()) - } - - pub async fn remove(&self, room_id: RoomId) { - if let Some(room) = self.0.get(room_id.0) { - *room - .write() - .await = None; - } - } - - pub async fn exists(&self, room_id: RoomId) -> bool { - match self.0.get(room_id.0) { - Some(room) => { - room - .read() - .await - .is_some() - }, - None => false, - } - } - - pub async fn with<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result - where - T: Send, - F: for<'b> FnOnce(&'b RoomState) -> BoxFuture<'b, T> + Send + 'a - { - let room = self.0 - .get(room_id.0) - .ok_or(RoomError::Invalid(room_id.0 as u32))? - .read() - .await; - if let Some(room) = room.as_ref() { - Ok(func(room).await) - } - else { - Err(RoomError::Invalid(room_id.0 as u32).into()) - } - } - - pub async fn with_mut<'a, T, F>(&'a self, room_id: RoomId, func: F) -> Result - where - T: Send, - F: for<'b> FnOnce(&'b mut RoomState) -> BoxFuture<'b, T> + Send + 'a - { - let mut room = self.0 - .get(room_id.0) - .ok_or(RoomError::Invalid(room_id.0 as u32))? - .write() - .await; - - if let Some(room) = room.as_mut() { - Ok(func(room).await) - } - else { - Err(RoomError::Invalid(room_id.0 as u32).into()) - } - } - - pub async fn get(&self, room_id: RoomId) -> RwLockReadGuard> { - self.0 - .get(room_id.0) - .unwrap() - .read() - .await - } - - pub fn stream(&self) -> impl Stream>> { - self.0 - .iter() - .map(|room| async move { - room - .read() - .await - }) - .collect::>() - } -} - -#[derive(Debug, Error)] -#[error("")] -pub enum RoomCreationError { - InvalidMode, - InvalidEpisode(u8), - InvalidDifficulty(u8), - CouldNotLoadMonsterStats(RoomMode), - CouldNotLoadQuests, -} - - -pub enum QuestCategoryType { - Standard, - Government, -} - -impl From for QuestCategoryType { - fn from(f: usize) -> QuestCategoryType { - match f { - 0 => QuestCategoryType::Standard, - _ => QuestCategoryType::Government, - } - } -} -impl From for QuestCategoryType { - fn from(f: u32) -> QuestCategoryType { - match f { - 0 => QuestCategoryType::Standard, - _ => QuestCategoryType::Government, - } - } -} - -impl QuestCategoryType { - pub fn value(&self) -> usize { - match self { - QuestCategoryType::Standard => 0, - QuestCategoryType::Government => 1, - } - } -} - -pub struct RoomState { - pub room_id: RoomEntityId, - pub mode: RoomMode, - pub name: String, - pub password: [u16; 16], - pub maps: Maps, - pub drop_table: Box, - pub section_id: SectionID, - pub random_seed: u32, - pub bursting: bool, - pub monster_stats: Box>, - pub map_areas: MapAreaLookup, - pub quest_group: QuestCategoryType, - pub standard_quests: quests::QuestList, - pub government_quests: quests::QuestList, - // enemy info -} - -impl RoomState { - pub fn get_flags_for_room_list(&self) -> u8 { - let mut flags = 0u8; - - match self.mode { - RoomMode::Single {..} => {flags += 0x04} - RoomMode::Battle {..} => {flags += 0x10}, - RoomMode::Challenge {..} => {flags += 0x20}, - _ => {flags += 0x40}, - }; - - if self.password[0] > 0 { - flags += 0x02; - } - flags - } - - pub fn get_episode_for_room_list(&self) -> u8 { - let episode: u8 = self.mode.episode().into(); - - match self.mode { - RoomMode::Single {..} => episode + 0x10, - _ => episode + 0x40, - } - } - - pub fn get_difficulty_for_room_list(&self) -> u8 { - let difficulty: u8 = self.mode.difficulty().into(); - difficulty + 0x22 - } - - pub fn quests(&self) -> &quests::QuestList { - match self.quest_group { - QuestCategoryType::Standard => &self.standard_quests, - QuestCategoryType::Government => &self.government_quests, - } - } - - #[allow(clippy::too_many_arguments, clippy::type_complexity)] - pub fn new (room_id: RoomEntityId, - mode: RoomEntityMode, - episode: Episode, - difficulty: Difficulty, - section_id: SectionID, - name: String, - password: [u16; 16], - event: Holiday, - map_builder: Arc Maps + Send + Sync>>, - drop_table_builder: Arc DropTable + Send + Sync>>, - ) -> Result { - let mode = match mode { - RoomEntityMode::Single => RoomMode::Single { - episode, - difficulty, - }, - RoomEntityMode::Multi => RoomMode::Multi { - episode, - difficulty, - }, - RoomEntityMode::Challenge => RoomMode::Challenge { - episode, - }, - RoomEntityMode::Battle => RoomMode::Battle { - episode, - difficulty, - }, - }; - - Ok(RoomState { - room_id, - monster_stats: Box::new(load_monster_stats_table(&mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(mode))?), - mode, - random_seed: rand::thread_rng().gen(), - name, - password, - maps: map_builder(mode, event), - section_id, - drop_table: Box::new(drop_table_builder(episode, difficulty, section_id)), - bursting: false, - map_areas: MapAreaLookup::new(&episode), - quest_group: QuestCategoryType::Standard, - standard_quests: quests::load_standard_quests(mode)?, - government_quests: quests::load_government_quests(mode)?, - }) - - } -} diff --git a/trade/src/lib.rs b/trade/src/lib.rs index 2629bda..a6a7b18 100644 --- a/trade/src/lib.rs +++ b/trade/src/lib.rs @@ -1,4 +1,132 @@ -pub mod trade; +use std::collections::HashMap; +use networking::serverstate::ClientId; +use items; +use async_std::sync::{Arc, Mutex, MutexGuard}; +use futures::future::{Future, OptionFuture}; +use items::trade::TradeItem; +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum TradeStatus { + SentRequest, + ReceivedRequest, + Trading, + Confirmed, + FinalConfirm, + ItemsChecked, + TradeComplete, +} -pub use trade::*; + +#[derive(Debug, Clone)] +pub struct ClientTradeState { + client: ClientId, + other_client: ClientId, + pub items: Vec, + pub meseta: usize, + pub status: TradeStatus, +} + + +impl ClientTradeState { + pub fn client(&self) -> ClientId { + self.client + } + + pub fn other_client(&self) -> ClientId { + self.other_client + } +} + +#[derive(thiserror::Error, Debug)] +pub enum TradeStateError { + #[error("client not in trade {0}")] + ClientNotInTrade(ClientId), + #[error("mismatched trade {0} {1}")] + MismatchedTrade(ClientId, ClientId), +} + +#[derive(Default, Debug, Clone)] +pub struct TradeState { + trades: HashMap>>, +} + +impl TradeState { + pub fn new_trade(&mut self, sender: &ClientId, receiver: &ClientId) { + let state = ClientTradeState { + client: *sender, + other_client: *receiver, + items: Default::default(), + meseta: 0, + status: TradeStatus::SentRequest, + }; + self.trades.insert(*sender, Arc::new(Mutex::new(state))); + + let state = ClientTradeState { + client: *receiver, + other_client: *sender, + items: Default::default(), + meseta: 0, + status: TradeStatus::ReceivedRequest, + }; + self.trades.insert(*receiver, Arc::new(Mutex::new(state))); + } + + pub fn in_trade(&self, client: &ClientId) -> bool { + self.trades.contains_key(client) + } + + /* + pub async fn with<'a, T, F, Fut> (&'a self, client: &ClientId, func: F) -> Result + where + F: FnOnce(MutexGuard<'a, ClientTradeState>, MutexGuard<'a, ClientTradeState>) -> Fut + 'a, + Fut: Future + { + let c1 = self.trades.get(client).ok_or(TradeStateError::ClientNotInTrade(*client))?.lock().await; + let c2 = self.trades.get(&c1.other_client).ok_or(TradeStateError::ClientNotInTrade(c1.other_client))?.lock().await; + + // sanity check + if c1.client != c2.other_client { + return Err(TradeStateError::MismatchedTrade(c1.client, c2.client)); + } + Ok(func(c1, c2).await) + } + */ + pub async fn with<'a, T, F, Fut> (&'a self, client: &ClientId, func: F) -> Result + where + T: Send, + //F: for<'b> FnOnce(&'b mut ClientTradeState, &'b mut ClientTradeState) -> BoxFuture<'b, T> + Send + 'a + //F: for<'b> FnOnce(&'b mut ClientTradeState, &'b mut ClientTradeState) -> Fut + Send + 'a, + F: FnOnce(MutexGuard<'a, ClientTradeState>, MutexGuard<'a, ClientTradeState>) -> Fut + 'a, + Fut: Future, + { + let c1 = self.trades + .get(client) + .ok_or(TradeStateError::ClientNotInTrade(*client))? + .lock() + .await; + let c2 = self.trades + .get(&c1.other_client) + .ok_or(TradeStateError::ClientNotInTrade(c1.other_client))? + .lock() + .await; + + if c1.client != c2.other_client { + return Err(TradeStateError::MismatchedTrade(c1.client, c2.client)); + } + + Ok(func(c1, c2).await) + } + + // TODO: is it possible for this to not return Options? + pub async fn remove_trade(&mut self, client: &ClientId) -> (Option, Option) { + let c1 = OptionFuture::from(self.trades.remove(client).map(|c| async move {c.lock().await.clone()} )).await; + let c2 = if let Some(ref state) = c1 { + OptionFuture::from(self.trades.remove(&state.other_client).map(|c| async move {c.lock().await.clone()})).await + } + else { + None + }; + + (c1, c2) + } +} diff --git a/trade/src/trade.rs b/trade/src/trade.rs deleted file mode 100644 index a6a7b18..0000000 --- a/trade/src/trade.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::collections::HashMap; -use networking::serverstate::ClientId; -use items; -use async_std::sync::{Arc, Mutex, MutexGuard}; -use futures::future::{Future, OptionFuture}; -use items::trade::TradeItem; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum TradeStatus { - SentRequest, - ReceivedRequest, - Trading, - Confirmed, - FinalConfirm, - ItemsChecked, - TradeComplete, -} - - -#[derive(Debug, Clone)] -pub struct ClientTradeState { - client: ClientId, - other_client: ClientId, - pub items: Vec, - pub meseta: usize, - pub status: TradeStatus, -} - - -impl ClientTradeState { - pub fn client(&self) -> ClientId { - self.client - } - - pub fn other_client(&self) -> ClientId { - self.other_client - } -} - -#[derive(thiserror::Error, Debug)] -pub enum TradeStateError { - #[error("client not in trade {0}")] - ClientNotInTrade(ClientId), - #[error("mismatched trade {0} {1}")] - MismatchedTrade(ClientId, ClientId), -} - -#[derive(Default, Debug, Clone)] -pub struct TradeState { - trades: HashMap>>, -} - -impl TradeState { - pub fn new_trade(&mut self, sender: &ClientId, receiver: &ClientId) { - let state = ClientTradeState { - client: *sender, - other_client: *receiver, - items: Default::default(), - meseta: 0, - status: TradeStatus::SentRequest, - }; - self.trades.insert(*sender, Arc::new(Mutex::new(state))); - - let state = ClientTradeState { - client: *receiver, - other_client: *sender, - items: Default::default(), - meseta: 0, - status: TradeStatus::ReceivedRequest, - }; - self.trades.insert(*receiver, Arc::new(Mutex::new(state))); - } - - pub fn in_trade(&self, client: &ClientId) -> bool { - self.trades.contains_key(client) - } - - /* - pub async fn with<'a, T, F, Fut> (&'a self, client: &ClientId, func: F) -> Result - where - F: FnOnce(MutexGuard<'a, ClientTradeState>, MutexGuard<'a, ClientTradeState>) -> Fut + 'a, - Fut: Future - { - let c1 = self.trades.get(client).ok_or(TradeStateError::ClientNotInTrade(*client))?.lock().await; - let c2 = self.trades.get(&c1.other_client).ok_or(TradeStateError::ClientNotInTrade(c1.other_client))?.lock().await; - - // sanity check - if c1.client != c2.other_client { - return Err(TradeStateError::MismatchedTrade(c1.client, c2.client)); - } - Ok(func(c1, c2).await) - } - */ - pub async fn with<'a, T, F, Fut> (&'a self, client: &ClientId, func: F) -> Result - where - T: Send, - //F: for<'b> FnOnce(&'b mut ClientTradeState, &'b mut ClientTradeState) -> BoxFuture<'b, T> + Send + 'a - //F: for<'b> FnOnce(&'b mut ClientTradeState, &'b mut ClientTradeState) -> Fut + Send + 'a, - F: FnOnce(MutexGuard<'a, ClientTradeState>, MutexGuard<'a, ClientTradeState>) -> Fut + 'a, - Fut: Future, - { - let c1 = self.trades - .get(client) - .ok_or(TradeStateError::ClientNotInTrade(*client))? - .lock() - .await; - let c2 = self.trades - .get(&c1.other_client) - .ok_or(TradeStateError::ClientNotInTrade(c1.other_client))? - .lock() - .await; - - if c1.client != c2.other_client { - return Err(TradeStateError::MismatchedTrade(c1.client, c2.client)); - } - - Ok(func(c1, c2).await) - } - - // TODO: is it possible for this to not return Options? - pub async fn remove_trade(&mut self, client: &ClientId) -> (Option, Option) { - let c1 = OptionFuture::from(self.trades.remove(client).map(|c| async move {c.lock().await.clone()} )).await; - let c2 = if let Some(ref state) = c1 { - OptionFuture::from(self.trades.remove(&state.other_client).map(|c| async move {c.lock().await.clone()})).await - } - else { - None - }; - - (c1, c2) - } -}