From b56919b123ec1c3774e99958438cdfa22c141174 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Tue, 3 Nov 2020 01:12:49 +0100 Subject: [PATCH] Make waypoints persistent Closes #549 --- CHANGELOG.md | 1 + common/src/event.rs | 8 +++- server/src/character_creator.rs | 3 +- server/src/events/entity_creation.rs | 8 +++- server/src/events/player.rs | 4 +- server/src/persistence/character.rs | 43 +++++++++++++++++-- .../src/persistence/character/conversions.rs | 12 +++++- server/src/persistence/character_updater.rs | 31 ++++++++++--- server/src/persistence/json_models.rs | 6 +++ server/src/persistence/mod.rs | 8 +++- server/src/state_ext.rs | 10 ++++- server/src/sys/persistence.rs | 11 +++-- 12 files changed, 125 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d58d790b2c..0da798c4f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Sneaking lets you be closer to enemies without being detected - Flight - Roll dodges melee attacks, and reduces the height of your hitbox +- Persistent waypoints (start from the last camp fire you visited) ### Changed diff --git a/common/src/event.rs b/common/src/event.rs index 4275aeb519..fee8baf6a0 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -84,7 +84,13 @@ pub enum ServerEvent { }, UpdateCharacterData { entity: EcsEntity, - components: (comp::Body, comp::Stats, comp::Inventory, comp::Loadout), + components: ( + comp::Body, + comp::Stats, + comp::Inventory, + comp::Loadout, + Option, + ), }, ExitIngame { entity: EcsEntity, diff --git a/server/src/character_creator.rs b/server/src/character_creator.rs index c8fcd35abe..ed285d047a 100644 --- a/server/src/character_creator.rs +++ b/server/src/character_creator.rs @@ -23,11 +23,12 @@ pub fn create_character( .build(); let inventory = Inventory::default(); + let waypoint = None; character_loader.create_character( entity, player_uuid, character_alias, - (body, stats, inventory, loadout), + (body, stats, inventory, loadout, waypoint), ); } diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 5126406c04..49a4a8e872 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -24,7 +24,13 @@ pub fn handle_initialize_character( pub fn handle_loaded_character_data( server: &mut Server, entity: EcsEntity, - loaded_components: (comp::Body, comp::Stats, comp::Inventory, comp::Loadout), + loaded_components: ( + comp::Body, + comp::Stats, + comp::Inventory, + comp::Loadout, + Option, + ), ) { server .state diff --git a/server/src/events/player.rs b/server/src/events/player.rs index e522fe28f0..ef75406261 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -156,7 +156,9 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event .read_resource::(), ) { if let PresenceKind::Character(character_id) = presences.kind { - updater.update(character_id, stats, inventory, loadout); + let waypoint_read = state.read_storage::(); + let waypoint = waypoint_read.get(entity); + updater.update(character_id, stats, inventory, loadout, waypoint); } } diff --git a/server/src/persistence/character.rs b/server/src/persistence/character.rs index 774b9e1b8e..66f29d4a03 100644 --- a/server/src/persistence/character.rs +++ b/server/src/persistence/character.rs @@ -15,13 +15,18 @@ use crate::{ convert_character_from_database, convert_inventory_from_database_items, convert_items_to_database_items, convert_loadout_from_database_items, convert_stats_from_database, convert_stats_to_database, + convert_waypoint_to_database_json, }, character_loader::{CharacterDataResult, CharacterListResult}, error::Error::DatabaseError, + json_models::CharacterPosition, PersistedComponents, }, }; -use common::character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER}; +use common::{ + character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER}, + state::Time, +}; use core::ops::Range; use diesel::{prelude::*, sql_query, sql_types::BigInt}; use std::sync::Arc; @@ -83,11 +88,22 @@ pub fn load_character_data( .filter(schema::body::dsl::body_id.eq(char_id)) .first::(&*connection)?; + let waypoint = item + .filter(item_id.eq(char_id)) + .first::(&*connection) + .ok() + .and_then(|it: Item| { + (serde_json::de::from_str::(it.position.as_str())) + .ok() + .map(|charpos| comp::Waypoint::new(charpos.waypoint, Time(0.0))) + }); + Ok(( convert_body_from_database(&char_body)?, convert_stats_from_database(&stats_data, character_data.alias), convert_inventory_from_database_items(&inventory_items)?, convert_loadout_from_database_items(&loadout_items)?, + waypoint, )) } @@ -157,7 +173,7 @@ pub fn create_character( use schema::{body, character, stats}; - let (body, stats, inventory, loadout) = persisted_components; + let (body, stats, inventory, loadout, waypoint) = persisted_components; // Fetch new entity IDs for character, inventory and loadout let mut new_entity_ids = get_new_entity_ids(connection, |next_id| next_id + 3)?; @@ -166,13 +182,17 @@ pub fn create_character( let character_id = new_entity_ids.next().unwrap(); let inventory_container_id = new_entity_ids.next().unwrap(); let loadout_container_id = new_entity_ids.next().unwrap(); + // by default the character's position is the id in textual form + let character_position = waypoint + .and_then(|waypoint| serde_json::to_string(&waypoint.get_pos()).ok()) + .unwrap_or_else(|| character_id.to_string()); let pseudo_containers = vec![ Item { stack_size: 1, item_id: character_id, parent_container_item_id: WORLD_PSEUDO_CONTAINER_ID, item_definition_id: CHARACTER_PSEUDO_CONTAINER_DEF_ID.to_owned(), - position: character_id.to_string(), + position: character_position, }, Item { stack_size: 1, @@ -510,6 +530,7 @@ pub fn update( char_stats: comp::Stats, inventory: comp::Inventory, loadout: comp::Loadout, + waypoint: Option, connection: VelorenTransaction, ) -> Result>, Error> { use super::schema::{item::dsl::*, stats::dsl::*}; @@ -532,6 +553,22 @@ pub fn update( next_id })?; + if let Some(waypoint) = waypoint { + match convert_waypoint_to_database_json(&waypoint) { + Ok(character_position) => { + diesel::update(item.filter(item_id.eq(char_id))) + .set(position.eq(character_position)) + .execute(&*connection)?; + }, + Err(err) => { + return Err(Error::ConversionError(format!( + "Error encoding waypoint: {:?}", + err + ))); + }, + } + } + // Next, delete any slots we aren't upserting. trace!("Deleting items for character_id {}", char_id); let existing_items = parent_container_item_id diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index 79f2d6093c..503b80be8b 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -3,7 +3,10 @@ use crate::persistence::{ models::{Body, Character, Item, Stats}, }; -use crate::persistence::{error::Error, json_models::HumanoidBody}; +use crate::persistence::{ + error::Error, + json_models::{CharacterPosition, HumanoidBody}, +}; use common::{ character::CharacterId, comp::{Body as CompBody, *}, @@ -165,6 +168,13 @@ pub fn convert_body_to_database_json(body: &CompBody) -> Result { serde_json::to_string(&json_model).map_err(Error::SerializationError) } +pub fn convert_waypoint_to_database_json(waypoint: &Waypoint) -> Result { + let charpos = CharacterPosition { + waypoint: waypoint.get_pos(), + }; + serde_json::to_string(&charpos).map_err(Error::SerializationError) +} + pub fn convert_stats_to_database(character_id: CharacterId, stats: &common::comp::Stats) -> Stats { Stats { stats_id: character_id, diff --git a/server/src/persistence/character_updater.rs b/server/src/persistence/character_updater.rs index 493042d854..07bb46e9ee 100644 --- a/server/src/persistence/character_updater.rs +++ b/server/src/persistence/character_updater.rs @@ -6,7 +6,12 @@ use crossbeam::channel; use std::{path::Path, sync::Arc}; use tracing::{error, trace}; -pub type CharacterUpdateData = (comp::Stats, comp::Inventory, comp::Loadout); +pub type CharacterUpdateData = ( + comp::Stats, + comp::Inventory, + comp::Loadout, + Option, +); /// A unidirectional messaging resource for saving characters in a /// background thread. @@ -48,17 +53,23 @@ impl CharacterUpdater { &'a comp::Stats, &'a comp::Inventory, &'a comp::Loadout, + Option<&'a comp::Waypoint>, ), >, ) { let updates = updates - .map(|(character_id, stats, inventory, loadout)| { + .map(|(character_id, stats, inventory, loadout, waypoint)| { ( character_id, - (stats.clone(), inventory.clone(), loadout.clone()), + ( + stats.clone(), + inventory.clone(), + loadout.clone(), + waypoint.cloned(), + ), ) }) - .collect::>(); + .collect::>(); if let Err(e) = self.update_tx.as_ref().unwrap().send(updates) { error!(?e, "Could not send stats updates"); @@ -72,8 +83,15 @@ impl CharacterUpdater { stats: &comp::Stats, inventory: &comp::Inventory, loadout: &comp::Loadout, + waypoint: Option<&comp::Waypoint>, ) { - self.batch_update(std::iter::once((character_id, stats, inventory, loadout))); + self.batch_update(std::iter::once(( + character_id, + stats, + inventory, + loadout, + waypoint, + ))); } } @@ -84,12 +102,13 @@ fn execute_batch_update( let mut inserted_items = Vec::>::new(); if let Err(e) = connection.transaction::<_, super::error::Error, _>(|txn| { - for (character_id, (stats, inventory, loadout)) in updates { + for (character_id, (stats, inventory, loadout, waypoint)) in updates { inserted_items.append(&mut super::character::update( character_id, stats, inventory, loadout, + waypoint, txn, )?); } diff --git a/server/src/persistence/json_models.rs b/server/src/persistence/json_models.rs index 03f4fe4c59..83353ee296 100644 --- a/server/src/persistence/json_models.rs +++ b/server/src/persistence/json_models.rs @@ -1,5 +1,6 @@ use common::comp; use serde::{Deserialize, Serialize}; +use vek::Vec3; #[derive(Serialize, Deserialize)] pub struct HumanoidBody { @@ -29,3 +30,8 @@ impl From<&comp::humanoid::Body> for HumanoidBody { } } } + +#[derive(Serialize, Deserialize)] +pub struct CharacterPosition { + pub waypoint: Vec3, +} diff --git a/server/src/persistence/mod.rs b/server/src/persistence/mod.rs index 797b4eefb0..89feb78ace 100644 --- a/server/src/persistence/mod.rs +++ b/server/src/persistence/mod.rs @@ -21,7 +21,13 @@ use std::{fs, path::Path}; use tracing::info; /// A tuple of the components that are persisted to the DB for each character -pub type PersistedComponents = (comp::Body, comp::Stats, comp::Inventory, comp::Loadout); +pub type PersistedComponents = ( + comp::Body, + comp::Stats, + comp::Inventory, + comp::Loadout, + Option, +); // See: https://docs.rs/diesel_migrations/1.4.0/diesel_migrations/macro.embed_migrations.html // This macro is called at build-time, and produces the necessary migration info diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index ebcc7e4f02..e7c672d254 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -246,7 +246,7 @@ impl StateExt for State { } fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) { - let (body, stats, inventory, loadout) = components; + let (body, stats, inventory, loadout, waypoint) = components; if let Some(player_uid) = self.read_component_copied::(entity) { // Notify clients of a player list update @@ -270,11 +270,17 @@ impl StateExt for State { self.write_component(entity, stats); self.write_component(entity, inventory); self.write_component(entity, loadout); - self.write_component( entity, comp::InventoryUpdate::new(comp::InventoryUpdateEvent::default()), ); + + if let Some(waypoint) = waypoint { + self.write_component(entity, waypoint); + self.write_component(entity, comp::Pos(waypoint.get_pos())); + self.write_component(entity, comp::Vel(Vec3::zero())); + self.write_component(entity, comp::ForceUpdate); + } } } diff --git a/server/src/sys/persistence.rs b/server/src/sys/persistence.rs index 60c578d7d8..e2e22181cd 100644 --- a/server/src/sys/persistence.rs +++ b/server/src/sys/persistence.rs @@ -4,7 +4,7 @@ use crate::{ sys::{SysScheduler, SysTimer}, }; use common::{ - comp::{Inventory, Loadout, Stats}, + comp::{Inventory, Loadout, Stats, Waypoint}, msg::PresenceKind, span, }; @@ -19,6 +19,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Stats>, ReadStorage<'a, Inventory>, ReadStorage<'a, Loadout>, + ReadStorage<'a, Waypoint>, ReadExpect<'a, character_updater::CharacterUpdater>, Write<'a, SysScheduler>, Write<'a, SysTimer>, @@ -31,6 +32,7 @@ impl<'a> System<'a> for Sys { player_stats, player_inventories, player_loadouts, + player_waypoint, updater, mut scheduler, mut timer, @@ -45,11 +47,14 @@ impl<'a> System<'a> for Sys { &player_stats, &player_inventories, &player_loadouts, + player_waypoint.maybe(), ) .join() .filter_map( - |(presence, stats, inventory, loadout)| match presence.kind { - PresenceKind::Character(id) => Some((id, stats, inventory, loadout)), + |(presence, stats, inventory, loadout, waypoint)| match presence.kind { + PresenceKind::Character(id) => { + Some((id, stats, inventory, loadout, waypoint)) + }, PresenceKind::Spectator => None, }, ),