diff --git a/client/src/lib.rs b/client/src/lib.rs index 14d051a333..8efadb48a4 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -25,11 +25,11 @@ use common::{ }, event::{EventBus, LocalEvent}, msg::{ - validate_chat_msg, ChatMsgValidationError, ClientCharacterScreenMsg, ClientGeneralMsg, - ClientInGameMsg, ClientIngame, ClientRegisterMsg, ClientType, DisconnectReason, + validate_chat_msg, ChatMsgValidationError, ClientCharacterScreen, ClientGeneral, + ClientInGame, ClientIngame, ClientMsg, ClientRegister, ClientType, DisconnectReason, InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, - ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg, - ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, + ServerCharacterScreen, ServerGeneral, ServerInGame, ServerInfo, ServerInit, + ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, }, outcome::Outcome, recipe::RecipeBook, @@ -71,7 +71,7 @@ pub enum Event { pub struct Client { registered: bool, - client_ingame: Option, + in_game: Option, thread_pool: ThreadPool, pub server_info: ServerInfo, /// Just the "base" layer for LOD; currently includes colors and nothing @@ -193,7 +193,7 @@ impl Client { max_group_size, client_timeout, ) = match block_on(register_stream.recv())? { - ServerInitMsg::GameSync { + ServerInit::GameSync { entity_package, time_of_day, max_group_size, @@ -362,7 +362,7 @@ impl Client { client_timeout, )) }, - ServerInitMsg::TooManyPlayers => Err(Error::TooManyPlayers), + ServerInit::TooManyPlayers => Err(Error::TooManyPlayers), }?; ping_stream.send(PingMsg::Ping)?; @@ -376,7 +376,7 @@ impl Client { Ok(Self { registered: false, - client_ingame: None, + in_game: None, thread_pool, server_info, world_map, @@ -444,10 +444,9 @@ impl Client { } ).unwrap_or(Ok(username))?; - self.register_stream - .send(ClientRegisterMsg { token_or_username })?; + self.send_msg_err(ClientRegister { token_or_username })?; - match block_on(self.register_stream.recv::())? { + match block_on(self.register_stream.recv::())? { Err(RegisterError::AlreadyLoggedIn) => Err(Error::AlreadyLoggedIn), Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)), Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter), @@ -460,14 +459,69 @@ impl Client { } } + fn send_msg_err(&mut self, msg: S) -> Result<(), network::StreamError> + where + S: Into, + { + let msg: ClientMsg = msg.into(); + #[cfg(debug_assertions)] + { + //There assertions veriy that the state is correct when a msg is send! + match &msg { + ClientMsg::Type(_) | ClientMsg::Register(_) => assert!( + !self.registered, + "must not send msg when already registered" + ), + ClientMsg::CharacterScreen(_) => { + assert!( + self.registered, + "must not send character_screen msg when not registered" + ); + assert!( + self.in_game.is_none(), + "must not send character_screen msg when not in character screen" + ); + }, + ClientMsg::InGame(_) => assert!( + self.in_game.is_some(), + "must not send in_game msg when not in game" + ), + ClientMsg::General(_) => assert!( + self.registered, + "must not send general msg when not registered" + ), + ClientMsg::Ping(_) => (), + } + } + match msg { + ClientMsg::Type(msg) => self.register_stream.send(msg), + ClientMsg::Register(msg) => self.register_stream.send(msg), + ClientMsg::CharacterScreen(msg) => self.character_screen_stream.send(msg), + ClientMsg::InGame(msg) => self.in_game_stream.send(msg), + ClientMsg::General(msg) => self.general_stream.send(msg), + ClientMsg::Ping(msg) => self.ping_stream.send(msg), + } + } + + fn send_msg(&mut self, msg: S) + where + S: Into, + { + let res = self.send_msg_err(msg); + if let Err(e) = res { + warn!( + ?e, + "connection to server no longer possible, couldn't send msg" + ); + } + } + /// Request a state transition to `ClientState::Character`. pub fn request_character(&mut self, character_id: CharacterId) { - self.character_screen_stream - .send(ClientCharacterScreenMsg::Character(character_id)) - .unwrap(); + self.send_msg(ClientCharacterScreen::Character(character_id)); //Assume we are in_game unless server tells us otherwise - self.client_ingame = Some(ClientIngame::Character); + self.in_game = Some(ClientIngame::Character); self.active_character_id = Some(character_id); } @@ -475,87 +529,59 @@ impl Client { /// Load the current players character list pub fn load_character_list(&mut self) { self.character_list.loading = true; - self.character_screen_stream - .send(ClientCharacterScreenMsg::RequestCharacterList) - .unwrap(); + self.send_msg(ClientCharacterScreen::RequestCharacterList); } /// New character creation pub fn create_character(&mut self, alias: String, tool: Option, body: comp::Body) { self.character_list.loading = true; - self.character_screen_stream - .send(ClientCharacterScreenMsg::CreateCharacter { alias, tool, body }) - .unwrap(); + self.send_msg(ClientCharacterScreen::CreateCharacter { alias, tool, body }); } /// Character deletion pub fn delete_character(&mut self, character_id: CharacterId) { self.character_list.loading = true; - self.character_screen_stream - .send(ClientCharacterScreenMsg::DeleteCharacter(character_id)) - .unwrap(); + self.send_msg(ClientCharacterScreen::DeleteCharacter(character_id)); } /// Send disconnect message to the server pub fn request_logout(&mut self) { debug!("Requesting logout from server"); - if let Err(e) = self.general_stream.send(ClientGeneralMsg::Disconnect) { - error!( - ?e, - "Couldn't send disconnect package to server, did server close already?" - ); - } + self.send_msg(ClientGeneral::Disconnect); } /// Request a state transition to `ClientState::Registered` from an ingame /// state. - pub fn request_remove_character(&mut self) { - self.in_game_stream - .send(ClientInGameMsg::ExitInGame) - .unwrap(); - } + pub fn request_remove_character(&mut self) { self.send_msg(ClientInGame::ExitInGame); } pub fn set_view_distance(&mut self, view_distance: u32) { self.view_distance = Some(view_distance.max(1).min(65)); - self.in_game_stream - .send(ClientInGameMsg::SetViewDistance( - self.view_distance.unwrap(), - )) - .unwrap(); - // Can't fail + self.send_msg(ClientInGame::SetViewDistance(self.view_distance.unwrap())); } pub fn use_slot(&mut self, slot: comp::slot::Slot) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Use(slot), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Use(slot), + ))); } pub fn swap_slots(&mut self, a: comp::slot::Slot, b: comp::slot::Slot) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Swap(a, b), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Swap(a, b), + ))); } pub fn drop_slot(&mut self, slot: comp::slot::Slot) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Drop(slot), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Drop(slot), + ))); } pub fn pick_up(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.read_component_copied(entity) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Pickup(uid), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Pickup(uid), + ))); } } @@ -573,11 +599,9 @@ impl Client { pub fn craft_recipe(&mut self, recipe: &str) -> bool { if self.can_craft_recipe(recipe) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::CraftRecipe(recipe.to_string()), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::CraftRecipe(recipe.to_string()), + ))); true } else { false @@ -594,15 +618,11 @@ impl Client { } pub fn enable_lantern(&mut self) { - self.singleton_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::EnableLantern)) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::EnableLantern)); } pub fn disable_lantern(&mut self) { - self.singleton_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::DisableLantern)) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::DisableLantern)); } pub fn max_group_size(&self) -> u32 { self.max_group_size } @@ -620,55 +640,43 @@ impl Client { pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } pub fn send_group_invite(&mut self, invitee: Uid) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Invite(invitee), - ))) - .unwrap() + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Invite(invitee), + ))) } pub fn accept_group_invite(&mut self) { // Clear invite self.group_invite.take(); - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Accept, - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Accept, + ))); } pub fn decline_group_invite(&mut self) { // Clear invite self.group_invite.take(); - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Decline, - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Decline, + ))); } pub fn leave_group(&mut self) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Leave, - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Leave, + ))); } pub fn kick_from_group(&mut self, uid: Uid) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Kick(uid), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Kick(uid), + ))); } pub fn assign_group_leader(&mut self, uid: Uid) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::AssignLeader(uid), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::AssignLeader(uid), + ))); } pub fn is_mounted(&self) -> bool { @@ -689,17 +697,11 @@ impl Client { pub fn mount(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.read_component_copied(entity) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::Mount(uid))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::Mount(uid))); } } - pub fn unmount(&mut self) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::Unmount)) - .unwrap(); - } + pub fn unmount(&mut self) { self.send_msg(ClientInGame::ControlEvent(ControlEvent::Unmount)); } pub fn respawn(&mut self) { if self @@ -709,9 +711,7 @@ impl Client { .get(self.entity) .map_or(false, |s| s.is_dead) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::Respawn)) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::Respawn)); } } @@ -808,9 +808,7 @@ impl Client { { controller.actions.push(control_action); } - self.in_game_stream - .send(ClientInGameMsg::ControlAction(control_action)) - .unwrap(); + self.send_msg(ClientInGame::ControlAction(control_action)); } pub fn view_distance(&self) -> Option { self.view_distance } @@ -839,10 +837,7 @@ impl Client { /// Send a chat message to the server. pub fn send_chat(&mut self, message: String) { match validate_chat_msg(&message) { - Ok(()) => self - .general_stream - .send(ClientGeneralMsg::ChatMsg(message)) - .unwrap(), + Ok(()) => self.send_msg(ClientGeneral::ChatMsg(message)), Err(ChatMsgValidationError::TooLong) => tracing::warn!( "Attempted to send a message that's too long (Over {} bytes)", MAX_BYTES_CHAT_MSG @@ -857,23 +852,15 @@ impl Client { } pub fn place_block(&mut self, pos: Vec3, block: Block) { - self.in_game_stream - .send(ClientInGameMsg::PlaceBlock(pos, block)) - .unwrap(); + self.send_msg(ClientInGame::PlaceBlock(pos, block)); } - pub fn remove_block(&mut self, pos: Vec3) { - self.in_game_stream - .send(ClientInGameMsg::BreakBlock(pos)) - .unwrap(); - } + pub fn remove_block(&mut self, pos: Vec3) { self.send_msg(ClientInGame::BreakBlock(pos)); } pub fn collect_block(&mut self, pos: Vec3) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Collect(pos), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Collect(pos), + ))); } /// Execute a single client tick, handle input and update the game state by @@ -905,7 +892,7 @@ impl Client { // 1) Handle input from frontend. // Pass character actions from frontend input to the player's entity. - if self.client_ingame.is_some() { + if self.in_game.is_some() { if let Err(e) = self .state .ecs() @@ -928,8 +915,7 @@ impl Client { "Couldn't access controller component on client entity" ); } - self.in_game_stream - .send(ClientInGameMsg::ControllerInputs(inputs))?; + self.send_msg_err(ClientInGame::ControllerInputs(inputs))?; } // 2) Build up a list of events for this frame, to be passed to the frontend. @@ -1033,8 +1019,9 @@ impl Client { if self.state.terrain().get_key(*key).is_none() { if !skip_mode && !self.pending_chunks.contains_key(key) { if self.pending_chunks.len() < 4 { - self.in_game_stream - .send(ClientInGameMsg::TerrainChunkRequest { key: *key })?; + self.send_msg_err(ClientInGame::TerrainChunkRequest { + key: *key, + })?; self.pending_chunks.insert(*key, Instant::now()); } else { skip_mode = true; @@ -1066,19 +1053,19 @@ impl Client { // Send a ping to the server once every second if self.state.get_time() - self.last_server_ping > 1. { - self.ping_stream.send(PingMsg::Ping)?; + self.send_msg_err(PingMsg::Ping)?; self.last_server_ping = self.state.get_time(); } // 6) Update the server about the player's physics attributes. - if self.client_ingame.is_some() { + if self.in_game.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(), self.state.read_storage().get(self.entity).cloned(), ) { self.in_game_stream - .send(ClientInGameMsg::PlayerPhysics { pos, vel, ori })?; + .send(ClientInGame::PlayerPhysics { pos, vel, ori })?; } } @@ -1108,26 +1095,26 @@ impl Client { fn handle_server_msg( &mut self, frontend_events: &mut Vec, - msg: ServerGeneralMsg, + msg: ServerGeneral, ) -> Result<(), Error> { match msg { - ServerGeneralMsg::Disconnect(reason) => match reason { + ServerGeneral::Disconnect(reason) => match reason { DisconnectReason::Shutdown => return Err(Error::ServerShutdown), DisconnectReason::Requested => { debug!("finally sending ClientMsg::Terminate"); frontend_events.push(Event::Disconnect); - self.general_stream.send(ClientGeneralMsg::Terminate)?; + self.send_msg_err(ClientGeneral::Terminate)?; }, DisconnectReason::Kicked(reason) => { debug!("sending ClientMsg::Terminate because we got kicked"); frontend_events.push(Event::Kicked(reason)); - self.general_stream.send(ClientGeneralMsg::Terminate)?; + self.send_msg_err(ClientGeneral::Terminate)?; }, }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init(list)) => { self.player_list = list }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => { if let Some(old_player_info) = self.player_list.insert(uid, player_info.clone()) { warn!( "Received msg to insert {} with uid {} into the player list but there was \ @@ -1136,7 +1123,7 @@ impl Client { ); } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.is_admin = admin; } else { @@ -1147,7 +1134,7 @@ impl Client { ); } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter( + ServerGeneral::PlayerListUpdate(PlayerListUpdate::SelectedCharacter( uid, char_info, )) => { @@ -1161,7 +1148,7 @@ impl Client { ); } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.character = match &player_info.character { Some(character) => Some(common::msg::CharacterInfo { @@ -1180,7 +1167,7 @@ impl Client { }; } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => { // Instead of removing players, mark them as offline because we need to // remember the names of disconnected players in chat. // @@ -1205,7 +1192,7 @@ impl Client { ); } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.player_alias = new_name; } else { @@ -1216,38 +1203,38 @@ impl Client { ); } }, - ServerGeneralMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)), - ServerGeneralMsg::SetPlayerEntity(uid) => { + ServerGeneral::ChatMsg(m) => frontend_events.push(Event::Chat(m)), + ServerGeneral::SetPlayerEntity(uid) => { if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) { self.entity = entity; } else { return Err(Error::Other("Failed to find entity from uid.".to_owned())); } }, - ServerGeneralMsg::TimeOfDay(time_of_day) => { + ServerGeneral::TimeOfDay(time_of_day) => { *self.state.ecs_mut().write_resource() = time_of_day; }, - ServerGeneralMsg::EntitySync(entity_sync_package) => { + ServerGeneral::EntitySync(entity_sync_package) => { self.state .ecs_mut() .apply_entity_sync_package(entity_sync_package); }, - ServerGeneralMsg::CompSync(comp_sync_package) => { + ServerGeneral::CompSync(comp_sync_package) => { self.state .ecs_mut() .apply_comp_sync_package(comp_sync_package); }, - ServerGeneralMsg::CreateEntity(entity_package) => { + ServerGeneral::CreateEntity(entity_package) => { self.state.ecs_mut().apply_entity_package(entity_package); }, - ServerGeneralMsg::DeleteEntity(entity) => { + ServerGeneral::DeleteEntity(entity) => { if self.uid() != Some(entity) { self.state .ecs_mut() .delete_entity_and_clear_from_uid_allocator(entity.0); } }, - ServerGeneralMsg::Notification(n) => { + ServerGeneral::Notification(n) => { frontend_events.push(Event::Notification(n)); }, } @@ -1257,10 +1244,10 @@ impl Client { fn handle_server_in_game_msg( &mut self, frontend_events: &mut Vec, - msg: ServerInGameMsg, + msg: ServerInGame, ) -> Result<(), Error> { match msg { - ServerInGameMsg::GroupUpdate(change_notification) => { + ServerInGame::GroupUpdate(change_notification) => { use comp::group::ChangeNotification::*; // Note: we use a hashmap since this would not work with entities outside // the view distance @@ -1332,15 +1319,15 @@ impl Client { }, } }, - ServerInGameMsg::GroupInvite { inviter, timeout } => { + ServerInGame::GroupInvite { inviter, timeout } => { self.group_invite = Some((inviter, std::time::Instant::now(), timeout)); }, - ServerInGameMsg::InvitePending(uid) => { + ServerInGame::InvitePending(uid) => { if !self.pending_invites.insert(uid) { warn!("Received message about pending invite that was already pending"); } }, - ServerInGameMsg::InviteComplete { target, answer } => { + ServerInGame::InviteComplete { target, answer } => { if !self.pending_invites.remove(&target) { warn!( "Received completed invite message for invite that was not in the list of \ @@ -1358,11 +1345,11 @@ 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` - ServerInGameMsg::ExitInGameSuccess => { - self.client_ingame = None; + ServerInGame::ExitInGameSuccess => { + self.in_game = None; self.clean_state(); }, - ServerInGameMsg::InventoryUpdate(mut inventory, event) => { + ServerInGame::InventoryUpdate(mut inventory, event) => { match event { InventoryUpdateEvent::CollectFailed => {}, _ => { @@ -1376,25 +1363,25 @@ impl Client { frontend_events.push(Event::InventoryUpdated(event)); }, - ServerInGameMsg::TerrainChunkUpdate { key, chunk } => { + ServerInGame::TerrainChunkUpdate { key, chunk } => { if let Ok(chunk) = chunk { self.state.insert_chunk(key, *chunk); } self.pending_chunks.remove(&key); }, - ServerInGameMsg::TerrainBlockUpdates(mut blocks) => { + ServerInGame::TerrainBlockUpdates(mut blocks) => { blocks.drain().for_each(|(pos, block)| { self.state.set_block(pos, block); }); }, - ServerInGameMsg::SetViewDistance(vd) => { + ServerInGame::SetViewDistance(vd) => { self.view_distance = Some(vd); frontend_events.push(Event::SetViewDistance(vd)); }, - ServerInGameMsg::Outcomes(outcomes) => { + ServerInGame::Outcomes(outcomes) => { frontend_events.extend(outcomes.into_iter().map(Event::Outcome)) }, - ServerInGameMsg::Knockback(impulse) => { + ServerInGame::Knockback(impulse) => { self.state .ecs() .read_resource::>() @@ -1409,24 +1396,24 @@ impl Client { fn handle_server_character_screen_msg( &mut self, - msg: ServerCharacterScreenMsg, + msg: ServerCharacterScreen, ) -> Result<(), Error> { match msg { - ServerCharacterScreenMsg::CharacterListUpdate(character_list) => { + ServerCharacterScreen::CharacterListUpdate(character_list) => { self.character_list.characters = character_list; self.character_list.loading = false; }, - ServerCharacterScreenMsg::CharacterActionError(error) => { + ServerCharacterScreen::CharacterActionError(error) => { warn!("CharacterActionError: {:?}.", error); self.character_list.error = Some(error); }, - ServerCharacterScreenMsg::CharacterDataLoadError(error) => { + ServerCharacterScreen::CharacterDataLoadError(error) => { trace!("Handling join error by server"); - self.client_ingame = None; + self.in_game = None; self.clean_state(); self.character_list.error = Some(error); }, - ServerCharacterScreenMsg::CharacterSuccess => { + ServerCharacterScreen::CharacterSuccess => { debug!("client is now in ingame state on server"); if let Some(vd) = self.view_distance { self.set_view_distance(vd); @@ -1439,7 +1426,7 @@ impl Client { fn handle_ping_msg(&mut self, msg: PingMsg) -> Result<(), Error> { match msg { PingMsg::Ping => { - self.ping_stream.send(PingMsg::Pong)?; + self.send_msg_err(PingMsg::Pong)?; }, PingMsg::Pong => { self.last_server_pong = self.state.get_time(); @@ -1535,7 +1522,7 @@ impl Client { pub fn get_client_type(&self) -> ClientType { ClientType::Game } - pub fn get_in_game(&self) -> Option { self.client_ingame } + pub fn get_in_game(&self) -> Option { self.in_game } pub fn get_registered(&self) -> bool { self.registered } @@ -1810,12 +1797,16 @@ impl Client { impl Drop for Client { fn drop(&mut self) { trace!("Dropping client"); - if let Err(e) = self.general_stream.send(ClientGeneralMsg::Disconnect) { - warn!( - ?e, - "Error during drop of client, couldn't send disconnect package, is the connection \ - already closed?", - ); + if self.registered { + if let Err(e) = self.send_msg_err(ClientGeneral::Disconnect) { + warn!( + ?e, + "Error during drop of client, couldn't send disconnect package, is the \ + connection already closed?", + ); + } + } else { + trace!("no disconnect msg necessary as client wasn't registered") } if let Err(e) = block_on(self.participant.take().unwrap().disconnect()) { warn!(?e, "error when disconnecting, couldn't send all data"); diff --git a/common/src/comp/chat.rs b/common/src/comp/chat.rs index 0888a9e21b..4774b18683 100644 --- a/common/src/comp/chat.rs +++ b/common/src/comp/chat.rs @@ -1,4 +1,4 @@ -use crate::{comp::group::Group, msg::ServerGeneralMsg, sync::Uid}; +use crate::{comp::group::Group, msg::ServerGeneral, sync::Uid}; use serde::{Deserialize, Serialize}; use specs::Component; use specs_idvs::IdvStorage; @@ -118,11 +118,11 @@ impl ChatType { } } impl ChatType { - pub fn server_msg(self, msg: S) -> ServerGeneralMsg + pub fn server_msg(self, msg: S) -> ServerGeneral where S: Into, { - ServerGeneralMsg::ChatMsg(self.chat_msg(msg)) + ServerGeneral::ChatMsg(self.chat_msg(msg)) } } // Stores chat text, type diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 5e17c978ab..2339bcab12 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -1,3 +1,4 @@ +use super::PingMsg; use crate::{ character::CharacterId, comp, @@ -7,25 +8,48 @@ use crate::{ use serde::{Deserialize, Serialize}; use vek::*; +///This struct contains all messages the client might send (on different +/// streams though). It's used to verify the correctness of the state in +/// debug_assertions +#[derive(Debug, Clone)] +pub enum ClientMsg { + ///Send on the first connection ONCE to identify client intention for + /// server + Type(ClientType), + ///Send ONCE to register/auth to the server + Register(ClientRegister), + ///Msg only to send while in character screen, e.g. `CreateCharacter` + CharacterScreen(ClientCharacterScreen), + ///Msg only to send while playing in game, e.g. `PlayerPositionUpdates` + InGame(ClientInGame), + ///Msg that can be send ALWAYS as soon as we are registered, e.g. `Chat` + General(ClientGeneral), + Ping(PingMsg), +} + +/* +2nd Level Enums +*/ + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum ClientType { - // Regular Client like Voxygen who plays the game + /// Regular Client like Voxygen who plays the game Game, - // A Chatonly client, which doesn't want to connect via its character + /// A Chatonly client, which doesn't want to connect via its character ChatOnly, - // A unprivileged bot, e.g. to request world information - // Or a privileged bot, e.g. to run admin commands used by server-cli + /// A unprivileged bot, e.g. to request world information + /// Or a privileged bot, e.g. to run admin commands used by server-cli Bot { privileged: bool }, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ClientRegisterMsg { +pub struct ClientRegister { pub token_or_username: String, } //messages send by clients only valid when in character screen #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ClientCharacterScreenMsg { +pub enum ClientCharacterScreen { RequestCharacterList, CreateCharacter { alias: String, @@ -39,7 +63,7 @@ pub enum ClientCharacterScreenMsg { //messages send by clients only valid when in game (with a character) #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ClientInGameMsg { +pub enum ClientInGame { ControllerInputs(comp::ControllerInputs), ControlEvent(comp::ControlEvent), ControlAction(comp::ControlAction), @@ -62,8 +86,36 @@ pub enum ClientInGameMsg { /// Messages sent from the client to the server #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ClientGeneralMsg { +pub enum ClientGeneral { ChatMsg(String), Disconnect, Terminate, } + +/* +end of 2nd level Enums +*/ + +impl Into for ClientType { + fn into(self) -> ClientMsg { ClientMsg::Type(self) } +} + +impl Into for ClientRegister { + fn into(self) -> ClientMsg { ClientMsg::Register(self) } +} + +impl Into for ClientCharacterScreen { + fn into(self) -> ClientMsg { ClientMsg::CharacterScreen(self) } +} + +impl Into for ClientInGame { + fn into(self) -> ClientMsg { ClientMsg::InGame(self) } +} + +impl Into for ClientGeneral { + fn into(self) -> ClientMsg { ClientMsg::General(self) } +} + +impl Into for PingMsg { + fn into(self) -> ClientMsg { ClientMsg::Ping(self) } +} diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index 58367669d3..b4ea9fe6fe 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -1,18 +1,20 @@ pub mod client; pub mod ecs_packet; pub mod server; +pub mod world_packet; // Reexports pub use self::{ client::{ - ClientCharacterScreenMsg, ClientGeneralMsg, ClientInGameMsg, ClientRegisterMsg, ClientType, + ClientCharacterScreen, ClientGeneral, ClientInGame, ClientMsg, ClientRegister, ClientType, }, ecs_packet::EcsCompPacket, server::{ CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, - RegisterError, ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInfo, - ServerInitMsg, ServerRegisterAnswerMsg, + RegisterError, ServerCharacterScreen, ServerGeneral, ServerInGame, ServerInfo, ServerInit, + ServerMsg, ServerRegisterAnswer, }, + world_packet::WorldMapMsg, }; use serde::{Deserialize, Serialize}; diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 59d2b0b91d..82b861ef6e 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,4 +1,4 @@ -use super::EcsCompPacket; +use super::{EcsCompPacket, PingMsg}; use crate::{ character::CharacterItem, comp, @@ -14,6 +14,31 @@ use serde::{Deserialize, Serialize}; use std::time::Duration; use vek::*; +///This struct contains all messages the server might send (on different +/// streams though) +#[derive(Debug, Clone)] +pub enum ServerMsg { + /// Basic info about server, send ONCE, clients need it to Register + Info(ServerInfo), + /// Initial data package, send BEFORE Register ONCE. Not Register relevant + Init(ServerInit), + /// Result to `ClientMsg::Register`. send ONCE + RegisterAnswer(ServerRegisterAnswer), + /// Msg only to send when client is on the character screen, e.g. + /// `CharacterListUpdate` + CharacterScreen(ServerCharacterScreen), + /// Msg only to send when client is playing in game, e.g. + /// `TerrainChunkUpdate` + InGame(ServerInGame), + ///Msg that can be send ALWAYS as soon as client is registered, e.g. `Chat` + General(ServerGeneral), + Ping(PingMsg), +} + +/* +2nd Level Enums +*/ + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerInfo { pub name: String, @@ -23,195 +48,26 @@ pub struct ServerInfo { pub auth_provider: Option, } -/// Inform the client of updates to the player list. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum PlayerListUpdate { - Init(HashMap), - Add(Uid, PlayerInfo), - SelectedCharacter(Uid, CharacterInfo), - LevelChange(Uid, u32), - Admin(Uid, bool), - Remove(Uid), - Alias(Uid, String), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PlayerInfo { - pub is_admin: bool, - pub is_online: bool, - pub player_alias: String, - pub character: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CharacterInfo { - pub name: String, - pub level: u32, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -/// World map information. Note that currently, we always send the whole thing -/// in one go, but the structure aims to try to provide information as locally -/// as possible, so that in the future we can split up large maps into multiple -/// WorldMapMsg fragments. -/// -/// TODO: Update message format to make fragmentable, allowing us to send more -/// information without running into bandwidth issues. -/// -/// TODO: Add information for rivers (currently, we just prerender them on the -/// server, but this is not a great solution for LoD. The map rendering code is -/// already set up to be able to take advantage of the river rendering being -/// split out, but the format is a little complicated for space reasons and it -/// may take some tweaking to get right, so we avoid sending it for now). -/// -/// TODO: measure explicit compression schemes that might save space, e.g. -/// repeating the "small angles" optimization that works well on more detailed -/// shadow maps intended for height maps. -pub struct WorldMapMsg { - /// Log base 2 of world map dimensions (width × height) in chunks. - /// - /// NOTE: Invariant: chunk count fits in a u16. - pub dimensions_lg: Vec2, - /// Sea level (used to provide a base altitude). - pub sea_level: f32, - /// Max height (used to scale altitudes). - pub max_height: f32, - /// RGB+A; the alpha channel is currently unused, but will be used in the - /// future. Entries are in the usual chunk order. - pub rgba: Vec, - /// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for - /// altitude. The remainder are currently unused, but we have plans to - /// use 7 bits for water depth (using an integer f7 encoding), and we - /// will find other uses for the remaining 12 bits. - pub alt: Vec, - /// Horizon mapping. This is a variant of shadow mapping that is - /// specifically designed for height maps; it takes advantage of their - /// regular structure (e.g. no holes) to compress all information needed - /// to decide when to cast a sharp shadow into a single nagle, the "horizon - /// angle." This is the smallest angle with the ground at which light can - /// pass through any occluders to reach the chunk, in some chosen - /// horizontal direction. This would not be sufficient for a more - /// complicated 3D structure, but it works for height maps since: - /// - /// 1. they have no gaps, so as soon as light can shine through it will - /// always be able to do so, and - /// 2. we only care about lighting from the top, and only from the east and - /// west (since at a large scale like this we mostly just want to - /// handle variable sunlight; moonlight would present more challenges - /// but we currently have no plans to try to cast accurate shadows in - /// moonlight). - /// - /// Our chosen format is two pairs of vectors, - /// with the first pair representing west-facing light (casting shadows on - /// the left side) and the second representing east-facing light - /// (casting shadows on the east side). - /// - /// The pair of vectors consists of (with each vector in the usual chunk - /// order): - /// - /// * Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360). - /// We might consider switching to tangent if that represents the - /// information we care about better. - /// * Approximate (floor) height of maximal occluder. We currently use this - /// to try to deliver some approximation of soft shadows, which isn't that - /// big a deal on the world map but is probably needed in order to ensure - /// smooth transitions between chunks in LoD view. Additionally, when we - /// start using the shadow information to do local lighting on the world - /// map, we'll want a quick way to test where we can go out of shadow at - /// arbitrary heights (since the player and other entities cajn find - /// themselves far from the ground at times). While this is only an - /// approximation to a proper distance map, hopefully it will give us - /// something that feels reasonable enough for Veloren's style. - /// - /// NOTE: On compression. - /// - /// Horizon mapping has a lot of advantages for height maps (simple, easy to - /// understand, doesn't require any fancy math or approximation beyond - /// precision loss), though it loses a few of them by having to store - /// distance to occluder as well. However, just storing tons - /// and tons of regular shadow maps (153 for a full day cycle, stored at - /// irregular intervals) combined with clever explicit compression and - /// avoiding recording sharp local shadows (preferring retracing for - /// these), yielded a compression rate of under 3 bits per column! Since - /// we likely want to avoid per-column shadows for worlds of the sizes we - /// want, we'd still need to store *some* extra information to create - /// soft shadows, but it would still be nice to try to drive down our - /// size as much as possible given how compressible shadows of height - /// maps seem to be in practice. Therefore, we try to take advantage of the - /// way existing compression algorithms tend to work to see if we can - /// achieve significant gains without doing a lot of custom work. - /// - /// Specifically, since our rays are cast east/west, we expect that for each - /// row, the horizon angles in each direction should be sequences of - /// monotonically increasing values (as chunks approach a tall - /// occluder), followed by sequences of no shadow, repeated - /// until the end of the map. Monotonic sequences and same-byte sequences - /// are usually easy to compress and existing algorithms are more likely - /// to be able to deal with them than jumbled data. If we were to keep - /// both directions in the same vector, off-the-shelf compression would - /// probably be less effective. - /// - /// For related reasons, rather than storing distances as in a standard - /// distance map (which would lead to monotonically *decreasing* values - /// as we approached the occluder from a given direction), we store the - /// estimated *occluder height.* The idea here is that we replace the - /// monotonic sequences with constant sequences, which are extremely - /// straightforward to compress and mostly handled automatically by anything - /// that does run-length encoding (i.e. most off-the-shelf compression - /// algorithms). - /// - /// We still need to benchmark this properly, as there's no guarantee our - /// current compression algorithms will actually work well on this data - /// in practice. It's possible that some other permutation (e.g. more - /// bits reserved for "distance to occluder" in exchange for an even - /// more predictible sequence) would end up compressing better than storing - /// angles, or that we don't need as much precision as we currently have - /// (256 possible angles). - pub horizons: [(Vec, Vec); 2], -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum InviteAnswer { - Accepted, - Declined, - TimedOut, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum Notification { - WaypointSaved, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum DisconnectReason { - /// Server shut down - Shutdown, - /// Client sent disconnect message - Requested, - /// Client was kicked - Kicked(String), -} - /// Reponse To ClientType #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(clippy::clippy::large_enum_variant)] -pub enum ServerInitMsg { +pub enum ServerInit { TooManyPlayers, GameSync { entity_package: sync::EntityPackage, time_of_day: state::TimeOfDay, max_group_size: u32, client_timeout: Duration, - world_map: WorldMapMsg, + world_map: crate::msg::world_packet::WorldMapMsg, recipe_book: RecipeBook, }, } -pub type ServerRegisterAnswerMsg = Result<(), RegisterError>; +pub type ServerRegisterAnswer = Result<(), RegisterError>; //Messages only allowed while client in character screen #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerCharacterScreenMsg { +pub enum ServerCharacterScreen { /// An error occurred while loading character data CharacterDataLoadError(String), /// A list of characters belonging to the a authenticated player was sent @@ -223,7 +79,7 @@ pub enum ServerCharacterScreenMsg { //Messages only allowed while client is in game (with a character) #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerInGameMsg { +pub enum ServerInGame { GroupUpdate(comp::group::ChangeNotification), // Indicate to the client that they are invited to join a group GroupInvite { @@ -256,7 +112,7 @@ pub enum ServerInGameMsg { /// Messages sent from the server to the client #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerGeneralMsg { +pub enum ServerGeneral { PlayerListUpdate(PlayerListUpdate), /// A message to go into the client chat box. The client is responsible for /// formatting the message and turning it into a speech bubble. @@ -272,6 +128,58 @@ pub enum ServerGeneralMsg { Notification(Notification), } +/* +end of 2nd level Enums +*/ + +/// Inform the client of updates to the player list. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum PlayerListUpdate { + Init(HashMap), + Add(Uid, PlayerInfo), + SelectedCharacter(Uid, CharacterInfo), + LevelChange(Uid, u32), + Admin(Uid, bool), + Remove(Uid), + Alias(Uid, String), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PlayerInfo { + pub is_admin: bool, + pub is_online: bool, + pub player_alias: String, + pub character: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CharacterInfo { + pub name: String, + pub level: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum InviteAnswer { + Accepted, + Declined, + TimedOut, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Notification { + WaypointSaved, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum DisconnectReason { + /// Server shut down + Shutdown, + /// Client sent disconnect message + Requested, + /// Client was kicked + Kicked(String), +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum RegisterError { AlreadyLoggedIn, @@ -286,6 +194,34 @@ impl From for RegisterError { fn from(err: AuthClientError) -> Self { Self::AuthError(err.to_string()) } } -impl From for ServerGeneralMsg { - fn from(v: comp::ChatMsg) -> Self { ServerGeneralMsg::ChatMsg(v) } +impl From for ServerGeneral { + fn from(v: comp::ChatMsg) -> Self { ServerGeneral::ChatMsg(v) } +} + +impl Into for ServerInfo { + fn into(self) -> ServerMsg { ServerMsg::Info(self) } +} + +impl Into for ServerInit { + fn into(self) -> ServerMsg { ServerMsg::Init(self) } +} + +impl Into for ServerRegisterAnswer { + fn into(self) -> ServerMsg { ServerMsg::RegisterAnswer(self) } +} + +impl Into for ServerCharacterScreen { + fn into(self) -> ServerMsg { ServerMsg::CharacterScreen(self) } +} + +impl Into for ServerInGame { + fn into(self) -> ServerMsg { ServerMsg::InGame(self) } +} + +impl Into for ServerGeneral { + fn into(self) -> ServerMsg { ServerMsg::General(self) } +} + +impl Into for PingMsg { + fn into(self) -> ServerMsg { ServerMsg::Ping(self) } } diff --git a/common/src/msg/world_packet.rs b/common/src/msg/world_packet.rs new file mode 100644 index 0000000000..c19c855166 --- /dev/null +++ b/common/src/msg/world_packet.rs @@ -0,0 +1,123 @@ +use serde::{Deserialize, Serialize}; +use vek::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +/// World map information. Note that currently, we always send the whole thing +/// in one go, but the structure aims to try to provide information as locally +/// as possible, so that in the future we can split up large maps into multiple +/// WorldMapMsg fragments. +/// +/// TODO: Update message format to make fragmentable, allowing us to send more +/// information without running into bandwidth issues. +/// +/// TODO: Add information for rivers (currently, we just prerender them on the +/// server, but this is not a great solution for LoD. The map rendering code is +/// already set up to be able to take advantage of the river rendering being +/// split out, but the format is a little complicated for space reasons and it +/// may take some tweaking to get right, so we avoid sending it for now). +/// +/// TODO: measure explicit compression schemes that might save space, e.g. +/// repeating the "small angles" optimization that works well on more detailed +/// shadow maps intended for height maps. +pub struct WorldMapMsg { + /// Log base 2 of world map dimensions (width × height) in chunks. + /// + /// NOTE: Invariant: chunk count fits in a u16. + pub dimensions_lg: Vec2, + /// Sea level (used to provide a base altitude). + pub sea_level: f32, + /// Max height (used to scale altitudes). + pub max_height: f32, + /// RGB+A; the alpha channel is currently unused, but will be used in the + /// future. Entries are in the usual chunk order. + pub rgba: Vec, + /// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for + /// altitude. The remainder are currently unused, but we have plans to + /// use 7 bits for water depth (using an integer f7 encoding), and we + /// will find other uses for the remaining 12 bits. + pub alt: Vec, + /// Horizon mapping. This is a variant of shadow mapping that is + /// specifically designed for height maps; it takes advantage of their + /// regular structure (e.g. no holes) to compress all information needed + /// to decide when to cast a sharp shadow into a single nagle, the "horizon + /// angle." This is the smallest angle with the ground at which light can + /// pass through any occluders to reach the chunk, in some chosen + /// horizontal direction. This would not be sufficient for a more + /// complicated 3D structure, but it works for height maps since: + /// + /// 1. they have no gaps, so as soon as light can shine through it will + /// always be able to do so, and + /// 2. we only care about lighting from the top, and only from the east and + /// west (since at a large scale like this we mostly just want to + /// handle variable sunlight; moonlight would present more challenges + /// but we currently have no plans to try to cast accurate shadows in + /// moonlight). + /// + /// Our chosen format is two pairs of vectors, + /// with the first pair representing west-facing light (casting shadows on + /// the left side) and the second representing east-facing light + /// (casting shadows on the east side). + /// + /// The pair of vectors consists of (with each vector in the usual chunk + /// order): + /// + /// * Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360). + /// We might consider switching to tangent if that represents the + /// information we care about better. + /// * Approximate (floor) height of maximal occluder. We currently use this + /// to try to deliver some approximation of soft shadows, which isn't that + /// big a deal on the world map but is probably needed in order to ensure + /// smooth transitions between chunks in LoD view. Additionally, when we + /// start using the shadow information to do local lighting on the world + /// map, we'll want a quick way to test where we can go out of shadow at + /// arbitrary heights (since the player and other entities cajn find + /// themselves far from the ground at times). While this is only an + /// approximation to a proper distance map, hopefully it will give us + /// something that feels reasonable enough for Veloren's style. + /// + /// NOTE: On compression. + /// + /// Horizon mapping has a lot of advantages for height maps (simple, easy to + /// understand, doesn't require any fancy math or approximation beyond + /// precision loss), though it loses a few of them by having to store + /// distance to occluder as well. However, just storing tons + /// and tons of regular shadow maps (153 for a full day cycle, stored at + /// irregular intervals) combined with clever explicit compression and + /// avoiding recording sharp local shadows (preferring retracing for + /// these), yielded a compression rate of under 3 bits per column! Since + /// we likely want to avoid per-column shadows for worlds of the sizes we + /// want, we'd still need to store *some* extra information to create + /// soft shadows, but it would still be nice to try to drive down our + /// size as much as possible given how compressible shadows of height + /// maps seem to be in practice. Therefore, we try to take advantage of the + /// way existing compression algorithms tend to work to see if we can + /// achieve significant gains without doing a lot of custom work. + /// + /// Specifically, since our rays are cast east/west, we expect that for each + /// row, the horizon angles in each direction should be sequences of + /// monotonically increasing values (as chunks approach a tall + /// occluder), followed by sequences of no shadow, repeated + /// until the end of the map. Monotonic sequences and same-byte sequences + /// are usually easy to compress and existing algorithms are more likely + /// to be able to deal with them than jumbled data. If we were to keep + /// both directions in the same vector, off-the-shelf compression would + /// probably be less effective. + /// + /// For related reasons, rather than storing distances as in a standard + /// distance map (which would lead to monotonically *decreasing* values + /// as we approached the occluder from a given direction), we store the + /// estimated *occluder height.* The idea here is that we replace the + /// monotonic sequences with constant sequences, which are extremely + /// straightforward to compress and mostly handled automatically by anything + /// that does run-length encoding (i.e. most off-the-shelf compression + /// algorithms). + /// + /// We still need to benchmark this properly, as there's no guarantee our + /// current compression algorithms will actually work well on this data + /// in practice. It's possible that some other permutation (e.g. more + /// bits reserved for "distance to occluder" in exchange for an even + /// more predictible sequence) would end up compressing better than storing + /// angles, or that we don't need as much precision as we currently have + /// (256 possible angles). + pub horizons: [(Vec, Vec); 2], +} diff --git a/server/src/client.rs b/server/src/client.rs index a473c03c52..f3db6ee99e 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,16 +1,16 @@ use crate::error::Error; use common::msg::{ - ClientCharacterScreenMsg, ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientType, PingMsg, - ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInitMsg, + ClientCharacterScreen, ClientGeneral, ClientInGame, ClientIngame, ClientType, PingMsg, + ServerMsg, }; use hashbrown::HashSet; -use network::{MessageBuffer, Participant, Stream}; +use network::{Participant, Stream}; use serde::{de::DeserializeOwned, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; use std::sync::{ atomic::{AtomicBool, Ordering}, - Arc, Mutex, + Mutex, }; use tracing::debug; use vek::*; @@ -20,7 +20,7 @@ pub struct Client { pub client_type: ClientType, pub in_game: Option, pub participant: Mutex>, - pub singleton_stream: Stream, + pub general_stream: Stream, pub ping_stream: Stream, pub register_stream: Stream, pub character_screen_stream: Stream, @@ -44,6 +44,7 @@ impl Client { } } + /* fn internal_send_raw(b: &AtomicBool, s: &mut Stream, msg: Arc) { if !b.load(Ordering::Relaxed) { if let Err(e) = s.send_raw(msg) { @@ -52,29 +53,32 @@ impl Client { } } } + */ - pub fn send_init(&mut self, msg: ServerInitMsg) { - Self::internal_send(&self.network_error, &mut self.register_stream, msg); - } - - pub fn send_msg(&mut self, msg: ServerGeneralMsg) { - Self::internal_send(&self.network_error, &mut self.singleton_stream, msg); - } - - pub fn send_in_game(&mut self, msg: ServerInGameMsg) { - Self::internal_send(&self.network_error, &mut self.in_game_stream, msg); - } - - pub fn send_character_screen(&mut self, msg: ServerCharacterScreenMsg) { - Self::internal_send(&self.network_error, &mut self.character_screen_stream, msg); - } - - pub fn send_ping(&mut self, msg: PingMsg) { - Self::internal_send(&self.network_error, &mut self.ping_stream, msg); - } - - pub fn send_msg_raw(&mut self, msg: Arc) { - Self::internal_send_raw(&self.network_error, &mut self.singleton_stream, msg); + pub fn send_msg(&mut self, msg: S) + where + S: Into, + { + const ERR: &str = "Dont do that, thats only done once at the start, no via this class"; + match msg.into() { + ServerMsg::Info(_) => panic!(ERR), + ServerMsg::Init(_) => panic!(ERR), + ServerMsg::RegisterAnswer(msg) => { + Self::internal_send(&self.network_error, &mut self.register_stream, &msg) + }, + ServerMsg::CharacterScreen(msg) => { + Self::internal_send(&self.network_error, &mut self.character_screen_stream, &msg) + }, + ServerMsg::InGame(msg) => { + Self::internal_send(&self.network_error, &mut self.in_game_stream, &msg) + }, + ServerMsg::General(msg) => { + Self::internal_send(&self.network_error, &mut self.general_stream, &msg) + }, + ServerMsg::Ping(msg) => { + Self::internal_send(&self.network_error, &mut self.ping_stream, &msg) + }, + }; } pub async fn internal_recv( @@ -95,15 +99,15 @@ impl Client { } } - pub async fn recv_msg(&mut self) -> Result { - Self::internal_recv(&self.network_error, &mut self.singleton_stream).await + pub async fn recv_msg(&mut self) -> Result { + Self::internal_recv(&self.network_error, &mut self.general_stream).await } - pub async fn recv_in_game_msg(&mut self) -> Result { + pub async fn recv_in_game_msg(&mut self) -> Result { Self::internal_recv(&self.network_error, &mut self.in_game_stream).await } - pub async fn recv_character_screen_msg(&mut self) -> Result { + pub async fn recv_character_screen_msg(&mut self) -> Result { Self::internal_recv(&self.network_error, &mut self.character_screen_stream).await } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 9f2fa203fc..4d0b61d807 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -12,7 +12,7 @@ use common::{ cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, comp::{self, ChatType, Item, LightEmitter, WaypointArea}, event::{EventBus, ServerEvent}, - msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg}, + msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral, ServerInGame}, npc::{self, get_npc_name}, state::TimeOfDay, sync::{Uid, WorldSyncExt}, @@ -504,7 +504,7 @@ fn handle_alias( ecs.read_storage::().get(target), old_alias_optional, ) { - let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias( + let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Alias( *uid, player.alias.clone(), )); @@ -669,9 +669,7 @@ fn handle_spawn( .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| { - c.send_in_game(ServerInGameMsg::GroupUpdate(g)) - }); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); } else if let Some(group) = match alignment { @@ -1161,7 +1159,7 @@ fn handle_waypoint( server.notify_client(client, ChatType::CommandInfo.server_msg("Waypoint saved!")); server.notify_client( client, - ServerGeneralMsg::Notification(Notification::WaypointSaved), + ServerGeneral::Notification(Notification::WaypointSaved), ); }, None => server.notify_client( @@ -1198,7 +1196,7 @@ fn handle_adminify( ecs.write_storage().insert(player, comp::Admin).is_ok() }; // Update player list so the player shows up as admin in client chat. - let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Admin( + let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Admin( *ecs.read_storage::() .get(player) .expect("Player should have uid"), @@ -1591,7 +1589,7 @@ fn find_target( ecs: &specs::World, opt_alias: Option, fallback: EcsEntity, -) -> Result { +) -> Result { if let Some(alias) = opt_alias { (&ecs.entities(), &ecs.read_storage::()) .join() @@ -1663,7 +1661,7 @@ fn handle_set_level( .expect("Failed to get uid for player"); server .state - .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( + .notify_registered_clients(ServerGeneral::PlayerListUpdate( PlayerListUpdate::LevelChange(uid, lvl), )); @@ -1903,7 +1901,7 @@ fn kick_player(server: &mut Server, target_player: EcsEntity, reason: &str) { .emit_now(ServerEvent::ClientDisconnect(target_player)); server.notify_client( target_player, - ServerGeneralMsg::Disconnect(DisconnectReason::Kicked(reason.to_string())), + ServerGeneral::Disconnect(DisconnectReason::Kicked(reason.to_string())), ); } diff --git a/server/src/connection_handler.rs b/server/src/connection_handler.rs index 4e3db3b09a..a2c7145f58 100644 --- a/server/src/connection_handler.rs +++ b/server/src/connection_handler.rs @@ -134,7 +134,7 @@ impl ConnectionHandler { client_type, in_game: None, participant: std::sync::Mutex::new(Some(participant)), - singleton_stream: general_stream, + general_stream, ping_stream, register_stream, in_game_stream, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 16eda3ef29..d480302e82 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -12,7 +12,7 @@ use common::{ Player, Pos, Stats, }, lottery::Lottery, - msg::{PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg}, + msg::{PlayerListUpdate, ServerGeneral, ServerInGame}, outcome::Outcome, state::BlockChange, sync::{Uid, UidAllocator, WorldSyncExt}, @@ -44,7 +44,7 @@ pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3) } let mut clients = state.ecs().write_storage::(); if let Some(client) = clients.get_mut(entity) { - client.send_in_game(ServerInGameMsg::Knockback(impulse)); + client.send_msg(ServerInGame::Knockback(impulse)); } } @@ -656,7 +656,7 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) { server .state - .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( + .notify_registered_clients(ServerGeneral::PlayerListUpdate( PlayerListUpdate::LevelChange(*uid, new_level), )); } diff --git a/server/src/events/group_manip.rs b/server/src/events/group_manip.rs index 6c96644968..bbaf5c0b32 100644 --- a/server/src/events/group_manip.rs +++ b/server/src/events/group_manip.rs @@ -5,7 +5,7 @@ use common::{ group::{self, Group, GroupManager, Invite, PendingInvites}, ChatType, GroupManip, }, - msg::{InviteAnswer, ServerInGameMsg}, + msg::{InviteAnswer, ServerInGame}, sync, sync::WorldSyncExt, }; @@ -155,7 +155,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani (clients.get_mut(invitee), uids.get(entity).copied()) { if send_invite() { - client.send_in_game(ServerInGameMsg::GroupInvite { + client.send_msg(ServerInGame::GroupInvite { inviter, timeout: PRESENTED_INVITE_TIMEOUT_DUR, }); @@ -171,7 +171,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani // Notify inviter that the invite is pending if invite_sent { if let Some(client) = clients.get_mut(entity) { - client.send_in_game(ServerInGameMsg::InvitePending(uid)); + client.send_msg(ServerInGame::InvitePending(uid)); } } }, @@ -196,7 +196,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if let (Some(client), Some(target)) = (clients.get_mut(inviter), uids.get(entity).copied()) { - client.send_in_game(ServerInGameMsg::InviteComplete { + client.send_msg(ServerInGame::InviteComplete { target, answer: InviteAnswer::Accepted, }) @@ -217,7 +217,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); } @@ -244,7 +244,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if let (Some(client), Some(target)) = (clients.get_mut(inviter), uids.get(entity).copied()) { - client.send_in_game(ServerInGameMsg::InviteComplete { + client.send_msg(ServerInGame::InviteComplete { target, answer: InviteAnswer::Declined, }) @@ -269,7 +269,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); }, @@ -336,7 +336,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); @@ -410,7 +410,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); // Tell them they are the leader diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 2496c0f7fa..d67ca8a1d4 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -4,7 +4,7 @@ use crate::{ }; use common::{ comp::{self, item}, - msg::ServerGeneralMsg, + msg::ServerGeneral, sync::{Uid, WorldSyncExt}, }; use specs::{world::WorldExt, Entity as EcsEntity}; @@ -116,7 +116,7 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) { let mut clients = ecs.write_storage::(); if clients.get_mut(possesse).is_none() { if let Some(mut client) = clients.remove(possessor) { - client.send_msg(ServerGeneralMsg::SetPlayerEntity(possesse_uid)); + client.send_msg(ServerGeneral::SetPlayerEntity(possesse_uid)); clients .insert(possesse, client) .err() diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index c894479dc8..2f39db3811 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -5,7 +5,7 @@ use common::{ slot::{self, Slot}, Pos, MAX_PICKUP_RANGE_SQR, }, - msg::ServerInGameMsg, + msg::ServerInGame, recipe::default_recipe_book, sync::{Uid, WorldSyncExt}, vol::ReadVol, @@ -281,9 +281,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .map(|g| (g, c)) }) .map(|(g, c)| { - c.send_in_game( - ServerInGameMsg::GroupUpdate(g), - ) + c.send_msg(ServerInGame::GroupUpdate(g)) }); }, ); diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 572c5d9058..db6a164a14 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -5,7 +5,7 @@ use crate::{ use common::{ comp, comp::{group, Player}, - msg::{PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg}, + msg::{PlayerListUpdate, ServerGeneral, ServerInGame}, span, sync::{Uid, UidAllocator}, }; @@ -34,7 +34,7 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) { // Tell client its request was successful client.in_game = None; - client.send_in_game(ServerInGameMsg::ExitInGameSuccess); + client.send_msg(ServerInGame::ExitInGameSuccess); let entity_builder = state.ecs_mut().create_entity().with(client).with(player); @@ -130,9 +130,9 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event ) { state.notify_registered_clients(comp::ChatType::Offline(*uid).server_msg("")); - state.notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( - PlayerListUpdate::Remove(*uid), - )); + state.notify_registered_clients(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove( + *uid, + ))); } // Make sure to remove the player from the logged in list. (See LoginProvider) diff --git a/server/src/lib.rs b/server/src/lib.rs index ce5d0b267e..138ac9c50e 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -47,8 +47,8 @@ use common::{ comp::{self, ChatType}, event::{EventBus, ServerEvent}, msg::{ - server::WorldMapMsg, ClientType, DisconnectReason, ServerCharacterScreenMsg, - ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg, + ClientType, DisconnectReason, ServerCharacterScreen, ServerGeneral, ServerInfo, ServerInit, + ServerMsg, WorldMapMsg, }, outcome::Outcome, recipe::default_recipe_book, @@ -525,13 +525,13 @@ impl Server { .messages() .for_each(|query_result| match query_result.result { CharacterLoaderResponseType::CharacterList(result) => match result { - Ok(character_list_data) => self.notify_character_screen_client( + Ok(character_list_data) => self.notify_client( query_result.entity, - ServerCharacterScreenMsg::CharacterListUpdate(character_list_data), + ServerCharacterScreen::CharacterListUpdate(character_list_data), ), - Err(error) => self.notify_character_screen_client( + Err(error) => self.notify_client( query_result.entity, - ServerCharacterScreenMsg::CharacterActionError(error.to_string()), + ServerCharacterScreen::CharacterActionError(error.to_string()), ), }, CharacterLoaderResponseType::CharacterData(result) => { @@ -544,9 +544,9 @@ impl Server { // We failed to load data for the character from the DB. Notify the // client to push the state back to character selection, with the error // to display - self.notify_character_screen_client( + self.notify_client( query_result.entity, - ServerCharacterScreenMsg::CharacterDataLoadError(error.to_string()), + ServerCharacterScreen::CharacterDataLoadError(error.to_string()), ); // Clean up the entity data on the server @@ -815,7 +815,7 @@ impl Server { ?client.participant, "to many players, wont allow participant to connect" ); - client.register_stream.send(ServerInitMsg::TooManyPlayers)?; + client.register_stream.send(ServerInit::TooManyPlayers)?; continue; } @@ -839,7 +839,7 @@ impl Server { .get_mut(entity) .unwrap() .register_stream - .send(ServerInitMsg::GameSync { + .send(ServerInit::GameSync { // Send client their entity entity_package: TrackedComps::fetch(&self.state.ecs()) .create_entity_package(entity, None, None, None), @@ -858,32 +858,14 @@ impl Server { pub fn notify_client(&self, entity: EcsEntity, msg: S) where - S: Into, + S: Into, { if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { client.send_msg(msg.into()) } } - pub fn notify_in_game_client(&self, entity: EcsEntity, msg: S) - where - S: Into, - { - if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { - client.send_in_game(msg.into()) - } - } - - pub fn notify_character_screen_client(&self, entity: EcsEntity, msg: S) - where - S: Into, - { - if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { - client.send_character_screen(msg.into()) - } - } - - pub fn notify_registered_clients(&mut self, msg: ServerGeneralMsg) { + pub fn notify_registered_clients(&mut self, msg: ServerGeneral) { self.state.notify_registered_clients(msg); } @@ -963,7 +945,7 @@ impl Server { impl Drop for Server { fn drop(&mut self) { self.state - .notify_registered_clients(ServerGeneralMsg::Disconnect(DisconnectReason::Shutdown)); + .notify_registered_clients(ServerGeneral::Disconnect(DisconnectReason::Shutdown)); } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 2f31784f91..807b553ef9 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -6,8 +6,8 @@ use common::{ comp, effect::Effect, msg::{ - CharacterInfo, ClientIngame, PlayerListUpdate, ServerCharacterScreenMsg, ServerGeneralMsg, - ServerInGameMsg, + CharacterInfo, ClientIngame, PlayerListUpdate, ServerCharacterScreen, ServerGeneral, + ServerInGame, ServerMsg, }, state::State, sync::{Uid, UidAllocator, WorldSyncExt}, @@ -62,7 +62,8 @@ 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: ServerGeneralMsg); + fn notify_registered_clients(&self, msg: ServerGeneral); + fn notify_in_game_clients(&self, msg: ServerInGame); /// Delete an entity, recording the deletion in [`DeletedEntities`] fn delete_entity_recorded( &mut self, @@ -220,7 +221,7 @@ impl StateExt for State { // Tell the client its request was successful. if let Some(client) = self.ecs().write_storage::().get_mut(entity) { client.in_game = Some(ClientIngame::Character); - client.send_character_screen(ServerCharacterScreenMsg::CharacterSuccess) + client.send_msg(ServerCharacterScreen::CharacterSuccess) } } @@ -229,7 +230,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(ServerGeneralMsg::PlayerListUpdate( + self.notify_registered_clients(ServerGeneral::PlayerListUpdate( PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo { name: String::from(&stats.name), level: stats.level.level(), @@ -276,7 +277,7 @@ impl StateExt for State { | comp::ChatType::Kill(_, _) | comp::ChatType::Meta | comp::ChatType::World(_) => { - self.notify_registered_clients(ServerGeneralMsg::ChatMsg(resolved_msg)) + self.notify_registered_clients(ServerGeneral::ChatMsg(resolved_msg)) }, comp::ChatType::Tell(u, t) => { for (client, uid) in ( @@ -286,7 +287,7 @@ impl StateExt for State { .join() { if uid == u || uid == t { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } }, @@ -298,7 +299,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } } @@ -310,7 +311,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } } @@ -322,7 +323,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } } @@ -336,7 +337,7 @@ impl StateExt for State { .join() { if s == &faction.0 { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } }, @@ -348,7 +349,7 @@ impl StateExt for State { .join() { if g == group { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } }, @@ -356,7 +357,8 @@ impl StateExt for State { } /// Sends the message to all connected clients - fn notify_registered_clients(&self, msg: ServerGeneralMsg) { + fn notify_registered_clients(&self, msg: ServerGeneral) { + let msg: ServerMsg = msg.into(); for client in (&mut self.ecs().write_storage::()) .join() .filter(|c| c.registered) @@ -365,6 +367,17 @@ impl StateExt for State { } } + /// Sends the message to all clients playing in game + fn notify_in_game_clients(&self, msg: ServerInGame) { + let msg: ServerMsg = msg.into(); + for client in (&mut self.ecs().write_storage::()) + .join() + .filter(|c| c.in_game.is_some()) + { + client.send_msg(msg.clone()); + } + } + fn delete_entity_recorded( &mut self, entity: EcsEntity, @@ -388,7 +401,7 @@ impl StateExt for State { .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); } diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 46a9e27d43..9f163db12e 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -8,7 +8,7 @@ use crate::{ }; use common::{ comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, - msg::{ServerGeneralMsg, ServerInGameMsg}, + msg::{ServerGeneral, ServerInGame}, outcome::Outcome, region::{Event as RegionEvent, RegionMap}, span, @@ -128,14 +128,13 @@ impl<'a> System<'a> for Sys { (uid, pos, velocities.get(entity), orientations.get(entity)) }) }) { - let create_msg = ServerGeneralMsg::CreateEntity( - tracked_comps.create_entity_package( + let create_msg = + ServerGeneral::CreateEntity(tracked_comps.create_entity_package( entity, Some(*pos), vel.copied(), ori.copied(), - ), - ); + )); for (client, regions, client_entity, _) in &mut subscribers { if maybe_key .as_ref() @@ -158,7 +157,7 @@ impl<'a> System<'a> for Sys { .map(|key| !regions.contains(key)) .unwrap_or(true) { - client.send_msg(ServerGeneralMsg::DeleteEntity(uid)); + client.send_msg(ServerGeneral::DeleteEntity(uid)); } } } @@ -175,14 +174,14 @@ impl<'a> System<'a> for Sys { .take_deleted_in_region(key) .unwrap_or_default(), ); - let entity_sync_msg = ServerGeneralMsg::EntitySync(entity_sync_package); - let comp_sync_msg = ServerGeneralMsg::CompSync(comp_sync_package); + let entity_sync_msg = ServerGeneral::EntitySync(entity_sync_package); + let comp_sync_msg = ServerGeneral::CompSync(comp_sync_package); subscribers.iter_mut().for_each(move |(client, _, _, _)| { client.send_msg(entity_sync_msg.clone()); client.send_msg(comp_sync_msg.clone()); }); - let mut send_msg = |msg: ServerGeneralMsg, + let mut send_msg = |msg: ServerGeneral, entity: EcsEntity, pos: Pos, force_update: Option<&ForceUpdate>, @@ -288,7 +287,7 @@ impl<'a> System<'a> for Sys { } send_msg( - ServerGeneralMsg::CompSync(comp_sync_package), + ServerGeneral::CompSync(comp_sync_package), entity, pos, force_update, @@ -312,7 +311,7 @@ impl<'a> System<'a> for Sys { }) { for uid in &deleted { - client.send_msg(ServerGeneralMsg::DeleteEntity(Uid(*uid))); + client.send_msg(ServerGeneral::DeleteEntity(Uid(*uid))); } } } @@ -321,7 +320,7 @@ impl<'a> System<'a> for Sys { // Sync inventories for (client, inventory, update) in (&mut clients, &inventories, &inventory_updates).join() { - client.send_in_game(ServerInGameMsg::InventoryUpdate( + client.send_msg(ServerInGame::InventoryUpdate( inventory.clone(), update.event(), )); @@ -342,7 +341,7 @@ impl<'a> System<'a> for Sys { .cloned() .collect::>(); if !outcomes.is_empty() { - client.send_in_game(ServerInGameMsg::Outcomes(outcomes)); + client.send_msg(ServerInGame::Outcomes(outcomes)); } } outcomes.clear(); @@ -354,7 +353,7 @@ impl<'a> System<'a> for Sys { // Sync resources // TODO: doesn't really belong in this system (rename system or create another // system?) - let tof_msg = ServerGeneralMsg::TimeOfDay(*time_of_day); + let tof_msg = ServerGeneral::TimeOfDay(*time_of_day); for client in (&mut clients).join() { client.send_msg(tof_msg.clone()); } diff --git a/server/src/sys/invite_timeout.rs b/server/src/sys/invite_timeout.rs index 2afd405c11..9cf3f71455 100644 --- a/server/src/sys/invite_timeout.rs +++ b/server/src/sys/invite_timeout.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::group::{Invite, PendingInvites}, - msg::{InviteAnswer, ServerInGameMsg}, + msg::{InviteAnswer, ServerInGame}, span, sync::Uid, }; @@ -54,7 +54,7 @@ impl<'a> System<'a> for Sys { if let (Some(client), Some(target)) = (clients.get_mut(*inviter), uids.get(invitee).copied()) { - client.send_in_game(ServerInGameMsg::InviteComplete { + client.send_msg(ServerInGame::InviteComplete { target, answer: InviteAnswer::TimedOut, }) diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 1dc5b98a44..70a6afcddb 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -15,10 +15,10 @@ use common::{ }, event::{EventBus, ServerEvent}, msg::{ - validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientCharacterScreenMsg, - ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientRegisterMsg, DisconnectReason, - PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerCharacterScreenMsg, - ServerGeneralMsg, ServerInGameMsg, ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, + validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientCharacterScreen, + ClientGeneral, ClientInGame, ClientIngame, ClientRegister, DisconnectReason, PingMsg, + PlayerInfo, PlayerListUpdate, RegisterError, ServerCharacterScreen, ServerGeneral, + ServerInGame, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, }, span, state::{BlockChange, Time}, @@ -45,10 +45,10 @@ impl Sys { player_metrics: &ReadExpect<'_, PlayerMetrics>, uids: &ReadStorage<'_, Uid>, chat_modes: &ReadStorage<'_, ChatMode>, - msg: ClientGeneralMsg, + msg: ClientGeneral, ) -> Result<(), crate::error::Error> { match msg { - ClientGeneralMsg::ChatMsg(message) => { + ClientGeneral::ChatMsg(message) => { if client.registered { match validate_chat_msg(&message) { Ok(()) => { @@ -68,10 +68,10 @@ impl Sys { } } }, - ClientGeneralMsg::Disconnect => { - client.send_msg(ServerGeneralMsg::Disconnect(DisconnectReason::Requested)); + ClientGeneral::Disconnect => { + client.send_msg(ServerGeneral::Disconnect(DisconnectReason::Requested)); }, - ClientGeneralMsg::Terminate => { + ClientGeneral::Terminate => { debug!(?entity, "Client send message to termitate session"); player_metrics .clients_disconnected @@ -100,24 +100,24 @@ impl Sys { players: &mut WriteStorage<'_, Player>, controllers: &mut WriteStorage<'_, Controller>, settings: &Read<'_, Settings>, - msg: ClientInGameMsg, + msg: ClientInGame, ) -> 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, ClientInGameMsg::TerrainChunkRequest{ .. }) { + if matches!(msg, ClientInGame::TerrainChunkRequest{ .. }) { network_metrics.chunks_request_dropped.inc(); } return Ok(()); } match msg { // Go back to registered state (char selection screen) - ClientInGameMsg::ExitInGame => { + ClientInGame::ExitInGame => { client.in_game = None; server_emitter.emit(ServerEvent::ExitIngame { entity }); - client.send_in_game(ServerInGameMsg::ExitInGameSuccess); + client.send_msg(ServerInGame::ExitInGameSuccess); }, - ClientInGameMsg::SetViewDistance(view_distance) => { + ClientInGame::SetViewDistance(view_distance) => { players.get_mut(entity).map(|player| { player.view_distance = Some( settings @@ -133,19 +133,19 @@ impl Sys { .map(|max| view_distance > max) .unwrap_or(false) { - client.send_in_game(ServerInGameMsg::SetViewDistance( + client.send_msg(ServerInGame::SetViewDistance( settings.max_view_distance.unwrap_or(0), )); } }, - ClientInGameMsg::ControllerInputs(inputs) => { + ClientInGame::ControllerInputs(inputs) => { if let Some(ClientIngame::Character) = client.in_game { if let Some(controller) = controllers.get_mut(entity) { controller.inputs.update_with_new(inputs); } } }, - ClientInGameMsg::ControlEvent(event) => { + ClientInGame::ControlEvent(event) => { if let Some(ClientIngame::Character) = client.in_game { // Skip respawn if client entity is alive if let ControlEvent::Respawn = event { @@ -159,14 +159,14 @@ impl Sys { } } }, - ClientInGameMsg::ControlAction(event) => { + ClientInGame::ControlAction(event) => { if let Some(ClientIngame::Character) = client.in_game { if let Some(controller) = controllers.get_mut(entity) { controller.actions.push(event); } } }, - ClientInGameMsg::PlayerPhysics { pos, vel, ori } => { + ClientInGame::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) @@ -177,17 +177,17 @@ impl Sys { } } }, - ClientInGameMsg::BreakBlock(pos) => { + ClientInGame::BreakBlock(pos) => { if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) { block_changes.set(pos, block.into_vacant()); } }, - ClientInGameMsg::PlaceBlock(pos, block) => { + ClientInGame::PlaceBlock(pos, block) => { if can_build.get(entity).is_some() { block_changes.try_set(pos, block); } }, - ClientInGameMsg::TerrainChunkRequest { key } => { + ClientInGame::TerrainChunkRequest { key } => { let in_vd = if let (Some(view_distance), Some(pos)) = ( players.get(entity).and_then(|p| p.view_distance), positions.get(entity), @@ -203,7 +203,7 @@ impl Sys { match terrain.get_key(key) { Some(chunk) => { network_metrics.chunks_served_from_memory.inc(); - client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { + client.send_msg(ServerInGame::TerrainChunkUpdate { key, chunk: Ok(Box::new(chunk.clone())), }) @@ -217,17 +217,17 @@ impl Sys { network_metrics.chunks_request_dropped.inc(); } }, - ClientInGameMsg::UnlockSkill(skill) => { + ClientInGame::UnlockSkill(skill) => { stats .get_mut(entity) .map(|s| s.skill_set.unlock_skill(skill)); }, - ClientInGameMsg::RefundSkill(skill) => { + ClientInGame::RefundSkill(skill) => { stats .get_mut(entity) .map(|s| s.skill_set.refund_skill(skill)); }, - ClientInGameMsg::UnlockSkillGroup(skill_group_type) => { + ClientInGame::UnlockSkillGroup(skill_group_type) => { stats .get_mut(entity) .map(|s| s.skill_set.unlock_skill_group(skill_group_type)); @@ -247,18 +247,18 @@ impl Sys { players: &mut WriteStorage<'_, Player>, editable_settings: &ReadExpect<'_, EditableSettings>, alias_validator: &ReadExpect<'_, AliasValidator>, - msg: ClientCharacterScreenMsg, + msg: ClientCharacterScreen, ) -> Result<(), crate::error::Error> { match msg { // Request spectator state - ClientCharacterScreenMsg::Spectate => { + ClientCharacterScreen::Spectate => { if client.registered { client.in_game = Some(ClientIngame::Spectator) } else { debug!("dropped Spectate msg from unregistered client"); } }, - ClientCharacterScreenMsg::Character(character_id) => { + ClientCharacterScreen::Character(character_id) => { if client.registered && client.in_game.is_none() { // Only send login message if it wasn't already // sent previously @@ -301,11 +301,9 @@ impl Sys { } } } else { - client.send_character_screen( - ServerCharacterScreenMsg::CharacterDataLoadError(String::from( - "Failed to fetch player entity", - )), - ) + client.send_msg(ServerCharacterScreen::CharacterDataLoadError( + String::from("Failed to fetch player entity"), + )) } } else { let registered = client.registered; @@ -313,15 +311,15 @@ impl Sys { debug!(?registered, ?in_game, "dropped Character msg from client"); } }, - ClientCharacterScreenMsg::RequestCharacterList => { + ClientCharacterScreen::RequestCharacterList => { if let Some(player) = players.get(entity) { character_loader.load_character_list(entity, player.uuid().to_string()) } }, - ClientCharacterScreenMsg::CreateCharacter { alias, tool, body } => { + ClientCharacterScreen::CreateCharacter { alias, tool, body } => { if let Err(error) = alias_validator.validate(&alias) { debug!(?error, ?alias, "denied alias as it contained a banned word"); - client.send_character_screen(ServerCharacterScreenMsg::CharacterActionError( + client.send_msg(ServerCharacterScreen::CharacterActionError( error.to_string(), )); } else if let Some(player) = players.get(entity) { @@ -335,7 +333,7 @@ impl Sys { ); } }, - ClientCharacterScreenMsg::DeleteCharacter(character_id) => { + ClientCharacterScreen::DeleteCharacter(character_id) => { if let Some(player) = players.get(entity) { character_loader.delete_character( entity, @@ -351,7 +349,7 @@ impl Sys { #[allow(clippy::too_many_arguments)] fn handle_ping_msg(client: &mut Client, msg: PingMsg) -> Result<(), crate::error::Error> { match msg { - PingMsg::Ping => client.send_ping(PingMsg::Pong), + PingMsg::Ping => client.send_msg(PingMsg::Pong), PingMsg::Pong => {}, } Ok(()) @@ -368,7 +366,7 @@ impl Sys { admins: &mut WriteStorage<'_, Admin>, players: &mut WriteStorage<'_, Player>, editable_settings: &ReadExpect<'_, EditableSettings>, - msg: ClientRegisterMsg, + msg: ClientRegister, ) -> Result<(), crate::error::Error> { let (username, uuid) = match login_provider.try_login( &msg.token_or_username, @@ -379,7 +377,7 @@ impl Sys { Err(err) => { client .register_stream - .send(ServerRegisterAnswerMsg::Err(err))?; + .send(ServerRegisterAnswer::Err(err))?; return Ok(()); }, Ok((username, uuid)) => (username, uuid), @@ -391,9 +389,9 @@ impl Sys { if !player.is_valid() { // Invalid player - client.register_stream.send(ServerRegisterAnswerMsg::Err( - RegisterError::InvalidCharacter, - ))?; + client + .register_stream + .send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?; return Ok(()); } @@ -410,12 +408,10 @@ impl Sys { // Tell the client its request was successful. client.registered = true; - client - .register_stream - .send(ServerRegisterAnswerMsg::Ok(()))?; + client.register_stream.send(ServerRegisterAnswer::Ok(()))?; // Send initial player list - client.send_msg(ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init( + client.send_msg(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init( player_list.clone(), ))); @@ -458,7 +454,7 @@ impl Sys { alias_validator: &ReadExpect<'_, AliasValidator>, ) -> Result<(), crate::error::Error> { loop { - let q1 = Client::internal_recv(&client.network_error, &mut client.singleton_stream); + let q1 = Client::internal_recv(&client.network_error, &mut client.general_stream); let q2 = Client::internal_recv(&client.network_error, &mut client.in_game_stream); let q3 = Client::internal_recv(&client.network_error, &mut client.character_screen_stream); @@ -693,7 +689,7 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::ClientDisconnect(entity)); } else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 * 0.5 { // Try pinging the client if the timeout is nearing. - client.send_ping(PingMsg::Ping); + client.send_msg(PingMsg::Ping); } } @@ -702,7 +698,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 msg = - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { player_alias: player.alias.clone(), is_online: true, is_admin: admins.get(entity).is_some(), diff --git a/server/src/sys/subscription.rs b/server/src/sys/subscription.rs index ba5f3906df..2eac60f2a6 100644 --- a/server/src/sys/subscription.rs +++ b/server/src/sys/subscription.rs @@ -5,7 +5,7 @@ use super::{ use crate::client::{self, Client, RegionSubscription}; use common::{ comp::{Ori, Player, Pos, Vel}, - msg::ServerGeneralMsg, + msg::ServerGeneral, region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap}, span, sync::Uid, @@ -153,7 +153,7 @@ impl<'a> System<'a> for Sys { .map(|key| subscription.regions.contains(key)) .unwrap_or(false) { - client.send_msg(ServerGeneralMsg::DeleteEntity(uid)); + client.send_msg(ServerGeneral::DeleteEntity(uid)); } } }, @@ -161,7 +161,7 @@ impl<'a> System<'a> for Sys { } // Tell client to delete entities in the region for (&uid, _) in (&uids, region.entities()).join() { - client.send_msg(ServerGeneralMsg::DeleteEntity(uid)); + client.send_msg(ServerGeneral::DeleteEntity(uid)); } } // Send deleted entities since they won't be processed for this client in entity @@ -171,7 +171,7 @@ impl<'a> System<'a> for Sys { .iter() .flat_map(|v| v.iter()) { - client.send_msg(ServerGeneralMsg::DeleteEntity(Uid(*uid))); + client.send_msg(ServerGeneral::DeleteEntity(Uid(*uid))); } } @@ -196,7 +196,7 @@ impl<'a> System<'a> for Sys { { // Send message to create entity and tracked components and physics // components - client.send_msg(ServerGeneralMsg::CreateEntity( + client.send_msg(ServerGeneral::CreateEntity( tracked_comps.create_entity_package( entity, Some(*pos), @@ -249,7 +249,7 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) { .join() { // Send message to create entity and tracked components and physics components - client.send_msg(ServerGeneralMsg::CreateEntity( + client.send_msg(ServerGeneral::CreateEntity( tracked_comps.create_entity_package( entity, Some(*pos), diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 8ca477e0fd..c29330addf 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -4,7 +4,7 @@ use common::{ comp::{self, bird_medium, Alignment, Player, Pos}, event::{EventBus, ServerEvent}, generation::get_npc_name, - msg::ServerInGameMsg, + msg::ServerInGame, npc::NPC_NAMES, span, state::TerrainChanges, @@ -63,7 +63,7 @@ impl<'a> System<'a> for Sys { Ok((chunk, supplement)) => (chunk, supplement), Err(Some(entity)) => { if let Some(client) = clients.get_mut(entity) { - client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { + client.send_msg(ServerInGame::TerrainChunkUpdate { key, chunk: Err(()), }); @@ -90,7 +90,7 @@ impl<'a> System<'a> for Sys { .magnitude_squared(); if adjusted_dist_sqr <= view_distance.pow(2) { - client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { + client.send_msg(ServerInGame::TerrainChunkUpdate { key, chunk: Ok(Box::new(chunk.clone())), }); diff --git a/server/src/sys/terrain_sync.rs b/server/src/sys/terrain_sync.rs index d0d1df5376..876b3b94eb 100644 --- a/server/src/sys/terrain_sync.rs +++ b/server/src/sys/terrain_sync.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::{Player, Pos}, - msg::ServerInGameMsg, + msg::ServerInGame, span, state::TerrainChanges, terrain::TerrainGrid, @@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys { .map(|vd| super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, vd)) .unwrap_or(false) { - client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { + client.send_msg(ServerInGame::TerrainChunkUpdate { key: *chunk_key, chunk: Ok(Box::new(match terrain.get_key(*chunk_key) { Some(chunk) => chunk.clone(), @@ -51,10 +51,10 @@ impl<'a> System<'a> for Sys { // TODO: Don't send all changed blocks to all clients // Sync changed blocks - let msg = ServerInGameMsg::TerrainBlockUpdates(terrain_changes.modified_blocks.clone()); + let msg = ServerInGame::TerrainBlockUpdates(terrain_changes.modified_blocks.clone()); for (player, client) in (&players, &mut clients).join() { if player.view_distance.is_some() { - client.send_in_game(msg.clone()); + client.send_msg(msg.clone()); } } diff --git a/server/src/sys/waypoint.rs b/server/src/sys/waypoint.rs index 63642e135a..bf8034baa0 100644 --- a/server/src/sys/waypoint.rs +++ b/server/src/sys/waypoint.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::{Player, Pos, Waypoint, WaypointArea}, - msg::{Notification, ServerGeneralMsg}, + msg::{Notification, ServerGeneral}, span, state::Time, }; @@ -42,9 +42,8 @@ impl<'a> System<'a> for Sys { if let Ok(wp_old) = waypoints.insert(entity, Waypoint::new(player_pos.0, *time)) { if wp_old.map_or(true, |w| w.elapsed(*time) > NOTIFY_TIME) { - client.send_msg(ServerGeneralMsg::Notification( - Notification::WaypointSaved, - )); + client + .send_msg(ServerGeneral::Notification(Notification::WaypointSaved)); } } } diff --git a/world/src/lib.rs b/world/src/lib.rs index 5e5a322229..7aa02fb46c 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -38,7 +38,7 @@ use crate::{ use common::{ comp::{self, bird_medium, quadruped_low, quadruped_medium, quadruped_small}, generation::{ChunkSupplement, EntityInfo}, - msg::server::WorldMapMsg, + msg::WorldMapMsg, terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, WriteVol}, }; diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 58ec0de7ea..5f67f0712b 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -33,7 +33,7 @@ use crate::{ }; use common::{ assets, - msg::server::WorldMapMsg, + msg::WorldMapMsg, store::Id, terrain::{ map::MapConfig, uniform_idx_as_vec2, vec2_as_uniform_idx, BiomeKind, MapSizeLg,