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`.
|
||||
pub fn request_character(&mut self, name: String, body: comp::Body, main: Option<String>) {
|
||||
self.postbox
|
||||
.send_message(ClientMsg::Character { name, body, main });
|
||||
pub fn request_character(
|
||||
&mut self,
|
||||
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;
|
||||
}
|
||||
|
@ -92,9 +92,10 @@ pub enum ServerEvent {
|
||||
Possess(Uid, Uid),
|
||||
SelectCharacter {
|
||||
entity: EcsEntity,
|
||||
name: String,
|
||||
character_id: i32,
|
||||
body: comp::Body,
|
||||
main: Option<String>,
|
||||
stats: comp::Stats,
|
||||
},
|
||||
ExitIngame {
|
||||
entity: EcsEntity,
|
||||
|
@ -15,9 +15,10 @@ pub enum ClientMsg {
|
||||
},
|
||||
DeleteCharacter(i32),
|
||||
Character {
|
||||
name: String,
|
||||
character_id: i32,
|
||||
body: comp::Body,
|
||||
main: Option<String>, // Specifier for the weapon
|
||||
stats: comp::Stats,
|
||||
},
|
||||
/// Request `ClientState::Registered` from an ingame state
|
||||
ExitIngame,
|
||||
|
@ -12,14 +12,15 @@ use vek::{Rgb, Vec3};
|
||||
pub fn handle_create_character(
|
||||
server: &mut Server,
|
||||
entity: EcsEntity,
|
||||
name: String,
|
||||
character_id: i32,
|
||||
body: Body,
|
||||
main: Option<String>,
|
||||
stats: Stats,
|
||||
) {
|
||||
let state = &mut server.state;
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -71,10 +71,11 @@ impl Server {
|
||||
},
|
||||
ServerEvent::SelectCharacter {
|
||||
entity,
|
||||
name,
|
||||
character_id,
|
||||
body,
|
||||
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::CreateNpc {
|
||||
pos,
|
||||
|
@ -94,6 +94,7 @@ impl Server {
|
||||
.insert(AuthProvider::new(settings.auth_server_address.clone()));
|
||||
state.ecs_mut().insert(Tick(0));
|
||||
state.ecs_mut().insert(ChunkGenerator::new());
|
||||
|
||||
// System timers for performance monitoring
|
||||
state.ecs_mut().insert(sys::EntitySyncTimer::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::TerrainTimer::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
|
||||
state.ecs_mut().register::<RegionSubscription>();
|
||||
state.ecs_mut().register::<Client>();
|
||||
|
@ -1,6 +1,5 @@
|
||||
CREATE TABLE "stats" (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
character_id INT NOT NULL,
|
||||
character_id INT NOT NULL PRIMARY KEY,
|
||||
"level" INT NOT NULL DEFAULT 1,
|
||||
"exp" INT NOT NULL DEFAULT 0,
|
||||
endurance INT NOT NULL DEFAULT 0,
|
||||
|
@ -1,4 +1,6 @@
|
||||
pub mod character;
|
||||
pub mod stats;
|
||||
|
||||
mod error;
|
||||
mod models;
|
||||
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);
|
||||
|
||||
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.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.fitness = data.stats.fitness as u32;
|
||||
base_stats.willpower = data.stats.willpower as u32;
|
||||
|
@ -33,9 +33,10 @@ pub trait StateExt {
|
||||
fn create_player_character(
|
||||
&mut self,
|
||||
entity: EcsEntity,
|
||||
name: String,
|
||||
character_id: i32,
|
||||
body: comp::Body,
|
||||
main: Option<String>,
|
||||
stats: comp::Stats,
|
||||
server_settings: &ServerSettings,
|
||||
);
|
||||
fn notify_registered_clients(&self, msg: ServerMsg);
|
||||
@ -152,9 +153,10 @@ impl StateExt for State {
|
||||
fn create_player_character(
|
||||
&mut self,
|
||||
entity: EcsEntity,
|
||||
name: String,
|
||||
character_id: i32, // TODO
|
||||
body: comp::Body,
|
||||
main: Option<String>,
|
||||
stats: comp::Stats,
|
||||
server_settings: &ServerSettings,
|
||||
) {
|
||||
// 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;
|
||||
|
||||
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::Controller::default());
|
||||
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.
|
||||
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.
|
||||
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
||||
ClientState::Registered | ClientState::Spectator => {
|
||||
@ -193,9 +198,10 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
server_emitter.emit(ServerEvent::SelectCharacter {
|
||||
entity,
|
||||
name,
|
||||
character_id,
|
||||
body,
|
||||
main,
|
||||
stats,
|
||||
});
|
||||
},
|
||||
ClientState::Character => client.error_state(RequestStateError::Already),
|
||||
|
@ -1,5 +1,6 @@
|
||||
pub mod entity_sync;
|
||||
pub mod message;
|
||||
pub mod persistence;
|
||||
pub mod sentinel;
|
||||
pub mod subscription;
|
||||
pub mod terrain;
|
||||
@ -7,7 +8,10 @@ pub mod terrain_sync;
|
||||
pub mod waypoint;
|
||||
|
||||
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 MessageTimer = SysTimer<message::Sys>;
|
||||
@ -16,6 +20,7 @@ pub type SubscriptionTimer = SysTimer<subscription::Sys>;
|
||||
pub type TerrainTimer = SysTimer<terrain::Sys>;
|
||||
pub type TerrainSyncTimer = SysTimer<terrain_sync::Sys>;
|
||||
pub type WaypointTimer = SysTimer<waypoint::Sys>;
|
||||
pub type StatsPersistenceScheduler = SysScheduler<persistence::stats::Sys>;
|
||||
|
||||
// System names
|
||||
// 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_SYS: &str = "server_terrain_sys";
|
||||
const WAYPOINT_SYS: &str = "waypoint_sys";
|
||||
const STATS_PERSISTENCE_SYS: &str = "stats_persistence_sys";
|
||||
|
||||
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(terrain::Sys, TERRAIN_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) {
|
||||
@ -44,12 +51,50 @@ pub fn run_sync_systems(ecs: &mut specs::World) {
|
||||
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
|
||||
pub struct SysTimer<S> {
|
||||
pub nanos: u64,
|
||||
start: Option<Instant>,
|
||||
_phantom: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S> SysTimer<S> {
|
||||
pub fn start(&mut self) {
|
||||
if self.start.is_some() {
|
||||
@ -67,6 +112,7 @@ impl<S> SysTimer<S> {
|
||||
.as_nanos() as u64;
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Default for SysTimer<S> {
|
||||
fn default() -> 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) =
|
||||
char_data.get(self.char_selection_ui.selected_character)
|
||||
{
|
||||
self.client.borrow_mut().request_character(
|
||||
selected_character.character.alias.clone(),
|
||||
selected_character.body,
|
||||
selected_character.character.tool.clone(),
|
||||
);
|
||||
if let Some(character_id) = selected_character.character.id {
|
||||
self.client.borrow_mut().request_character(
|
||||
character_id,
|
||||
selected_character.body,
|
||||
selected_character.character.tool.clone(),
|
||||
selected_character.stats.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return PlayStateResult::Switch(Box::new(SessionState::new(
|
||||
|
@ -803,12 +803,10 @@ impl CharSelectionUi {
|
||||
.color(TEXT_COLOR)
|
||||
.set(self.ids.character_names[i], ui_widgets);
|
||||
|
||||
Text::new(
|
||||
&self
|
||||
.voxygen_i18n
|
||||
.get("char_selection.level_fmt")
|
||||
.replace("{level_nb}", "1"),
|
||||
) //TODO Insert real level here as soon as they get saved
|
||||
Text::new(&self.voxygen_i18n.get("char_selection.level_fmt").replace(
|
||||
"{level_nb}",
|
||||
&character_item.stats.level.level().to_string(),
|
||||
))
|
||||
.down_from(self.ids.character_names[i], 4.0)
|
||||
.font_size(self.fonts.cyri.scale(17))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
|
Loading…
Reference in New Issue
Block a user