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
- Flight
- Roll dodges melee attacks, and reduces the height of your hitbox
- Persistent waypoints (start from the last camp fire you visited)
### Changed

View File

@ -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<comp::Waypoint>,
),
},
ExitIngame {
entity: EcsEntity,

View File

@ -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),
);
}

View File

@ -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<comp::Waypoint>,
),
) {
server
.state

View File

@ -156,7 +156,9 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event
.read_resource::<persistence::character_updater::CharacterUpdater>(),
) {
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_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::<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((
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<comp::Waypoint>,
connection: VelorenTransaction,
) -> Result<Vec<Arc<common::comp::item::ItemId>>, 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

View File

@ -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<String, Error> {
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 {
Stats {
stats_id: character_id,

View File

@ -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<comp::Waypoint>,
);
/// 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::<Vec<(CharacterId, (comp::Stats, comp::Inventory, comp::Loadout))>>();
.collect::<Vec<_>>();
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::<Arc<ItemId>>::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,
)?);
}

View File

@ -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<f32>,
}

View File

@ -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<comp::Waypoint>,
);
// 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

View File

@ -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::<Uid>(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);
}
}
}

View File

@ -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<Self>>,
Write<'a, SysTimer<Self>>,
@ -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,
},
),