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 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; } }