diff --git a/src/ship/client.rs b/src/ship/client.rs new file mode 100644 index 0000000..795c880 --- /dev/null +++ b/src/ship/client.rs @@ -0,0 +1,172 @@ +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 crate::common::serverstate::ClientId; +use crate::entity::account::{UserAccountEntity, UserSettingsEntity}; +use crate::entity::character::CharacterEntity; +use crate::entity::item; + +use crate::ship::ship::ShipError; +use crate::ship::items; +use crate::ship::map::MapArea; +use crate::ship::shops::{WeaponShopItem, ToolShopItem, ArmorShopItem}; + + +#[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_else(|| ShipError::ClientNotFound(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_else(|| ShipError::ClientNotFound(*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_else(|| ShipError::ClientNotFound(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; + } +} + +