Make waypoints persistent

Closes #549
This commit is contained in:
Christof Petig 2020-11-03 01:12:49 +01:00
parent b11da85ff9
commit b56919b123
12 changed files with 125 additions and 20 deletions

View File

@ -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 - Sneaking lets you be closer to enemies without being detected
- Flight - Flight
- Roll dodges melee attacks, and reduces the height of your hitbox - Roll dodges melee attacks, and reduces the height of your hitbox
- Persistent waypoints (start from the last camp fire you visited)
### Changed ### Changed

View File

@ -84,7 +84,13 @@ pub enum ServerEvent {
}, },
UpdateCharacterData { UpdateCharacterData {
entity: EcsEntity, entity: EcsEntity,
components: (comp::Body, comp::Stats, comp::Inventory, comp::Loadout), components: (
comp::Body,
comp::Stats,
comp::Inventory,
comp::Loadout,
Option<comp::Waypoint>,
),
}, },
ExitIngame { ExitIngame {
entity: EcsEntity, entity: EcsEntity,

View File

@ -23,11 +23,12 @@ pub fn create_character(
.build(); .build();
let inventory = Inventory::default(); let inventory = Inventory::default();
let waypoint = None;
character_loader.create_character( character_loader.create_character(
entity, entity,
player_uuid, player_uuid,
character_alias, character_alias,
(body, stats, inventory, loadout), (body, stats, inventory, loadout, waypoint),
); );
} }

View File

@ -24,7 +24,13 @@ pub fn handle_initialize_character(
pub fn handle_loaded_character_data( pub fn handle_loaded_character_data(
server: &mut Server, server: &mut Server,
entity: EcsEntity, entity: EcsEntity,
loaded_components: (comp::Body, comp::Stats, comp::Inventory, comp::Loadout), loaded_components: (
comp::Body,
comp::Stats,
comp::Inventory,
comp::Loadout,
Option<comp::Waypoint>,
),
) { ) {
server server
.state .state

View File

@ -156,7 +156,9 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event
.read_resource::<persistence::character_updater::CharacterUpdater>(), .read_resource::<persistence::character_updater::CharacterUpdater>(),
) { ) {
if let PresenceKind::Character(character_id) = presences.kind { if let PresenceKind::Character(character_id) = presences.kind {
updater.update(character_id, stats, inventory, loadout); let waypoint_read = state.read_storage::<comp::Waypoint>();
let waypoint = waypoint_read.get(entity);
updater.update(character_id, stats, inventory, loadout, waypoint);
} }
} }

View File

@ -15,13 +15,18 @@ use crate::{
convert_character_from_database, convert_inventory_from_database_items, convert_character_from_database, convert_inventory_from_database_items,
convert_items_to_database_items, convert_loadout_from_database_items, convert_items_to_database_items, convert_loadout_from_database_items,
convert_stats_from_database, convert_stats_to_database, convert_stats_from_database, convert_stats_to_database,
convert_waypoint_to_database_json,
}, },
character_loader::{CharacterDataResult, CharacterListResult}, character_loader::{CharacterDataResult, CharacterListResult},
error::Error::DatabaseError, error::Error::DatabaseError,
json_models::CharacterPosition,
PersistedComponents, 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 core::ops::Range;
use diesel::{prelude::*, sql_query, sql_types::BigInt}; use diesel::{prelude::*, sql_query, sql_types::BigInt};
use std::sync::Arc; use std::sync::Arc;
@ -83,11 +88,22 @@ pub fn load_character_data(
.filter(schema::body::dsl::body_id.eq(char_id)) .filter(schema::body::dsl::body_id.eq(char_id))
.first::<Body>(&*connection)?; .first::<Body>(&*connection)?;
let waypoint = item
.filter(item_id.eq(char_id))
.first::<Item>(&*connection)
.ok()
.and_then(|it: Item| {
(serde_json::de::from_str::<CharacterPosition>(it.position.as_str()))
.ok()
.map(|charpos| comp::Waypoint::new(charpos.waypoint, Time(0.0)))
});
Ok(( Ok((
convert_body_from_database(&char_body)?, convert_body_from_database(&char_body)?,
convert_stats_from_database(&stats_data, character_data.alias), convert_stats_from_database(&stats_data, character_data.alias),
convert_inventory_from_database_items(&inventory_items)?, convert_inventory_from_database_items(&inventory_items)?,
convert_loadout_from_database_items(&loadout_items)?, convert_loadout_from_database_items(&loadout_items)?,
waypoint,
)) ))
} }
@ -157,7 +173,7 @@ pub fn create_character(
use schema::{body, character, stats}; 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 // Fetch new entity IDs for character, inventory and loadout
let mut new_entity_ids = get_new_entity_ids(connection, |next_id| next_id + 3)?; 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 character_id = new_entity_ids.next().unwrap();
let inventory_container_id = new_entity_ids.next().unwrap(); let inventory_container_id = new_entity_ids.next().unwrap();
let loadout_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![ let pseudo_containers = vec![
Item { Item {
stack_size: 1, stack_size: 1,
item_id: character_id, item_id: character_id,
parent_container_item_id: WORLD_PSEUDO_CONTAINER_ID, parent_container_item_id: WORLD_PSEUDO_CONTAINER_ID,
item_definition_id: CHARACTER_PSEUDO_CONTAINER_DEF_ID.to_owned(), item_definition_id: CHARACTER_PSEUDO_CONTAINER_DEF_ID.to_owned(),
position: character_id.to_string(), position: character_position,
}, },
Item { Item {
stack_size: 1, stack_size: 1,
@ -510,6 +530,7 @@ pub fn update(
char_stats: comp::Stats, char_stats: comp::Stats,
inventory: comp::Inventory, inventory: comp::Inventory,
loadout: comp::Loadout, loadout: comp::Loadout,
waypoint: Option<comp::Waypoint>,
connection: VelorenTransaction, connection: VelorenTransaction,
) -> Result<Vec<Arc<common::comp::item::ItemId>>, Error> { ) -> Result<Vec<Arc<common::comp::item::ItemId>>, Error> {
use super::schema::{item::dsl::*, stats::dsl::*}; use super::schema::{item::dsl::*, stats::dsl::*};
@ -532,6 +553,22 @@ pub fn update(
next_id 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. // Next, delete any slots we aren't upserting.
trace!("Deleting items for character_id {}", char_id); trace!("Deleting items for character_id {}", char_id);
let existing_items = parent_container_item_id let existing_items = parent_container_item_id

View File

@ -3,7 +3,10 @@ use crate::persistence::{
models::{Body, Character, Item, Stats}, models::{Body, Character, Item, Stats},
}; };
use crate::persistence::{error::Error, json_models::HumanoidBody}; use crate::persistence::{
error::Error,
json_models::{CharacterPosition, HumanoidBody},
};
use common::{ use common::{
character::CharacterId, character::CharacterId,
comp::{Body as CompBody, *}, comp::{Body as CompBody, *},
@ -165,6 +168,13 @@ pub fn convert_body_to_database_json(body: &CompBody) -> Result<String, Error> {
serde_json::to_string(&json_model).map_err(Error::SerializationError) serde_json::to_string(&json_model).map_err(Error::SerializationError)
} }
pub fn convert_waypoint_to_database_json(waypoint: &Waypoint) -> Result<String, Error> {
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 { pub fn convert_stats_to_database(character_id: CharacterId, stats: &common::comp::Stats) -> Stats {
Stats { Stats {
stats_id: character_id, stats_id: character_id,

View File

@ -6,7 +6,12 @@ use crossbeam::channel;
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
use tracing::{error, trace}; use tracing::{error, trace};
pub type CharacterUpdateData = (comp::Stats, comp::Inventory, comp::Loadout); pub type CharacterUpdateData = (
comp::Stats,
comp::Inventory,
comp::Loadout,
Option<comp::Waypoint>,
);
/// A unidirectional messaging resource for saving characters in a /// A unidirectional messaging resource for saving characters in a
/// background thread. /// background thread.
@ -48,17 +53,23 @@ impl CharacterUpdater {
&'a comp::Stats, &'a comp::Stats,
&'a comp::Inventory, &'a comp::Inventory,
&'a comp::Loadout, &'a comp::Loadout,
Option<&'a comp::Waypoint>,
), ),
>, >,
) { ) {
let updates = updates let updates = updates
.map(|(character_id, stats, inventory, loadout)| { .map(|(character_id, stats, inventory, loadout, waypoint)| {
( (
character_id, character_id,
(stats.clone(), inventory.clone(), loadout.clone()), (
stats.clone(),
inventory.clone(),
loadout.clone(),
waypoint.cloned(),
),
) )
}) })
.collect::<Vec<(CharacterId, (comp::Stats, comp::Inventory, comp::Loadout))>>(); .collect::<Vec<_>>();
if let Err(e) = self.update_tx.as_ref().unwrap().send(updates) { if let Err(e) = self.update_tx.as_ref().unwrap().send(updates) {
error!(?e, "Could not send stats updates"); error!(?e, "Could not send stats updates");
@ -72,8 +83,15 @@ impl CharacterUpdater {
stats: &comp::Stats, stats: &comp::Stats,
inventory: &comp::Inventory, inventory: &comp::Inventory,
loadout: &comp::Loadout, 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::<Arc<ItemId>>::new(); let mut inserted_items = Vec::<Arc<ItemId>>::new();
if let Err(e) = connection.transaction::<_, super::error::Error, _>(|txn| { 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( inserted_items.append(&mut super::character::update(
character_id, character_id,
stats, stats,
inventory, inventory,
loadout, loadout,
waypoint,
txn, txn,
)?); )?);
} }

View File

@ -1,5 +1,6 @@
use common::comp; use common::comp;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::Vec3;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct HumanoidBody { pub struct HumanoidBody {
@ -29,3 +30,8 @@ impl From<&comp::humanoid::Body> for HumanoidBody {
} }
} }
} }
#[derive(Serialize, Deserialize)]
pub struct CharacterPosition {
pub waypoint: Vec3<f32>,
}

View File

@ -21,7 +21,13 @@ use std::{fs, path::Path};
use tracing::info; use tracing::info;
/// A tuple of the components that are persisted to the DB for each character /// 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<comp::Waypoint>,
);
// See: https://docs.rs/diesel_migrations/1.4.0/diesel_migrations/macro.embed_migrations.html // 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 // This macro is called at build-time, and produces the necessary migration info

View File

@ -246,7 +246,7 @@ impl StateExt for State {
} }
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) { 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::<Uid>(entity) { if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
// Notify clients of a player list update // Notify clients of a player list update
@ -270,11 +270,17 @@ impl StateExt for State {
self.write_component(entity, stats); self.write_component(entity, stats);
self.write_component(entity, inventory); self.write_component(entity, inventory);
self.write_component(entity, loadout); self.write_component(entity, loadout);
self.write_component( self.write_component(
entity, entity,
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::default()), 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);
}
} }
} }

View File

@ -4,7 +4,7 @@ use crate::{
sys::{SysScheduler, SysTimer}, sys::{SysScheduler, SysTimer},
}; };
use common::{ use common::{
comp::{Inventory, Loadout, Stats}, comp::{Inventory, Loadout, Stats, Waypoint},
msg::PresenceKind, msg::PresenceKind,
span, span,
}; };
@ -19,6 +19,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Stats>, ReadStorage<'a, Stats>,
ReadStorage<'a, Inventory>, ReadStorage<'a, Inventory>,
ReadStorage<'a, Loadout>, ReadStorage<'a, Loadout>,
ReadStorage<'a, Waypoint>,
ReadExpect<'a, character_updater::CharacterUpdater>, ReadExpect<'a, character_updater::CharacterUpdater>,
Write<'a, SysScheduler<Self>>, Write<'a, SysScheduler<Self>>,
Write<'a, SysTimer<Self>>, Write<'a, SysTimer<Self>>,
@ -31,6 +32,7 @@ impl<'a> System<'a> for Sys {
player_stats, player_stats,
player_inventories, player_inventories,
player_loadouts, player_loadouts,
player_waypoint,
updater, updater,
mut scheduler, mut scheduler,
mut timer, mut timer,
@ -45,11 +47,14 @@ impl<'a> System<'a> for Sys {
&player_stats, &player_stats,
&player_inventories, &player_inventories,
&player_loadouts, &player_loadouts,
player_waypoint.maybe(),
) )
.join() .join()
.filter_map( .filter_map(
|(presence, stats, inventory, loadout)| match presence.kind { |(presence, stats, inventory, loadout, waypoint)| match presence.kind {
PresenceKind::Character(id) => Some((id, stats, inventory, loadout)), PresenceKind::Character(id) => {
Some((id, stats, inventory, loadout, waypoint))
},
PresenceKind::Spectator => None, PresenceKind::Spectator => None,
}, },
), ),