extract a Presence Component, which is server only and has state of Player and Client. Presence is only valid for Clients that are in game

This commit is contained in:
Marcel Märtens 2020-10-30 17:39:53 +01:00
parent 084b60d7ec
commit 00456c8373
27 changed files with 328 additions and 321 deletions

View File

@ -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<ClientInGame>,
presence: Option<PresenceKind>,
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<DynamicImage>, Vec2<u16>, Vec2<f32>),
pub player_list: HashMap<Uid, PlayerInfo>,
pub character_list: CharacterList,
pub active_character_id: Option<CharacterId>,
recipe_book: RecipeBook,
available_recipes: HashSet<String>,
@ -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<Uid> { self.state.read_component_copied(self.entity) }
pub fn in_game(&self) -> Option<ClientInGame> { self.in_game }
pub fn presence(&self) -> Option<PresenceKind> { self.presence }
pub fn registered(&self) -> bool { self.registered }

View File

@ -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<CharacterId>,
pub view_distance: Option<u32>,
uuid: Uuid,
}
impl Player {
pub fn new(
alias: String,
character_id: Option<CharacterId>,
view_distance: Option<u32>,
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) }

View File

@ -86,21 +86,21 @@ impl ClientMsg {
&self,
c_type: ClientType,
registered: bool,
in_game: Option<super::ClientInGame>,
presence: Option<super::PresenceKind>,
) -> 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,

View File

@ -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)]

View File

@ -182,11 +182,11 @@ impl ServerMsg {
&self,
c_type: ClientType,
registered: bool,
in_game: Option<super::ClientInGame>,
presence: Option<super::PresenceKind>,
) -> 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(_)

View File

@ -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

View File

@ -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<ClientInGame>,
pub participant: Option<Participant>,
pub last_ping: f64,
pub login_msg_sent: bool,
@ -16,20 +18,5 @@ pub struct Client {
}
impl Component for Client {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
}
// 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<i32>,
pub regions: HashSet<Vec2<i32>>,
}
impl Component for RegionSubscription {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
type Storage = IdvStorage<Self>;
}

View File

@ -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::<comp::Body>().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::<Uid>()
.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

View File

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

View File

@ -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) {

View File

@ -1,5 +1,6 @@
use crate::{
client::{Client, RegionSubscription},
client::Client,
presence::RegionSubscription,
streams::{
CharacterScreenStream, GeneralStream, GetStream, InGameStream, PingStream, RegisterStream,
},

View File

@ -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::<Uid>().get(entity),
state.read_storage::<comp::Player>().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::<Player>().get(entity),
if let (Some(presences), Some(stats), Some(inventory), Some(loadout), updater) = (
state.read_storage::<Presence>().get(entity),
state.read_storage::<comp::Stats>().get(entity),
state.read_storage::<comp::Inventory>().get(entity),
state.read_storage::<comp::Loadout>().get(entity),
@ -186,7 +186,7 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event
.ecs()
.read_resource::<persistence::character_updater::CharacterUpdater>(),
) {
if let Some(character_id) = player.character_id {
if let PresenceKind::Character(character_id) = presences.kind {
updater.update(character_id, stats, inventory, loadout);
}
}

View File

@ -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::<RegionSubscription>();
state.ecs_mut().register::<Client>();
state.ecs_mut().register::<Presence>();
state.ecs_mut().register::<GeneralStream>();
state.ecs_mut().register::<PingStream>();
state.ecs_mut().register::<RegisterStream>();
@ -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<i32>) {
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));
}
}

40
server/src/presence.rs Normal file
View File

@ -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<Self, IdvStorage<Self>>;
}
// 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<i32>,
pub regions: HashSet<Vec2<i32>>,
}
impl Component for RegionSubscription {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
}

View File

@ -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::<comp::Player>()
.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::<Client>().get_mut(entity) {
if let Some(character_screen_stream) = self
.ecs()
.write_storage::<CharacterScreenStream>()
.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::<CharacterScreenStream>()
.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::<Uid>(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::<GeneralStream>(),
@ -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::<GeneralStream>(),
&self.ecs().read_storage::<Client>(),
&self.ecs().read_storage::<comp::Player>(),
)
.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::<GeneralStream>(),
&self.ecs().read_storage::<Client>(),
&self.ecs().read_storage::<Presence>(),
)
.join()
.filter(|(_, c)| c.in_game.is_some())
{
if lazy_msg.is_none() {
lazy_msg = Some(general_stream.prepare(&msg));

View File

@ -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<Pos>>,
WriteStorage<'a, Last<Vel>>,
WriteStorage<'a, Last<Ori>>,
@ -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(&region_key) {
.filter_map(|(presence, subscription, general_stream)| {
if presence.is_some() && subscription.regions.contains(&region_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<f32>| {
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)
})
};

View File

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

View File

@ -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<specs::Entity>, 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<Self>>,
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,

View File

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

View File

@ -27,7 +27,7 @@ impl Sys {
player_list: &HashMap<Uid, PlayerInfo>,
new_players: &mut Vec<specs::Entity>,
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<u32> = 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 {

View File

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

View File

@ -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::<f32>::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::<Pos>().get(entity),
world
.read_storage::<Player>()
.get(entity)
.map(|pl| pl.view_distance)
.and_then(|v| v),
world.read_storage::<Presence>().get(entity),
world.write_storage::<GeneralStream>().get_mut(entity),
) {
let fuzzy_chunk = (Vec2::<f32>::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::<RegionMap>();

View File

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

View File

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

View File

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

View File

@ -61,11 +61,11 @@ impl PlayState for CharSelectionState {
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<WinEvent>) -> PlayStateResult {
span!(_guard, "tick", "<CharSelectionState as PlayState>::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()) {

View File

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