mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Stats persistence
- Update client code to use persisted stats - Add a system for stats persistence - Add a basic scheduler to control duration between execution of persistence systems
This commit is contained in:
parent
e5853dbdd4
commit
7c6c9f4302
@ -234,9 +234,19 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Request a state transition to `ClientState::Character`.
|
/// Request a state transition to `ClientState::Character`.
|
||||||
pub fn request_character(&mut self, name: String, body: comp::Body, main: Option<String>) {
|
pub fn request_character(
|
||||||
self.postbox
|
&mut self,
|
||||||
.send_message(ClientMsg::Character { name, body, main });
|
character_id: i32,
|
||||||
|
body: comp::Body,
|
||||||
|
main: Option<String>,
|
||||||
|
stats: comp::Stats,
|
||||||
|
) {
|
||||||
|
self.postbox.send_message(ClientMsg::Character {
|
||||||
|
character_id,
|
||||||
|
body,
|
||||||
|
main,
|
||||||
|
stats,
|
||||||
|
});
|
||||||
|
|
||||||
self.client_state = ClientState::Pending;
|
self.client_state = ClientState::Pending;
|
||||||
}
|
}
|
||||||
|
@ -92,9 +92,10 @@ pub enum ServerEvent {
|
|||||||
Possess(Uid, Uid),
|
Possess(Uid, Uid),
|
||||||
SelectCharacter {
|
SelectCharacter {
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
name: String,
|
character_id: i32,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
main: Option<String>,
|
main: Option<String>,
|
||||||
|
stats: comp::Stats,
|
||||||
},
|
},
|
||||||
ExitIngame {
|
ExitIngame {
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
|
@ -15,9 +15,10 @@ pub enum ClientMsg {
|
|||||||
},
|
},
|
||||||
DeleteCharacter(i32),
|
DeleteCharacter(i32),
|
||||||
Character {
|
Character {
|
||||||
name: String,
|
character_id: i32,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
main: Option<String>, // Specifier for the weapon
|
main: Option<String>, // Specifier for the weapon
|
||||||
|
stats: comp::Stats,
|
||||||
},
|
},
|
||||||
/// Request `ClientState::Registered` from an ingame state
|
/// Request `ClientState::Registered` from an ingame state
|
||||||
ExitIngame,
|
ExitIngame,
|
||||||
|
@ -12,14 +12,15 @@ use vek::{Rgb, Vec3};
|
|||||||
pub fn handle_create_character(
|
pub fn handle_create_character(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
name: String,
|
character_id: i32,
|
||||||
body: Body,
|
body: Body,
|
||||||
main: Option<String>,
|
main: Option<String>,
|
||||||
|
stats: Stats,
|
||||||
) {
|
) {
|
||||||
let state = &mut server.state;
|
let state = &mut server.state;
|
||||||
let server_settings = &server.server_settings;
|
let server_settings = &server.server_settings;
|
||||||
|
|
||||||
state.create_player_character(entity, name, body, main, server_settings);
|
state.create_player_character(entity, character_id, body, main, stats, server_settings);
|
||||||
sys::subscription::initialize_region_subscription(state.ecs(), entity);
|
sys::subscription::initialize_region_subscription(state.ecs(), entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,10 +71,11 @@ impl Server {
|
|||||||
},
|
},
|
||||||
ServerEvent::SelectCharacter {
|
ServerEvent::SelectCharacter {
|
||||||
entity,
|
entity,
|
||||||
name,
|
character_id,
|
||||||
body,
|
body,
|
||||||
main,
|
main,
|
||||||
} => handle_create_character(self, entity, name, body, main),
|
stats,
|
||||||
|
} => handle_create_character(self, entity, character_id, body, main, stats),
|
||||||
ServerEvent::ExitIngame { entity } => handle_exit_ingame(self, entity),
|
ServerEvent::ExitIngame { entity } => handle_exit_ingame(self, entity),
|
||||||
ServerEvent::CreateNpc {
|
ServerEvent::CreateNpc {
|
||||||
pos,
|
pos,
|
||||||
|
@ -94,6 +94,7 @@ impl Server {
|
|||||||
.insert(AuthProvider::new(settings.auth_server_address.clone()));
|
.insert(AuthProvider::new(settings.auth_server_address.clone()));
|
||||||
state.ecs_mut().insert(Tick(0));
|
state.ecs_mut().insert(Tick(0));
|
||||||
state.ecs_mut().insert(ChunkGenerator::new());
|
state.ecs_mut().insert(ChunkGenerator::new());
|
||||||
|
|
||||||
// System timers for performance monitoring
|
// System timers for performance monitoring
|
||||||
state.ecs_mut().insert(sys::EntitySyncTimer::default());
|
state.ecs_mut().insert(sys::EntitySyncTimer::default());
|
||||||
state.ecs_mut().insert(sys::MessageTimer::default());
|
state.ecs_mut().insert(sys::MessageTimer::default());
|
||||||
@ -102,6 +103,14 @@ impl Server {
|
|||||||
state.ecs_mut().insert(sys::TerrainSyncTimer::default());
|
state.ecs_mut().insert(sys::TerrainSyncTimer::default());
|
||||||
state.ecs_mut().insert(sys::TerrainTimer::default());
|
state.ecs_mut().insert(sys::TerrainTimer::default());
|
||||||
state.ecs_mut().insert(sys::WaypointTimer::default());
|
state.ecs_mut().insert(sys::WaypointTimer::default());
|
||||||
|
|
||||||
|
// System schedulers to control execution of systems
|
||||||
|
state
|
||||||
|
.ecs_mut()
|
||||||
|
.insert(sys::StatsPersistenceScheduler::every(Duration::from_secs(
|
||||||
|
5,
|
||||||
|
)));
|
||||||
|
|
||||||
// Server-only components
|
// Server-only components
|
||||||
state.ecs_mut().register::<RegionSubscription>();
|
state.ecs_mut().register::<RegionSubscription>();
|
||||||
state.ecs_mut().register::<Client>();
|
state.ecs_mut().register::<Client>();
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
CREATE TABLE "stats" (
|
CREATE TABLE "stats" (
|
||||||
id INTEGER NOT NULL PRIMARY KEY,
|
character_id INT NOT NULL PRIMARY KEY,
|
||||||
character_id INT NOT NULL,
|
|
||||||
"level" INT NOT NULL DEFAULT 1,
|
"level" INT NOT NULL DEFAULT 1,
|
||||||
"exp" INT NOT NULL DEFAULT 0,
|
"exp" INT NOT NULL DEFAULT 0,
|
||||||
endurance INT NOT NULL DEFAULT 0,
|
endurance INT NOT NULL DEFAULT 0,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
pub mod character;
|
pub mod character;
|
||||||
|
pub mod stats;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod models;
|
mod models;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
@ -91,10 +91,13 @@ impl From<StatsJoinData<'_>> for comp::Stats {
|
|||||||
let mut base_stats = comp::Stats::new(String::from(&data.character.alias), *data.body);
|
let mut base_stats = comp::Stats::new(String::from(&data.character.alias), *data.body);
|
||||||
|
|
||||||
base_stats.level.set_level(data.stats.level as u32);
|
base_stats.level.set_level(data.stats.level as u32);
|
||||||
base_stats.update_max_hp();
|
|
||||||
|
|
||||||
base_stats.exp.set_current(data.stats.exp as u32);
|
base_stats.exp.set_current(data.stats.exp as u32);
|
||||||
|
|
||||||
|
base_stats.update_max_hp();
|
||||||
|
base_stats
|
||||||
|
.health
|
||||||
|
.set_to(base_stats.health.maximum(), comp::HealthSource::Revive);
|
||||||
|
|
||||||
base_stats.endurance = data.stats.endurance as u32;
|
base_stats.endurance = data.stats.endurance as u32;
|
||||||
base_stats.fitness = data.stats.fitness as u32;
|
base_stats.fitness = data.stats.fitness as u32;
|
||||||
base_stats.willpower = data.stats.willpower as u32;
|
base_stats.willpower = data.stats.willpower as u32;
|
||||||
|
@ -33,9 +33,10 @@ pub trait StateExt {
|
|||||||
fn create_player_character(
|
fn create_player_character(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
name: String,
|
character_id: i32,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
main: Option<String>,
|
main: Option<String>,
|
||||||
|
stats: comp::Stats,
|
||||||
server_settings: &ServerSettings,
|
server_settings: &ServerSettings,
|
||||||
);
|
);
|
||||||
fn notify_registered_clients(&self, msg: ServerMsg);
|
fn notify_registered_clients(&self, msg: ServerMsg);
|
||||||
@ -152,9 +153,10 @@ impl StateExt for State {
|
|||||||
fn create_player_character(
|
fn create_player_character(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
name: String,
|
character_id: i32, // TODO
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
main: Option<String>,
|
main: Option<String>,
|
||||||
|
stats: comp::Stats,
|
||||||
server_settings: &ServerSettings,
|
server_settings: &ServerSettings,
|
||||||
) {
|
) {
|
||||||
// Give no item when an invalid specifier is given
|
// Give no item when an invalid specifier is given
|
||||||
@ -163,7 +165,7 @@ impl StateExt for State {
|
|||||||
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
|
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
|
||||||
|
|
||||||
self.write_component(entity, body);
|
self.write_component(entity, body);
|
||||||
self.write_component(entity, comp::Stats::new(name, body));
|
self.write_component(entity, stats);
|
||||||
self.write_component(entity, comp::Energy::new(1000));
|
self.write_component(entity, comp::Energy::new(1000));
|
||||||
self.write_component(entity, comp::Controller::default());
|
self.write_component(entity, comp::Controller::default());
|
||||||
self.write_component(entity, comp::Pos(spawn_point));
|
self.write_component(entity, comp::Pos(spawn_point));
|
||||||
@ -222,6 +224,19 @@ impl StateExt for State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Set the character id for the player
|
||||||
|
// TODO this results in a warning in the console: "Error modifying synced
|
||||||
|
// component, it doesn't seem to exist"
|
||||||
|
// It appears to be caused by the player not yet existing on the client at this
|
||||||
|
// point, despite being able to write the data on the server
|
||||||
|
&self
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Player>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.map(|player| {
|
||||||
|
player.character_id = Some(character_id);
|
||||||
|
});
|
||||||
|
|
||||||
// Make sure physics are accepted.
|
// Make sure physics are accepted.
|
||||||
self.write_component(entity, comp::ForceUpdate);
|
self.write_component(entity, comp::ForceUpdate);
|
||||||
|
|
||||||
|
@ -171,7 +171,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
},
|
},
|
||||||
ClientMsg::Character { name, body, main } => match client.client_state {
|
ClientMsg::Character {
|
||||||
|
character_id,
|
||||||
|
body,
|
||||||
|
main,
|
||||||
|
stats,
|
||||||
|
} => match client.client_state {
|
||||||
// Become Registered first.
|
// Become Registered first.
|
||||||
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
||||||
ClientState::Registered | ClientState::Spectator => {
|
ClientState::Registered | ClientState::Spectator => {
|
||||||
@ -193,9 +198,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
server_emitter.emit(ServerEvent::SelectCharacter {
|
server_emitter.emit(ServerEvent::SelectCharacter {
|
||||||
entity,
|
entity,
|
||||||
name,
|
character_id,
|
||||||
body,
|
body,
|
||||||
main,
|
main,
|
||||||
|
stats,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
ClientState::Character => client.error_state(RequestStateError::Already),
|
ClientState::Character => client.error_state(RequestStateError::Already),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
pub mod entity_sync;
|
pub mod entity_sync;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
pub mod persistence;
|
||||||
pub mod sentinel;
|
pub mod sentinel;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
pub mod terrain;
|
pub mod terrain;
|
||||||
@ -7,7 +8,10 @@ pub mod terrain_sync;
|
|||||||
pub mod waypoint;
|
pub mod waypoint;
|
||||||
|
|
||||||
use specs::DispatcherBuilder;
|
use specs::DispatcherBuilder;
|
||||||
use std::{marker::PhantomData, time::Instant};
|
use std::{
|
||||||
|
marker::PhantomData,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
pub type EntitySyncTimer = SysTimer<entity_sync::Sys>;
|
pub type EntitySyncTimer = SysTimer<entity_sync::Sys>;
|
||||||
pub type MessageTimer = SysTimer<message::Sys>;
|
pub type MessageTimer = SysTimer<message::Sys>;
|
||||||
@ -16,6 +20,7 @@ pub type SubscriptionTimer = SysTimer<subscription::Sys>;
|
|||||||
pub type TerrainTimer = SysTimer<terrain::Sys>;
|
pub type TerrainTimer = SysTimer<terrain::Sys>;
|
||||||
pub type TerrainSyncTimer = SysTimer<terrain_sync::Sys>;
|
pub type TerrainSyncTimer = SysTimer<terrain_sync::Sys>;
|
||||||
pub type WaypointTimer = SysTimer<waypoint::Sys>;
|
pub type WaypointTimer = SysTimer<waypoint::Sys>;
|
||||||
|
pub type StatsPersistenceScheduler = SysScheduler<persistence::stats::Sys>;
|
||||||
|
|
||||||
// System names
|
// System names
|
||||||
// Note: commented names may be useful in the future
|
// Note: commented names may be useful in the future
|
||||||
@ -25,10 +30,12 @@ pub type WaypointTimer = SysTimer<waypoint::Sys>;
|
|||||||
//const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys";
|
//const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys";
|
||||||
const TERRAIN_SYS: &str = "server_terrain_sys";
|
const TERRAIN_SYS: &str = "server_terrain_sys";
|
||||||
const WAYPOINT_SYS: &str = "waypoint_sys";
|
const WAYPOINT_SYS: &str = "waypoint_sys";
|
||||||
|
const STATS_PERSISTENCE_SYS: &str = "stats_persistence_sys";
|
||||||
|
|
||||||
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||||
dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]);
|
dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]);
|
||||||
dispatch_builder.add(waypoint::Sys, WAYPOINT_SYS, &[]);
|
dispatch_builder.add(waypoint::Sys, WAYPOINT_SYS, &[]);
|
||||||
|
dispatch_builder.add(persistence::stats::Sys, STATS_PERSISTENCE_SYS, &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_sync_systems(ecs: &mut specs::World) {
|
pub fn run_sync_systems(ecs: &mut specs::World) {
|
||||||
@ -44,12 +51,50 @@ pub fn run_sync_systems(ecs: &mut specs::World) {
|
|||||||
entity_sync::Sys.run_now(ecs);
|
entity_sync::Sys.run_now(ecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used to schedule systems to run at an interval
|
||||||
|
pub struct SysScheduler<S> {
|
||||||
|
interval: Duration,
|
||||||
|
last_run: Instant,
|
||||||
|
_phantom: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> SysScheduler<S> {
|
||||||
|
pub fn every(interval: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
interval,
|
||||||
|
last_run: Instant::now(),
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_run(&mut self) -> bool {
|
||||||
|
if self.last_run.elapsed() > self.interval {
|
||||||
|
self.last_run = Instant::now();
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Default for SysScheduler<S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
interval: Duration::from_secs(30),
|
||||||
|
last_run: Instant::now(),
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Used to keep track of how much time each system takes
|
/// Used to keep track of how much time each system takes
|
||||||
pub struct SysTimer<S> {
|
pub struct SysTimer<S> {
|
||||||
pub nanos: u64,
|
pub nanos: u64,
|
||||||
start: Option<Instant>,
|
start: Option<Instant>,
|
||||||
_phantom: PhantomData<S>,
|
_phantom: PhantomData<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> SysTimer<S> {
|
impl<S> SysTimer<S> {
|
||||||
pub fn start(&mut self) {
|
pub fn start(&mut self) {
|
||||||
if self.start.is_some() {
|
if self.start.is_some() {
|
||||||
@ -67,6 +112,7 @@ impl<S> SysTimer<S> {
|
|||||||
.as_nanos() as u64;
|
.as_nanos() as u64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Default for SysTimer<S> {
|
impl<S> Default for SysTimer<S> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
1
server/src/sys/persistence/mod.rs
Normal file
1
server/src/sys/persistence/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod stats;
|
30
server/src/sys/persistence/stats.rs
Normal file
30
server/src/sys/persistence/stats.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::{persistence::stats, sys::SysScheduler};
|
||||||
|
use common::comp::{Player, Stats};
|
||||||
|
use specs::{Join, ReadStorage, System, Write};
|
||||||
|
|
||||||
|
pub struct Sys;
|
||||||
|
|
||||||
|
impl<'a> System<'a> for Sys {
|
||||||
|
type SystemData = (
|
||||||
|
ReadStorage<'a, Player>,
|
||||||
|
ReadStorage<'a, Stats>,
|
||||||
|
Write<'a, SysScheduler<Self>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn run(&mut self, (players, player_stats, mut scheduler): Self::SystemData) {
|
||||||
|
if scheduler.should_run() {
|
||||||
|
for (player, stats) in (&players, &player_stats).join() {
|
||||||
|
if let Some(character_id) = player.character_id {
|
||||||
|
stats::update(
|
||||||
|
character_id,
|
||||||
|
Some(stats.level.level() as i32),
|
||||||
|
Some(stats.exp.current() as i32),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -87,11 +87,14 @@ impl PlayState for CharSelectionState {
|
|||||||
if let Some(selected_character) =
|
if let Some(selected_character) =
|
||||||
char_data.get(self.char_selection_ui.selected_character)
|
char_data.get(self.char_selection_ui.selected_character)
|
||||||
{
|
{
|
||||||
self.client.borrow_mut().request_character(
|
if let Some(character_id) = selected_character.character.id {
|
||||||
selected_character.character.alias.clone(),
|
self.client.borrow_mut().request_character(
|
||||||
selected_character.body,
|
character_id,
|
||||||
selected_character.character.tool.clone(),
|
selected_character.body,
|
||||||
);
|
selected_character.character.tool.clone(),
|
||||||
|
selected_character.stats.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlayStateResult::Switch(Box::new(SessionState::new(
|
return PlayStateResult::Switch(Box::new(SessionState::new(
|
||||||
|
@ -803,12 +803,10 @@ impl CharSelectionUi {
|
|||||||
.color(TEXT_COLOR)
|
.color(TEXT_COLOR)
|
||||||
.set(self.ids.character_names[i], ui_widgets);
|
.set(self.ids.character_names[i], ui_widgets);
|
||||||
|
|
||||||
Text::new(
|
Text::new(&self.voxygen_i18n.get("char_selection.level_fmt").replace(
|
||||||
&self
|
"{level_nb}",
|
||||||
.voxygen_i18n
|
&character_item.stats.level.level().to_string(),
|
||||||
.get("char_selection.level_fmt")
|
))
|
||||||
.replace("{level_nb}", "1"),
|
|
||||||
) //TODO Insert real level here as soon as they get saved
|
|
||||||
.down_from(self.ids.character_names[i], 4.0)
|
.down_from(self.ids.character_names[i], 4.0)
|
||||||
.font_size(self.fonts.cyri.scale(17))
|
.font_size(self.fonts.cyri.scale(17))
|
||||||
.font_id(self.fonts.cyri.conrod_id)
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
|
Loading…
Reference in New Issue
Block a user