diff --git a/client/src/lib.rs b/client/src/lib.rs index d01636e650..46fefef2d2 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -25,9 +25,9 @@ use common::{ }, event::{EventBus, LocalEvent}, msg::{ - validate_chat_msg, ChatMsgValidationError, ClientGeneral, ClientInGame, ClientMsg, - ClientRegister, ClientType, DisconnectReason, InviteAnswer, Notification, PingMsg, - PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneral, ServerInfo, ServerInit, + validate_chat_msg, ChatMsgValidationError, ClientGeneral, ClientMsg, ClientRegister, + ClientType, DisconnectReason, InviteAnswer, Notification, PingMsg, PlayerInfo, + PlayerListUpdate, PresenceKind, RegisterError, ServerGeneral, ServerInfo, ServerInit, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, }, outcome::Outcome, @@ -71,7 +71,7 @@ pub enum Event { pub struct Client { registered: bool, - in_game: Option, + presence: Option, thread_pool: ThreadPool, pub server_info: ServerInfo, /// Just the "base" layer for LOD; currently includes colors and nothing @@ -98,7 +98,6 @@ pub struct Client { pub world_map: (Arc, Vec2, Vec2), pub player_list: HashMap, pub character_list: CharacterList, - pub active_character_id: Option, recipe_book: RecipeBook, available_recipes: HashSet, @@ -376,7 +375,7 @@ impl Client { Ok(Self { registered: false, - in_game: None, + presence: None, thread_pool, server_info, world_map, @@ -385,7 +384,6 @@ impl Client { lod_horizon, player_list: HashMap::new(), character_list: CharacterList::default(), - active_character_id: None, recipe_book, available_recipes: HashSet::default(), @@ -467,12 +465,12 @@ impl Client { #[cfg(debug_assertions)] { const C_TYPE: ClientType = ClientType::Game; - let verified = msg.verify(C_TYPE, self.registered, self.in_game); + let verified = msg.verify(C_TYPE, self.registered, self.presence); assert!( verified, format!( - "c_type: {:?}, registered: {}, in_game: {:?}, msg: {:?}", - C_TYPE, self.registered, self.in_game, msg + "c_type: {:?}, registered: {}, presence: {:?}, msg: {:?}", + C_TYPE, self.registered, self.presence, msg ) ); } @@ -528,9 +526,7 @@ impl Client { self.send_msg(ClientGeneral::Character(character_id)); //Assume we are in_game unless server tells us otherwise - self.in_game = Some(ClientInGame::Character); - - self.active_character_id = Some(character_id); + self.presence = Some(PresenceKind::Character(character_id)); } /// Load the current players character list @@ -556,7 +552,7 @@ impl Client { debug!("Sending logout from server"); self.send_msg(ClientGeneral::Terminate); self.registered = false; - self.in_game = None; + self.presence = None; } /// Request a state transition to `ClientState::Registered` from an ingame @@ -922,7 +918,7 @@ impl Client { // 1) Handle input from frontend. // Pass character actions from frontend input to the player's entity. - if self.in_game.is_some() { + if self.presence.is_some() { if let Err(e) = self .state .ecs() @@ -1093,7 +1089,7 @@ impl Client { } // 6) Update the server about the player's physics attributes. - if self.in_game.is_some() { + if self.presence.is_some() { if let (Some(pos), Some(vel), Some(ori)) = ( self.state.read_storage().get(self.entity).cloned(), self.state.read_storage().get(self.entity).cloned(), @@ -1375,9 +1371,9 @@ impl Client { }; frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg(msg))); }, - // Cleanup for when the client goes back to the `in_game = None` + // Cleanup for when the client goes back to the `presence = None` ServerGeneral::ExitInGameSuccess => { - self.in_game = None; + self.presence = None; self.clean_state(); }, ServerGeneral::InventoryUpdate(mut inventory, event) => { @@ -1438,7 +1434,7 @@ impl Client { }, ServerGeneral::CharacterDataLoadError(error) => { trace!("Handling join error by server"); - self.in_game = None; + self.presence = None; self.clean_state(); self.character_list.error = Some(error); }, @@ -1548,7 +1544,7 @@ impl Client { pub fn uid(&self) -> Option { self.state.read_component_copied(self.entity) } - pub fn in_game(&self) -> Option { self.in_game } + pub fn presence(&self) -> Option { self.presence } pub fn registered(&self) -> bool { self.registered } diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index 7b12b8741a..d8a5a90307 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -1,4 +1,3 @@ -use crate::character::CharacterId; use authc::Uuid; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage, NullStorage}; @@ -9,25 +8,11 @@ const MAX_ALIAS_LEN: usize = 32; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Player { pub alias: String, - pub character_id: Option, - pub view_distance: Option, uuid: Uuid, } impl Player { - pub fn new( - alias: String, - character_id: Option, - view_distance: Option, - uuid: Uuid, - ) -> Self { - Self { - alias, - character_id, - view_distance, - uuid, - } - } + pub fn new(alias: String, uuid: Uuid) -> Self { Self { alias, uuid } } pub fn is_valid(&self) -> bool { Self::alias_is_valid(&self.alias) } diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index fc68afe140..c9c5bb1fba 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -86,21 +86,21 @@ impl ClientMsg { &self, c_type: ClientType, registered: bool, - in_game: Option, + presence: Option, ) -> bool { match self { ClientMsg::Type(t) => c_type == *t, - ClientMsg::Register(_) => !registered && in_game.is_none(), + ClientMsg::Register(_) => !registered && presence.is_none(), ClientMsg::General(g) => { registered && match g { ClientGeneral::RequestCharacterList | ClientGeneral::CreateCharacter { .. } | ClientGeneral::DeleteCharacter(_) => { - c_type != ClientType::ChatOnly && in_game.is_none() + c_type != ClientType::ChatOnly && presence.is_none() }, ClientGeneral::Character(_) | ClientGeneral::Spectate => { - c_type == ClientType::Game && in_game.is_none() + c_type == ClientType::Game && presence.is_none() }, //Only in game ClientGeneral::ControllerInputs(_) @@ -115,7 +115,7 @@ impl ClientMsg { | ClientGeneral::UnlockSkill(_) | ClientGeneral::RefundSkill(_) | ClientGeneral::UnlockSkillGroup(_) => { - c_type == ClientType::Game && in_game.is_some() + c_type == ClientType::Game && presence.is_some() }, //Always possible ClientGeneral::ChatMsg(_) | ClientGeneral::Terminate => true, diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index c25cb8a8a1..4580088795 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -13,12 +13,13 @@ pub use self::{ }, world_msg::WorldMapMsg, }; +use crate::character::CharacterId; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum ClientInGame { +pub enum PresenceKind { Spectator, - Character, + Character(CharacterId), } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 3f7fe9beb1..625d07b0e1 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -182,11 +182,11 @@ impl ServerMsg { &self, c_type: ClientType, registered: bool, - in_game: Option, + presence: Option, ) -> bool { match self { ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => { - !registered && in_game.is_none() + !registered && presence.is_none() }, ServerMsg::General(g) => { registered @@ -195,10 +195,10 @@ impl ServerMsg { ServerGeneral::CharacterDataLoadError(_) | ServerGeneral::CharacterListUpdate(_) | ServerGeneral::CharacterActionError(_) => { - c_type != ClientType::ChatOnly && in_game.is_none() + c_type != ClientType::ChatOnly && presence.is_none() }, ServerGeneral::CharacterSuccess => { - c_type == ClientType::Game && in_game.is_none() + c_type == ClientType::Game && presence.is_none() }, //Ingame related ServerGeneral::GroupUpdate(_) @@ -212,7 +212,7 @@ impl ServerMsg { | ServerGeneral::SetViewDistance(_) | ServerGeneral::Outcomes(_) | ServerGeneral::Knockback(_) => { - c_type == ClientType::Game && in_game.is_some() + c_type == ClientType::Game && presence.is_some() }, // Always possible ServerGeneral::PlayerListUpdate(_) diff --git a/server-cli/src/shutdown_coordinator.rs b/server-cli/src/shutdown_coordinator.rs index 59705c2003..fa9be90b25 100644 --- a/server-cli/src/shutdown_coordinator.rs +++ b/server-cli/src/shutdown_coordinator.rs @@ -155,7 +155,7 @@ impl ShutdownCoordinator { /// Logs and sends a message to all connected clients fn send_msg(server: &mut Server, msg: String) { info!("{}", &msg); - server.notify_registered_clients(ChatType::CommandError.server_msg(msg)); + server.notify_players(ChatType::CommandError.server_msg(msg)); } /// Converts a `Duration` into text in the format XsXm for example 1 minute diff --git a/server/src/client.rs b/server/src/client.rs index 680a826a3b..a968194fad 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,14 +1,16 @@ -use common::msg::{ClientInGame, ClientType}; -use hashbrown::HashSet; +use common::msg::ClientType; use network::Participant; -use specs::{Component, FlaggedStorage}; +use specs::Component; use specs_idvs::IdvStorage; -use vek::*; +/// Client handles ALL network related information of everything that connects +/// to the server Client DOES NOT handle game states +/// Client DOES NOT handle network information that is only relevant to some +/// "things" connecting to the server (there is currently no such case). First a +/// Client connects to the game, when it registers, it gets the `Player` +/// component, when he enters the game he gets the `InGame` component. pub struct Client { - pub registered: bool, pub client_type: ClientType, - pub in_game: Option, pub participant: Option, pub last_ping: f64, pub login_msg_sent: bool, @@ -16,20 +18,5 @@ pub struct Client { } impl Component for Client { - type Storage = FlaggedStorage>; -} - -// Distance from fuzzy_chunk before snapping to current chunk -pub const CHUNK_FUZZ: u32 = 2; -// Distance out of the range of a region before removing it from subscriptions -pub const REGION_FUZZ: u32 = 16; - -#[derive(Clone, Debug)] -pub struct RegionSubscription { - pub fuzzy_chunk: Vec2, - pub regions: HashSet>, -} - -impl Component for RegionSubscription { - type Storage = FlaggedStorage>; + type Storage = IdvStorage; } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 204d4f399d..edb1cc2fc0 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -510,11 +510,11 @@ fn handle_alias( *uid, player.alias.clone(), )); - server.state.notify_registered_clients(msg); + server.state.notify_players(msg); // Announce alias change if target has a Body. if ecs.read_storage::().get(target).is_some() { - server.state.notify_registered_clients( + server.state.notify_players( ChatType::CommandInfo .server_msg(format!("{} is now known as {}.", old_alias, player.alias)), ); @@ -1214,7 +1214,7 @@ fn handle_adminify( .expect("Player should have uid"), is_admin, )); - server.state.notify_registered_clients(msg); + server.state.notify_players(msg); }, None => { server.notify_client( @@ -1671,11 +1671,9 @@ fn handle_set_level( .read_storage::() .get(player) .expect("Failed to get uid for player"); - server - .state - .notify_registered_clients(ServerGeneral::PlayerListUpdate( - PlayerListUpdate::LevelChange(uid, lvl), - )); + server.state.notify_players(ServerGeneral::PlayerListUpdate( + PlayerListUpdate::LevelChange(uid, lvl), + )); if let Some(stats) = server .state diff --git a/server/src/connection_handler.rs b/server/src/connection_handler.rs index e0d2214388..dfe64a971d 100644 --- a/server/src/connection_handler.rs +++ b/server/src/connection_handler.rs @@ -137,9 +137,7 @@ impl ConnectionHandler { }; let client = Client { - registered: false, client_type, - in_game: None, participant: Some(participant), last_ping: server_data.time, login_msg_sent: false, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index c8896e1f8f..dbbd361f8a 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -200,9 +200,8 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc | HealthSource::Healing { by: _ } | HealthSource::Unknown => KillSource::Other, }; - state.notify_registered_clients( - comp::ChatType::Kill(kill_source, *uid).server_msg("".to_string()), - ); + state + .notify_players(comp::ChatType::Kill(kill_source, *uid).server_msg("".to_string())); } } @@ -668,11 +667,9 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) { .get(entity) .expect("Failed to fetch uid component for entity."); - server - .state - .notify_registered_clients(ServerGeneral::PlayerListUpdate( - PlayerListUpdate::LevelChange(*uid, new_level), - )); + server.state.notify_players(ServerGeneral::PlayerListUpdate( + PlayerListUpdate::LevelChange(*uid, new_level), + )); } pub fn handle_buff(server: &mut Server, entity: EcsEntity, buff_change: buff::BuffChange) { diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 79a9073309..9d868692d2 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -1,5 +1,6 @@ use crate::{ - client::{Client, RegionSubscription}, + client::Client, + presence::RegionSubscription, streams::{ CharacterScreenStream, GeneralStream, GetStream, InGameStream, PingStream, RegisterStream, }, diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 0e79497073..65297e3f60 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -3,6 +3,7 @@ use crate::{ client::Client, login_provider::LoginProvider, persistence, + presence::Presence, state_ext::StateExt, streams::{ CharacterScreenStream, GeneralStream, GetStream, InGameStream, PingStream, RegisterStream, @@ -12,7 +13,7 @@ use crate::{ use common::{ comp, comp::{group, Player}, - msg::{PlayerListUpdate, ServerGeneral}, + msg::{PlayerListUpdate, PresenceKind, ServerGeneral}, span, sync::{Uid, UidAllocator}, }; @@ -37,7 +38,7 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { .cloned(); if let Some(( - mut client, + client, uid, player, general_stream, @@ -60,7 +61,6 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { )) })() { // Tell client its request was successful - client.in_game = None; in_game_stream.send_fallible(ServerGeneral::ExitInGameSuccess); let entity_builder = state @@ -163,9 +163,9 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event state.read_storage::().get(entity), state.read_storage::().get(entity), ) { - state.notify_registered_clients(comp::ChatType::Offline(*uid).server_msg("")); + state.notify_players(comp::ChatType::Offline(*uid).server_msg("")); - state.notify_registered_clients(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove( + state.notify_players(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove( *uid, ))); } @@ -177,8 +177,8 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event } // Sync the player's character data to the database - if let (Some(player), Some(stats), Some(inventory), Some(loadout), updater) = ( - state.read_storage::().get(entity), + if let (Some(presences), Some(stats), Some(inventory), Some(loadout), updater) = ( + state.read_storage::().get(entity), state.read_storage::().get(entity), state.read_storage::().get(entity), state.read_storage::().get(entity), @@ -186,7 +186,7 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event .ecs() .read_resource::(), ) { - if let Some(character_id) = player.character_id { + if let PresenceKind::Character(character_id) = presences.kind { updater.update(character_id, stats, inventory, loadout); } } diff --git a/server/src/lib.rs b/server/src/lib.rs index f87a821566..256f12c0f0 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -17,6 +17,7 @@ pub mod input; pub mod login_provider; pub mod metrics; pub mod persistence; +pub mod presence; pub mod settings; pub mod state_ext; pub mod streams; @@ -35,11 +36,12 @@ pub use crate::{ use crate::{ alias_validator::AliasValidator, chunk_generator::ChunkGenerator, - client::{Client, RegionSubscription}, + client::Client, cmd::ChatCommandExt, connection_handler::ConnectionHandler, data_dir::DataDir, login_provider::LoginProvider, + presence::{Presence, RegionSubscription}, state_ext::StateExt, streams::{ CharacterScreenStream, GeneralStream, GetStream, InGameStream, PingStream, RegisterStream, @@ -190,6 +192,7 @@ impl Server { // Server-only components state.ecs_mut().register::(); state.ecs_mut().register::(); + state.ecs_mut().register::(); state.ecs_mut().register::(); state.ecs_mut().register::(); state.ecs_mut().register::(); @@ -971,9 +974,7 @@ impl Server { } } - pub fn notify_registered_clients(&mut self, msg: ServerGeneral) { - self.state.notify_registered_clients(msg); - } + pub fn notify_players(&mut self, msg: ServerGeneral) { self.state.notify_players(msg); } pub fn generate_chunk(&mut self, entity: EcsEntity, key: Vec2) { self.state @@ -1051,7 +1052,7 @@ impl Server { impl Drop for Server { fn drop(&mut self) { self.state - .notify_registered_clients(ServerGeneral::Disconnect(DisconnectReason::Shutdown)); + .notify_players(ServerGeneral::Disconnect(DisconnectReason::Shutdown)); } } diff --git a/server/src/presence.rs b/server/src/presence.rs new file mode 100644 index 0000000000..5cba90dd72 --- /dev/null +++ b/server/src/presence.rs @@ -0,0 +1,40 @@ +use common::msg::PresenceKind; +use hashbrown::HashSet; +use serde::{Deserialize, Serialize}; +use specs::{Component, FlaggedStorage}; +use specs_idvs::IdvStorage; +use vek::*; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Presence { + pub view_distance: u32, + pub kind: PresenceKind, +} + +impl Presence { + pub fn new(view_distance: u32, kind: PresenceKind) -> Self { + Self { + view_distance, + kind, + } + } +} + +impl Component for Presence { + type Storage = FlaggedStorage>; +} + +// Distance from fuzzy_chunk before snapping to current chunk +pub const CHUNK_FUZZ: u32 = 2; +// Distance out of the range of a region before removing it from subscriptions +pub const REGION_FUZZ: u32 = 16; + +#[derive(Clone, Debug)] +pub struct RegionSubscription { + pub fuzzy_chunk: Vec2, + pub regions: HashSet>, +} + +impl Component for RegionSubscription { + type Storage = FlaggedStorage>; +} diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 7cd3082955..71548b2dd8 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -1,6 +1,6 @@ use crate::{ - client::Client, persistence::PersistedComponents, + presence::Presence, streams::{CharacterScreenStream, GeneralStream, GetStream, InGameStream}, sys::sentinel::DeletedEntities, SpawnPoint, @@ -9,7 +9,7 @@ use common::{ character::CharacterId, comp, effect::Effect, - msg::{CharacterInfo, ClientInGame, PlayerListUpdate, ServerGeneral}, + msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral}, state::State, sync::{Uid, UidAllocator, WorldSyncExt}, util::Dir, @@ -63,7 +63,7 @@ pub trait StateExt { fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents); /// Iterates over registered clients and send each `ServerMsg` fn send_chat(&self, msg: comp::UnresolvedChatMsg); - fn notify_registered_clients(&self, msg: ServerGeneral); + fn notify_players(&self, msg: ServerGeneral); fn notify_in_game_clients(&self, msg: ServerGeneral); /// Delete an entity, recording the deletion in [`DeletedEntities`] fn delete_entity_recorded( @@ -212,28 +212,19 @@ impl StateExt for State { // Make sure physics components are updated self.write_component(entity, comp::ForceUpdate); - // 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::() - .get_mut(entity) - .map(|player| { - player.character_id = Some(character_id); - }); + const INITIAL_VD: u32 = 5; //will be changed after login + self.write_component( + entity, + Presence::new(INITIAL_VD, PresenceKind::Character(character_id)), + ); // Tell the client its request was successful. - if let Some(client) = self.ecs().write_storage::().get_mut(entity) { - if let Some(character_screen_stream) = self - .ecs() - .write_storage::() - .get_mut(entity) - { - client.in_game = Some(ClientInGame::Character); - character_screen_stream.send_fallible(ServerGeneral::CharacterSuccess); - } + if let Some(character_screen_stream) = self + .ecs() + .write_storage::() + .get_mut(entity) + { + character_screen_stream.send_fallible(ServerGeneral::CharacterSuccess); } } @@ -242,7 +233,7 @@ impl StateExt for State { if let Some(player_uid) = self.read_component_copied::(entity) { // Notify clients of a player list update - self.notify_registered_clients(ServerGeneral::PlayerListUpdate( + self.notify_players(ServerGeneral::PlayerListUpdate( PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo { name: String::from(&stats.name), level: stats.level.level(), @@ -287,9 +278,7 @@ impl StateExt for State { | comp::ChatType::Loot | comp::ChatType::Kill(_, _) | comp::ChatType::Meta - | comp::ChatType::World(_) => { - self.notify_registered_clients(ServerGeneral::ChatMsg(resolved_msg)) - }, + | comp::ChatType::World(_) => self.notify_players(ServerGeneral::ChatMsg(resolved_msg)), comp::ChatType::Online(u) => { for (general_stream, uid) in ( &mut ecs.write_storage::(), @@ -389,14 +378,13 @@ impl StateExt for State { } /// Sends the message to all connected clients - fn notify_registered_clients(&self, msg: ServerGeneral) { + fn notify_players(&self, msg: ServerGeneral) { let mut lazy_msg = None; for (general_stream, _) in ( &mut self.ecs().write_storage::(), - &self.ecs().read_storage::(), + &self.ecs().read_storage::(), ) .join() - .filter(|(_, c)| c.registered) { if lazy_msg.is_none() { lazy_msg = Some(general_stream.prepare(&msg)); @@ -412,10 +400,9 @@ impl StateExt for State { let mut lazy_msg = None; for (general_stream, _) in ( &mut self.ecs().write_storage::(), - &self.ecs().read_storage::(), + &self.ecs().read_storage::(), ) .join() - .filter(|(_, c)| c.in_game.is_some()) { if lazy_msg.is_none() { lazy_msg = Some(general_stream.prepare(&msg)); diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 7431f979fe..811af3b723 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -3,12 +3,13 @@ use super::{ SysTimer, }; use crate::{ - client::{Client, RegionSubscription}, + client::Client, + presence::{Presence, RegionSubscription}, streams::{GeneralStream, GetStream, InGameStream}, Tick, }; use common::{ - comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, + comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel}, msg::ServerGeneral, outcome::Outcome, region::{Event as RegionEvent, RegionMap}, @@ -39,7 +40,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Ori>, ReadStorage<'a, Inventory>, ReadStorage<'a, RegionSubscription>, - ReadStorage<'a, Player>, + ReadStorage<'a, Presence>, WriteStorage<'a, Last>, WriteStorage<'a, Last>, WriteStorage<'a, Last>, @@ -68,7 +69,7 @@ impl<'a> System<'a> for Sys { orientations, inventories, subscriptions, - players, + presences, mut last_pos, mut last_vel, mut last_ori, @@ -112,6 +113,7 @@ impl<'a> System<'a> for Sys { let mut subscribers = ( &mut clients, &entities, + presences.maybe(), &subscriptions, &positions, &mut in_game_streams, @@ -119,8 +121,16 @@ impl<'a> System<'a> for Sys { ) .join() .filter_map( - |(client, entity, subscription, pos, in_game_stream, general_stream)| { - if client.in_game.is_some() && subscription.regions.contains(&key) { + |( + client, + entity, + presence, + subscription, + pos, + in_game_stream, + general_stream, + )| { + if presence.is_some() && subscription.regions.contains(&key) { Some(( client, &subscription.regions, @@ -339,10 +349,10 @@ impl<'a> System<'a> for Sys { // Handle entity deletion in regions that don't exist in RegionMap // (theoretically none) for (region_key, deleted) in deleted_entities.take_remaining_deleted() { - for general_stream in (&mut clients, &subscriptions, &mut general_streams) + for general_stream in (presences.maybe(), &subscriptions, &mut general_streams) .join() - .filter_map(|(client, subscription, general_stream)| { - if client.in_game.is_some() && subscription.regions.contains(®ion_key) { + .filter_map(|(presence, subscription, general_stream)| { + if presence.is_some() && subscription.regions.contains(®ion_key) { Some(general_stream) } else { None @@ -368,13 +378,14 @@ impl<'a> System<'a> for Sys { } // Sync outcomes - for (player, pos, in_game_stream) in - (&players, positions.maybe(), &mut in_game_streams).join() + for (presence, pos, in_game_stream) in + (presences.maybe(), positions.maybe(), &mut in_game_streams).join() { let is_near = |o_pos: Vec3| { - pos.zip_with(player.view_distance, |pos, vd| { + pos.zip_with(presence, |pos, presence| { pos.0.xy().distance_squared(o_pos.xy()) - < (vd as f32 * TerrainChunkSize::RECT_SIZE.x as f32).powf(2.0) + < (presence.view_distance as f32 * TerrainChunkSize::RECT_SIZE.x as f32) + .powf(2.0) }) }; diff --git a/server/src/sys/msg/character_screen.rs b/server/src/sys/msg/character_screen.rs index 4c344a5007..2fb8b8a49c 100644 --- a/server/src/sys/msg/character_screen.rs +++ b/server/src/sys/msg/character_screen.rs @@ -4,19 +4,20 @@ use crate::{ character_creator, client::Client, persistence::character_loader::CharacterLoader, + presence::Presence, streams::{CharacterScreenStream, GeneralStream, GetStream}, EditableSettings, }; use common::{ comp::{ChatType, Player, UnresolvedChatMsg}, event::{EventBus, ServerEvent}, - msg::{ClientGeneral, ClientInGame, ServerGeneral}, + msg::{ClientGeneral, ServerGeneral}, span, state::Time, sync::Uid, }; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage}; -use tracing::debug; +use tracing::{debug, warn}; impl Sys { #[allow(clippy::too_many_arguments)] @@ -30,64 +31,65 @@ impl Sys { character_loader: &ReadExpect<'_, CharacterLoader>, uids: &ReadStorage<'_, Uid>, players: &ReadStorage<'_, Player>, + presences: &ReadStorage<'_, Presence>, editable_settings: &ReadExpect<'_, EditableSettings>, alias_validator: &ReadExpect<'_, AliasValidator>, msg: ClientGeneral, ) -> Result<(), crate::error::Error> { match msg { // Request spectator state - ClientGeneral::Spectate if client.registered => { - client.in_game = Some(ClientInGame::Spectator) + ClientGeneral::Spectate => { + if players.contains(entity) { + warn!("Spectator mode not yet implemented on server"); + } else { + debug!("dropped Spectate msg from unregistered client") + } }, - ClientGeneral::Spectate => debug!("dropped Spectate msg from unregistered client"), - ClientGeneral::Character(character_id) - if client.registered && client.in_game.is_none() => - { + ClientGeneral::Character(character_id) => { if let Some(player) = players.get(entity) { - // Send a request to load the character's component data from the - // DB. Once loaded, persisted components such as stats and inventory - // will be inserted for the entity - character_loader.load_character_data( - entity, - player.uuid().to_string(), - character_id, - ); + if presences.contains(entity) { + debug!("player already ingame, aborting"); + } else { + // Send a request to load the character's component data from the + // DB. Once loaded, persisted components such as stats and inventory + // will be inserted for the entity + character_loader.load_character_data( + entity, + player.uuid().to_string(), + character_id, + ); - // Start inserting non-persisted/default components for the entity - // while we load the DB data - server_emitter.emit(ServerEvent::InitCharacterData { - entity, - character_id, - }); + // Start inserting non-persisted/default components for the entity + // while we load the DB data + server_emitter.emit(ServerEvent::InitCharacterData { + entity, + character_id, + }); - // Give the player a welcome message - if !editable_settings.server_description.is_empty() { - general_stream - .send(ChatType::CommandInfo.server_msg(String::from( + // Give the player a welcome message + if !editable_settings.server_description.is_empty() { + general_stream.send(ChatType::CommandInfo.server_msg(String::from( &*editable_settings.server_description, )))?; - } + } - if !client.login_msg_sent { - if let Some(player_uid) = uids.get(entity) { - new_chat_msgs.push((None, UnresolvedChatMsg { - chat_type: ChatType::Online(*player_uid), - message: "".to_string(), - })); + if !client.login_msg_sent { + if let Some(player_uid) = uids.get(entity) { + new_chat_msgs.push((None, UnresolvedChatMsg { + chat_type: ChatType::Online(*player_uid), + message: "".to_string(), + })); - client.login_msg_sent = true; + client.login_msg_sent = true; + } } } } else { + debug!("Client is not yet registered"); character_screen_stream.send(ServerGeneral::CharacterDataLoadError( String::from("Failed to fetch player entity"), ))? } - } - ClientGeneral::Character(_) => { - let registered = client.registered; - let in_game = client.in_game; - debug!(?registered, ?in_game, "dropped Character msg from client"); }, ClientGeneral::RequestCharacterList => { if let Some(player) = players.get(entity) { @@ -138,6 +140,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Uid>, WriteStorage<'a, Client>, ReadStorage<'a, Player>, + ReadStorage<'a, Presence>, WriteStorage<'a, CharacterScreenStream>, WriteStorage<'a, GeneralStream>, ReadExpect<'a, EditableSettings>, @@ -155,6 +158,7 @@ impl<'a> System<'a> for Sys { uids, mut clients, players, + presences, mut character_screen_streams, mut general_streams, editable_settings, @@ -187,6 +191,7 @@ impl<'a> System<'a> for Sys { &character_loader, &uids, &players, + &presences, &editable_settings, &alias_validator, msg, diff --git a/server/src/sys/msg/general.rs b/server/src/sys/msg/general.rs index 6900a63c19..dc58478cdb 100644 --- a/server/src/sys/msg/general.rs +++ b/server/src/sys/msg/general.rs @@ -1,7 +1,7 @@ use super::super::SysTimer; use crate::{client::Client, metrics::PlayerMetrics, streams::GeneralStream}; use common::{ - comp::{ChatMode, UnresolvedChatMsg}, + comp::{ChatMode, Player, UnresolvedChatMsg}, event::{EventBus, ServerEvent}, msg::{validate_chat_msg, ChatMsgValidationError, ClientGeneral, MAX_BYTES_CHAT_MSG}, span, @@ -18,6 +18,7 @@ impl Sys { new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, entity: specs::Entity, client: &mut Client, + player: Option<&Player>, player_metrics: &ReadExpect<'_, PlayerMetrics>, uids: &ReadStorage<'_, Uid>, chat_modes: &ReadStorage<'_, ChatMode>, @@ -25,7 +26,7 @@ impl Sys { ) -> Result<(), crate::error::Error> { match msg { ClientGeneral::ChatMsg(message) => { - if client.registered { + if player.is_some() { match validate_chat_msg(&message) { Ok(()) => { if let Some(from) = uids.get(entity) { @@ -71,6 +72,7 @@ impl<'a> System<'a> for Sys { Write<'a, SysTimer>, ReadStorage<'a, Uid>, ReadStorage<'a, ChatMode>, + ReadStorage<'a, Player>, WriteStorage<'a, Client>, WriteStorage<'a, GeneralStream>, ); @@ -85,6 +87,7 @@ impl<'a> System<'a> for Sys { mut timer, uids, chat_modes, + players, mut clients, mut general_streams, ): Self::SystemData, @@ -95,8 +98,13 @@ impl<'a> System<'a> for Sys { let mut server_emitter = server_event_bus.emitter(); let mut new_chat_msgs = Vec::new(); - for (entity, client, general_stream) in - (&entities, &mut clients, &mut general_streams).join() + for (entity, client, player, general_stream) in ( + &entities, + &mut clients, + (&players).maybe(), + &mut general_streams, + ) + .join() { let res = super::try_recv_all(general_stream, |_, msg| { Self::handle_general_msg( @@ -104,6 +112,7 @@ impl<'a> System<'a> for Sys { &mut new_chat_msgs, entity, client, + player, &player_metrics, &uids, &chat_modes, diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index 0b8ffa8e46..8d3ddd0248 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -2,13 +2,14 @@ use super::super::SysTimer; use crate::{ client::Client, metrics::NetworkRequestMetrics, + presence::Presence, streams::{GetStream, InGameStream}, Settings, }; use common::{ - comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel}, + comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Pos, Stats, Vel}, event::{EventBus, ServerEvent}, - msg::{ClientGeneral, ClientInGame, ServerGeneral}, + msg::{ClientGeneral, PresenceKind, ServerGeneral}, span, state::{BlockChange, Time}, terrain::{TerrainChunkSize, TerrainGrid}, @@ -22,7 +23,8 @@ impl Sys { fn handle_client_in_game_msg( server_emitter: &mut common::event::Emitter<'_, ServerEvent>, entity: specs::Entity, - client: &mut Client, + _client: &Client, + maybe_presence: &mut Option<&mut Presence>, in_game_stream: &mut InGameStream, terrain: &ReadExpect<'_, TerrainGrid>, network_metrics: &ReadExpect<'_, NetworkRequestMetrics>, @@ -33,35 +35,33 @@ impl Sys { positions: &mut WriteStorage<'_, Pos>, velocities: &mut WriteStorage<'_, Vel>, orientations: &mut WriteStorage<'_, Ori>, - players: &mut WriteStorage<'_, Player>, controllers: &mut WriteStorage<'_, Controller>, settings: &Read<'_, Settings>, msg: ClientGeneral, ) -> Result<(), crate::error::Error> { - if client.in_game.is_none() { - debug!(?entity, "client is not in_game, ignoring msg"); - trace!(?msg, "ignored msg content"); - if matches!(msg, ClientGeneral::TerrainChunkRequest{ .. }) { - network_metrics.chunks_request_dropped.inc(); - } - return Ok(()); - } + let presence = match maybe_presence { + Some(g) => g, + None => { + debug!(?entity, "client is not in_game, ignoring msg"); + trace!(?msg, "ignored msg content"); + if matches!(msg, ClientGeneral::TerrainChunkRequest{ .. }) { + network_metrics.chunks_request_dropped.inc(); + } + return Ok(()); + }, + }; match msg { // Go back to registered state (char selection screen) ClientGeneral::ExitInGame => { - client.in_game = None; server_emitter.emit(ServerEvent::ExitIngame { entity }); in_game_stream.send(ServerGeneral::ExitInGameSuccess)?; + *maybe_presence = None; }, ClientGeneral::SetViewDistance(view_distance) => { - players.get_mut(entity).map(|player| { - player.view_distance = Some( - settings - .max_view_distance - .map(|max| view_distance.min(max)) - .unwrap_or(view_distance), - ) - }); + presence.view_distance = settings + .max_view_distance + .map(|max| view_distance.min(max)) + .unwrap_or(view_distance); //correct client if its VD is to high if settings @@ -75,14 +75,14 @@ impl Sys { } }, ClientGeneral::ControllerInputs(inputs) => { - if let Some(ClientInGame::Character) = client.in_game { + if matches!(presence.kind, PresenceKind::Character(_)) { if let Some(controller) = controllers.get_mut(entity) { controller.inputs.update_with_new(inputs); } } }, ClientGeneral::ControlEvent(event) => { - if let Some(ClientInGame::Character) = client.in_game { + if matches!(presence.kind, PresenceKind::Character(_)) { // Skip respawn if client entity is alive if let ControlEvent::Respawn = event { if stats.get(entity).map_or(true, |s| !s.is_dead) { @@ -96,21 +96,20 @@ impl Sys { } }, ClientGeneral::ControlAction(event) => { - if let Some(ClientInGame::Character) = client.in_game { + if matches!(presence.kind, PresenceKind::Character(_)) { if let Some(controller) = controllers.get_mut(entity) { controller.actions.push(event); } } }, ClientGeneral::PlayerPhysics { pos, vel, ori } => { - if let Some(ClientInGame::Character) = client.in_game { - if force_updates.get(entity).is_none() - && stats.get(entity).map_or(true, |s| !s.is_dead) - { - let _ = positions.insert(entity, pos); - let _ = velocities.insert(entity, vel); - let _ = orientations.insert(entity, ori); - } + if matches!(presence.kind, PresenceKind::Character(_)) + && force_updates.get(entity).is_none() + && stats.get(entity).map_or(true, |s| !s.is_dead) + { + let _ = positions.insert(entity, pos); + let _ = velocities.insert(entity, vel); + let _ = orientations.insert(entity, ori); } }, ClientGeneral::BreakBlock(pos) => { @@ -124,13 +123,10 @@ impl Sys { } }, ClientGeneral::TerrainChunkRequest { key } => { - let in_vd = if let (Some(view_distance), Some(pos)) = ( - players.get(entity).and_then(|p| p.view_distance), - positions.get(entity), - ) { + let in_vd = if let Some(pos) = positions.get(entity) { pos.0.xy().map(|e| e as f64).distance( key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64), - ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt()) + ) < (presence.view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt()) * TerrainChunkSize::RECT_SIZE.x as f64 } else { true @@ -192,7 +188,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, - WriteStorage<'a, Player>, + WriteStorage<'a, Presence>, WriteStorage<'a, Client>, WriteStorage<'a, InGameStream>, WriteStorage<'a, Controller>, @@ -215,7 +211,7 @@ impl<'a> System<'a> for Sys { mut positions, mut velocities, mut orientations, - mut players, + mut presences, mut clients, mut in_game_streams, mut controllers, @@ -227,14 +223,20 @@ impl<'a> System<'a> for Sys { let mut server_emitter = server_event_bus.emitter(); - for (entity, client, in_game_stream) in - (&entities, &mut clients, &mut in_game_streams).join() + for (entity, client, mut presence, in_game_stream) in ( + &entities, + &mut clients, + (&mut presences).maybe(), + &mut in_game_streams, + ) + .join() { let res = super::try_recv_all(in_game_stream, |in_game_stream, msg| { Self::handle_client_in_game_msg( &mut server_emitter, entity, client, + &mut presence, in_game_stream, &terrain, &network_metrics, @@ -245,7 +247,6 @@ impl<'a> System<'a> for Sys { &mut positions, &mut velocities, &mut orientations, - &mut players, &mut controllers, &settings, msg, diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index 6f3fc31641..219b3b81e9 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -27,7 +27,7 @@ impl Sys { player_list: &HashMap, new_players: &mut Vec, entity: specs::Entity, - client: &mut Client, + _client: &mut Client, register_stream: &mut RegisterStream, general_stream: &mut GeneralStream, player_metrics: &ReadExpect<'_, PlayerMetrics>, @@ -50,8 +50,7 @@ impl Sys { Ok((username, uuid)) => (username, uuid), }; - const INITIAL_VD: Option = Some(5); //will be changed after login - let player = Player::new(username, None, INITIAL_VD, uuid); + let player = Player::new(username, uuid); let is_admin = editable_settings.admins.contains(&uuid); if !player.is_valid() { @@ -60,7 +59,7 @@ impl Sys { return Ok(()); } - if !client.registered && client.in_game.is_none() { + if !players.contains(entity) { // Add Player component to this client let _ = players.insert(entity, player); player_metrics.players_connected.inc(); @@ -72,7 +71,6 @@ impl Sys { } // Tell the client its request was successful. - client.registered = true; register_stream.send(ServerRegisterAnswer::Ok(()))?; // Send initial player list @@ -83,6 +81,7 @@ impl Sys { // Add to list to notify all clients of the new player new_players.push(entity); } + Ok(()) } } @@ -182,10 +181,7 @@ impl<'a> System<'a> for Sys { for entity in new_players { if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) { let mut lazy_msg = None; - for (_, general_stream) in (&mut clients, &mut general_streams) - .join() - .filter(|(c, _)| c.registered) - { + for (_, general_stream) in (&players, &mut general_streams).join() { if lazy_msg.is_none() { lazy_msg = Some(general_stream.prepare(&ServerGeneral::PlayerListUpdate( PlayerListUpdate::Add(*uid, PlayerInfo { diff --git a/server/src/sys/persistence.rs b/server/src/sys/persistence.rs index 91bc9c4fe4..60c578d7d8 100644 --- a/server/src/sys/persistence.rs +++ b/server/src/sys/persistence.rs @@ -1,9 +1,11 @@ use crate::{ persistence::character_updater, + presence::Presence, sys::{SysScheduler, SysTimer}, }; use common::{ - comp::{Inventory, Loadout, Player, Stats}, + comp::{Inventory, Loadout, Stats}, + msg::PresenceKind, span, }; use specs::{Join, ReadExpect, ReadStorage, System, Write}; @@ -13,7 +15,7 @@ pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] // TODO: Pending review in #587 type SystemData = ( - ReadStorage<'a, Player>, + ReadStorage<'a, Presence>, ReadStorage<'a, Stats>, ReadStorage<'a, Inventory>, ReadStorage<'a, Loadout>, @@ -25,7 +27,7 @@ impl<'a> System<'a> for Sys { fn run( &mut self, ( - players, + presences, player_stats, player_inventories, player_loadouts, @@ -39,17 +41,18 @@ impl<'a> System<'a> for Sys { timer.start(); updater.batch_update( ( - &players, + &presences, &player_stats, &player_inventories, &player_loadouts, ) .join() - .filter_map(|(player, stats, inventory, loadout)| { - player - .character_id - .map(|id| (id, stats, inventory, loadout)) - }), + .filter_map( + |(presence, stats, inventory, loadout)| match presence.kind { + PresenceKind::Character(id) => Some((id, stats, inventory, loadout)), + PresenceKind::Spectator => None, + }, + ), ); timer.end(); } diff --git a/server/src/sys/subscription.rs b/server/src/sys/subscription.rs index 45327314b5..66d9992e03 100644 --- a/server/src/sys/subscription.rs +++ b/server/src/sys/subscription.rs @@ -3,11 +3,12 @@ use super::{ SysTimer, }; use crate::{ - client::{self, Client, RegionSubscription}, + client::Client, + presence::{self, Presence, RegionSubscription}, streams::{GeneralStream, GetStream}, }; use common::{ - comp::{Ori, Player, Pos, Vel}, + comp::{Ori, Pos, Vel}, msg::ServerGeneral, region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap}, span, @@ -34,7 +35,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Pos>, ReadStorage<'a, Vel>, ReadStorage<'a, Ori>, - ReadStorage<'a, Player>, + ReadStorage<'a, Presence>, ReadStorage<'a, Client>, WriteStorage<'a, GeneralStream>, WriteStorage<'a, RegionSubscription>, @@ -53,8 +54,8 @@ impl<'a> System<'a> for Sys { positions, velocities, orientations, - players, - clients, + presences, + _clients, mut general_streams, mut subscriptions, mut deleted_entities, @@ -76,23 +77,16 @@ impl<'a> System<'a> for Sys { // 7. Determine list of regions that are in range and iterate through it // - check if in hashset (hash calc) if not add it let mut regions_to_remove = Vec::new(); - for (_, subscription, pos, vd, client_entity, general_stream) in ( - &clients, + for (subscription, pos, presence, client_entity, general_stream) in ( &mut subscriptions, &positions, - &players, + &presences, &entities, &mut general_streams, ) .join() - .filter_map(|(client, s, pos, player, e, stream)| { - if client.in_game.is_some() { - player.view_distance.map(|v| (client, s, pos, v, e, stream)) - } else { - None - } - }) { + let vd = presence.view_distance; // Calculate current chunk let chunk = (Vec2::::from(pos.0)) .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32); @@ -107,7 +101,7 @@ impl<'a> System<'a> for Sys { }) - Vec2::from(pos.0)) .map2(TerrainChunkSize::RECT_SIZE, |e, sz| { - e.abs() > (sz / 2 + client::CHUNK_FUZZ) as f32 + e.abs() > (sz / 2 + presence::CHUNK_FUZZ) as f32 }) .reduce_or() { @@ -123,7 +117,9 @@ impl<'a> System<'a> for Sys { *key, pos.0, (vd as f32 * chunk_size) - + (client::CHUNK_FUZZ as f32 + client::REGION_FUZZ as f32 + chunk_size) + + (presence::CHUNK_FUZZ as f32 + + presence::REGION_FUZZ as f32 + + chunk_size) * 2.0f32.sqrt(), ) { // Add to the list of regions to remove @@ -185,7 +181,7 @@ impl<'a> System<'a> for Sys { for key in regions_in_vd( pos.0, (vd as f32 * chunk_size) - + (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(), + + (presence::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(), ) { // Send client initial info about the entities in this region if it was not // already within the set of subscribed regions @@ -224,13 +220,9 @@ impl<'a> System<'a> for Sys { /// Initialize region subscription pub fn initialize_region_subscription(world: &World, entity: specs::Entity) { - if let (Some(client_pos), Some(client_vd), Some(general_stream)) = ( + if let (Some(client_pos), Some(presence), Some(general_stream)) = ( world.read_storage::().get(entity), - world - .read_storage::() - .get(entity) - .map(|pl| pl.view_distance) - .and_then(|v| v), + world.read_storage::().get(entity), world.write_storage::().get_mut(entity), ) { let fuzzy_chunk = (Vec2::::from(client_pos.0)) @@ -238,8 +230,8 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) { let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32; let regions = common::region::regions_in_vd( client_pos.0, - (client_vd as f32 * chunk_size) as f32 - + (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(), + (presence.view_distance as f32 * chunk_size) as f32 + + (presence::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(), ); let region_map = world.read_resource::(); diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index cd2826541c..5b169ab18a 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -1,11 +1,12 @@ use super::SysTimer; use crate::{ chunk_generator::ChunkGenerator, + presence::Presence, streams::{GetStream, InGameStream}, Tick, }; use common::{ - comp::{self, bird_medium, Alignment, Player, Pos}, + comp::{self, bird_medium, Alignment, Pos}, event::{EventBus, ServerEvent}, generation::get_npc_name, msg::ServerGeneral, @@ -37,7 +38,7 @@ impl<'a> System<'a> for Sys { WriteExpect<'a, TerrainGrid>, Write<'a, TerrainChanges>, ReadStorage<'a, Pos>, - ReadStorage<'a, Player>, + ReadStorage<'a, Presence>, WriteStorage<'a, InGameStream>, ); @@ -51,7 +52,7 @@ impl<'a> System<'a> for Sys { mut terrain, mut terrain_changes, positions, - players, + presences, mut in_game_streams, ): Self::SystemData, ) { @@ -79,11 +80,8 @@ impl<'a> System<'a> for Sys { }, }; // Send the chunk to all nearby players. - for (view_distance, pos, in_game_stream) in (&players, &positions, &mut in_game_streams) - .join() - .filter_map(|(player, pos, in_game_stream)| { - player.view_distance.map(|vd| (vd, pos, in_game_stream)) - }) + for (presence, pos, in_game_stream) in + (&presences, &positions, &mut in_game_streams).join() { let chunk_pos = terrain.pos_key(pos.0.map(|e| e as i32)); // Subtract 2 from the offset before computing squared magnitude @@ -93,7 +91,7 @@ impl<'a> System<'a> for Sys { .map(|e: i32| (e.abs() as u32).saturating_sub(2)) .magnitude_squared(); - if adjusted_dist_sqr <= view_distance.pow(2) { + if adjusted_dist_sqr <= presence.view_distance.pow(2) { in_game_stream.send_fallible(ServerGeneral::TerrainChunkUpdate { key, chunk: Ok(Box::new(chunk.clone())), @@ -210,12 +208,8 @@ impl<'a> System<'a> for Sys { let mut should_drop = true; // For each player with a position, calculate the distance. - for (player, pos) in (&players, &positions).join() { - if player - .view_distance - .map(|vd| chunk_in_vd(pos.0, chunk_key, &terrain, vd)) - .unwrap_or(false) - { + for (presence, pos) in (&presences, &positions).join() { + if chunk_in_vd(pos.0, chunk_key, &terrain, presence.view_distance) { should_drop = false; break; } diff --git a/server/src/sys/terrain_sync.rs b/server/src/sys/terrain_sync.rs index 0942261f5a..a04c55fd25 100644 --- a/server/src/sys/terrain_sync.rs +++ b/server/src/sys/terrain_sync.rs @@ -1,12 +1,9 @@ use super::SysTimer; -use crate::streams::{GetStream, InGameStream}; -use common::{ - comp::{Player, Pos}, - msg::ServerGeneral, - span, - state::TerrainChanges, - terrain::TerrainGrid, +use crate::{ + presence::Presence, + streams::{GetStream, InGameStream}, }; +use common::{comp::Pos, msg::ServerGeneral, span, state::TerrainChanges, terrain::TerrainGrid}; use specs::{Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage}; /// This systems sends new chunks to clients as well as changes to existing @@ -19,13 +16,13 @@ impl<'a> System<'a> for Sys { Read<'a, TerrainChanges>, Write<'a, SysTimer>, ReadStorage<'a, Pos>, - ReadStorage<'a, Player>, + ReadStorage<'a, Presence>, WriteStorage<'a, InGameStream>, ); fn run( &mut self, - (terrain, terrain_changes, mut timer, positions, players, mut in_game_streams): Self::SystemData, + (terrain, terrain_changes, mut timer, positions, presences, mut in_game_streams): Self::SystemData, ) { span!(_guard, "run", "terrain_sync::Sys::run"); timer.start(); @@ -34,12 +31,10 @@ impl<'a> System<'a> for Sys { 'chunk: for chunk_key in &terrain_changes.modified_chunks { let mut lazy_msg = None; - for (player, pos, in_game_stream) in (&players, &positions, &mut in_game_streams).join() + for (presence, pos, in_game_stream) in + (&presences, &positions, &mut in_game_streams).join() { - if player - .view_distance - .map(|vd| super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, vd)) - .unwrap_or(false) + if super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, presence.view_distance) { if lazy_msg.is_none() { lazy_msg = @@ -61,17 +56,15 @@ impl<'a> System<'a> for Sys { // TODO: Don't send all changed blocks to all clients // Sync changed blocks let mut lazy_msg = None; - for (player, in_game_stream) in (&players, &mut in_game_streams).join() { + for (_, in_game_stream) in (&presences, &mut in_game_streams).join() { if lazy_msg.is_none() { lazy_msg = Some(in_game_stream.prepare(&ServerGeneral::TerrainBlockUpdates( terrain_changes.modified_blocks.clone(), ))); } - if player.view_distance.is_some() { - lazy_msg - .as_ref() - .map(|ref msg| in_game_stream.0.send_raw(&msg)); - } + lazy_msg + .as_ref() + .map(|ref msg| in_game_stream.0.send_raw(&msg)); } timer.end(); diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index a0496cadf5..1022a935dd 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -64,6 +64,7 @@ use common::{ item::{ItemDesc, Quality}, BuffKind, }, + msg::PresenceKind, span, sync::Uid, terrain::TerrainChunk, @@ -658,7 +659,12 @@ impl Hud { let server = &client.server_info.name; // Get the id, unwrap is safe because this CANNOT be None at this // point. - let character_id = client.active_character_id.unwrap(); + + let character_id = match client.presence().unwrap() { + PresenceKind::Character(id) => id, + PresenceKind::Spectator => unreachable!("HUD creation in Spectator mode!"), + }; + // Create a new HotbarState from the persisted slots. let hotbar_state = HotbarState::new(global_state.profile.get_hotbar_slots(server, character_id)); diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index fe80710b07..cd3adc0229 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -61,11 +61,11 @@ impl PlayState for CharSelectionState { fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { span!(_guard, "tick", "::tick"); - let (client_in_game, client_registered) = { + let (client_presence, client_registered) = { let client = self.client.borrow(); - (client.in_game(), client.registered()) + (client.presence(), client.registered()) }; - if client_in_game.is_none() && client_registered { + if client_presence.is_none() && client_registered { // Handle window events for event in events { if self.char_selection_ui.handle_event(event.clone()) { diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 052f0e5127..b51a178346 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -18,6 +18,7 @@ use common::{ comp::{ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel}, consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE}, event::EventBus, + msg::PresenceKind, outcome::Outcome, span, terrain::{Block, BlockKind}, @@ -211,11 +212,11 @@ impl PlayState for SessionState { )); // TODO: can this be a method on the session or are there borrowcheck issues? - let (client_in_game, client_registered) = { + let (client_presence, client_registered) = { let client = self.client.borrow(); - (client.in_game(), client.registered()) + (client.presence(), client.registered()) }; - if client_in_game.is_some() { + if client_presence.is_some() { // Update MyEntity // Note: Alternatively, the client could emit an event when the entity changes // which may or may not be more elegant @@ -927,7 +928,12 @@ impl PlayState for SessionState { let server = &client.server_info.name; // If we are changing the hotbar state this CANNOT be None. - let character_id = client.active_character_id.unwrap(); + let character_id = match client.presence().unwrap() { + PresenceKind::Character(id) => id, + PresenceKind::Spectator => { + unreachable!("HUD adaption in Spectator mode!") + }, + }; // Get or update the ServerProfile. global_state @@ -1080,7 +1086,7 @@ impl PlayState for SessionState { self.cleanup(); PlayStateResult::Continue - } else if client_registered && client_in_game.is_none() { + } else if client_registered && client_presence.is_none() { PlayStateResult::Switch(Box::new(CharSelectionState::new( global_state, Rc::clone(&self.client),