diff --git a/src/entity/character.rs b/src/entity/character.rs index 7a2431d..bb5a6d0 100644 --- a/src/entity/character.rs +++ b/src/entity/character.rs @@ -413,4 +413,6 @@ pub struct CharacterEntity { pub option_flags: u32, pub keyboard_config: CharacterKeyboardConfig, pub gamepad_config: CharacterGamepadConfig, + + pub playtime: u32, } diff --git a/src/entity/gateway/entitygateway.rs b/src/entity/gateway/entitygateway.rs index 53b6fc4..2a8b65b 100644 --- a/src/entity/gateway/entitygateway.rs +++ b/src/entity/gateway/entitygateway.rs @@ -147,6 +147,10 @@ pub trait EntityGateway: Send + Sync { async fn create_trade(&mut self, _char_id1: &CharacterEntityId, _char_id2: &CharacterEntityId) -> Result { unimplemented!(); } + + async fn set_character_playtime(&mut self, _char_id: &CharacterEntityId, _playtime: u32) -> Result<(), GatewayError> { + unimplemented!(); + } } diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index f8b3c96..965b478 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -346,6 +346,7 @@ impl EntityGateway for InMemoryGateway { option_flags: character.option_flags, keyboard_config: character.keyboard_config, gamepad_config: character.gamepad_config, + playtime: 0, }; characters.insert(new_character.id, new_character.clone()); Ok(new_character) @@ -503,4 +504,15 @@ impl EntityGateway for InMemoryGateway { trades.push(new_trade.clone()); Ok(new_trade) } + + async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> { + let mut characters = self.characters.lock().unwrap(); + if let Some(character) = characters.get_mut(char_id) { + character.playtime = playtime; + Ok(()) + } + else { + Err(GatewayError::Error) + } + } } diff --git a/src/entity/gateway/postgres/migrations/V0006__playtime.sql b/src/entity/gateway/postgres/migrations/V0006__playtime.sql new file mode 100644 index 0000000..9d7e0d7 --- /dev/null +++ b/src/entity/gateway/postgres/migrations/V0006__playtime.sql @@ -0,0 +1,2 @@ +alter table player_character + add playtime integer; diff --git a/src/entity/gateway/postgres/models.rs b/src/entity/gateway/postgres/models.rs index c1257ee..cad8959 100644 --- a/src/entity/gateway/postgres/models.rs +++ b/src/entity/gateway/postgres/models.rs @@ -219,6 +219,8 @@ pub struct PgCharacter { tech_menu: Vec, keyboard_config: Vec, gamepad_config: Vec, + + playtime: i32, } impl From for CharacterEntity { @@ -274,6 +276,7 @@ impl From for CharacterEntity { gamepad_config: CharacterGamepadConfig { gamepad_config: vec_to_array(other.gamepad_config) }, + playtime: other.playtime as u32, } } } diff --git a/src/entity/gateway/postgres/postgres.rs b/src/entity/gateway/postgres/postgres.rs index 8987ab1..a8ec6fe 100644 --- a/src/entity/gateway/postgres/postgres.rs +++ b/src/entity/gateway/postgres/postgres.rs @@ -269,39 +269,40 @@ async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) - let q = r#"update player_character set user_account=$1, slot=$2, name=$3, exp=$4, class=$5, section_id=$6, costume=$7, skin=$8, face=$9, head=$10, hair=$11, hair_r=$12, hair_g=$13, hair_b=$14, prop_x=$15, prop_y=$16, techs=$17, config=$18, infoboard=$19, guildcard=$20, power=$21, mind=$22, def=$23, - evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29 - where id=$32;"#; + evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29, playtime=$30 + where id=$31;"#; sqlx::query(q) - .bind(char.user_id.0) - .bind(char.slot as i16) - .bind(&char.name) - .bind(char.exp as i32) - .bind(char.char_class.to_string()) - .bind(char.section_id.to_string()) - .bind(char.appearance.costume as i16) - .bind(char.appearance.skin as i16) - .bind(char.appearance.face as i16) - .bind(char.appearance.head as i16) - .bind(char.appearance.hair as i16) - .bind(char.appearance.hair_r as i16) - .bind(char.appearance.hair_g as i16) - .bind(char.appearance.hair_b as i16) - .bind(char.appearance.prop_x) - .bind(char.appearance.prop_y) - .bind(&char.techs.as_bytes().to_vec()) - .bind(&char.config.as_bytes().to_vec()) - .bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0))) - .bind(&char.guildcard.description) - .bind(char.materials.power as i16) - .bind(char.materials.mind as i16) - .bind(char.materials.def as i16) - .bind(char.materials.evade as i16) - .bind(char.materials.luck as i16) - .bind(char.materials.hp as i16) - .bind(char.materials.tp as i16) - .bind(char.tech_menu.tech_menu.to_vec()) - .bind(char.option_flags as i32) - .bind(char.id.0 as i32) + .bind(char.user_id.0) // $1 + .bind(char.slot as i16) // $2 + .bind(&char.name) // $3 + .bind(char.exp as i32) // $4 + .bind(char.char_class.to_string()) // $5 + .bind(char.section_id.to_string()) // $6 + .bind(char.appearance.costume as i16) // $7 + .bind(char.appearance.skin as i16) // $8 + .bind(char.appearance.face as i16) // $9 + .bind(char.appearance.head as i16) // $10 + .bind(char.appearance.hair as i16) // $11 + .bind(char.appearance.hair_r as i16) // $12 + .bind(char.appearance.hair_g as i16) // $13 + .bind(char.appearance.hair_b as i16) // $14 + .bind(char.appearance.prop_x) // $15 + .bind(char.appearance.prop_y) // $16 + .bind(&char.techs.as_bytes().to_vec()) // $17 + .bind(&char.config.as_bytes().to_vec()) // $18 + .bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0))) // $19 + .bind(&char.guildcard.description) // $20 + .bind(char.materials.power as i16) // $21 + .bind(char.materials.mind as i16) // $22 + .bind(char.materials.def as i16) // $23 + .bind(char.materials.evade as i16) // $24 + .bind(char.materials.luck as i16) // $25 + .bind(char.materials.hp as i16) // $26 + .bind(char.materials.tp as i16) // $27 + .bind(char.tech_menu.tech_menu.to_vec()) // $28 + .bind(char.option_flags as i32) // $29 + .bind(char.playtime as i32) // $20 + .bind(char.id.0 as i32) // $31 .execute(conn).await?; Ok(()) } @@ -566,6 +567,16 @@ async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityI Ok(trade.into()) } +async fn set_character_playtime(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> +{ + sqlx::query_as::<_, PgTradeEntity>(r#"update player_character set playtime=$2 where id=$1;"#) + .bind(char_id.0) + .bind(playtime) + .fetch_one(conn) + .await?; + Ok(()) +} + #[async_trait::async_trait] impl EntityGateway for PostgresGateway { async fn transaction<'a>(&'a mut self) -> Result, GatewayError> @@ -705,6 +716,10 @@ impl EntityGateway for PostgresGateway { async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result { create_trade(&mut *self.pool.acquire().await?, char_id1, char_id2).await } + + async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> { + set_character_playtime(&mut *self.pool.acquire().await?, char_id, playtime).await + } } @@ -825,5 +840,9 @@ impl<'c> EntityGateway for PostgresTransaction<'c> { async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result { create_trade(&mut *self.pgtransaction, char_id1, char_id2).await } + + async fn set_character_playtime(&mut self, char_id: &CharacterEntityId, playtime: u32) -> Result<(), GatewayError> { + set_character_playtime(&mut *self.pgtransaction, char_id, playtime).await + } } diff --git a/src/login/character.rs b/src/login/character.rs index ed2045a..63bd8bc 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -790,7 +790,7 @@ impl<'a> SelectScreenCharacterBuilder<'a> { prop_x: character.appearance.prop_x, prop_y: character.appearance.prop_y, name: utf8_to_utf16_array!(character.name, 16), - //play_time: character.play_time, + play_time: character.playtime, ..character::SelectScreenCharacter::default() } } diff --git a/src/ship/ship.rs b/src/ship/ship.rs index c1ec3d8..aefbc74 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -290,10 +290,6 @@ pub struct LoadingQuest { - - - - pub struct ClientState { pub user: UserAccountEntity, pub settings: UserSettingsEntity, @@ -312,10 +308,13 @@ pub struct ClientState { 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, @@ -332,10 +331,18 @@ impl ClientState { tool_shop: Vec::new(), armor_shop: Vec::new(), tek: None, + character_playtime, + log_on_time: chrono::Utc::now(), } } + + 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 struct ItemShops { pub weapon_shop: HashMap<(room::Difficulty, SectionID), WeaponShop>, pub tool_shop: ToolShop, @@ -631,6 +638,11 @@ impl ServerState for ShipServerState { async fn handle(&mut self, id: ClientId, pkt: &RecvShipPacket) -> Result + Send>, anyhow::Error> { + if let Some(client) = self.clients.get_mut(&id) { + client.update_playtime(); + self.entity_gateway.set_character_playtime(&client.character.id, client.character.playtime).await?; + } + Ok(match pkt { RecvShipPacket::Login(login) => { Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_state, &self.shipgate_sender, &self.name, self.blocks.0.len())