diff --git a/client/src/lib.rs b/client/src/lib.rs index 09841e3fa7..390ee69b5e 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -220,10 +220,7 @@ impl Client { } /// Send disconnect message to the server - pub fn request_logout(&mut self) { - self.postbox.send_message(ClientMsg::Disconnect); - self.client_state = ClientState::Pending; - } + pub fn request_logout(&mut self) { self.postbox.send_message(ClientMsg::Disconnect); } /// Request a state transition to `ClientState::Registered` from an ingame /// state. @@ -722,6 +719,7 @@ impl Client { }, ServerMsg::Disconnect => { frontend_events.push(Event::Disconnect); + self.postbox.send_message(ClientMsg::Terminate); }, } } diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 962cf40174..2b2ec63f33 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -35,4 +35,5 @@ pub enum ClientMsg { key: Vec2, }, Disconnect, + Terminate, } diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 3237ee57dd..10d1ee78bf 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -7,7 +7,7 @@ use common::{ sync::{Uid, UidAllocator}, }; use log::error; -use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, WorldExt}; +use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt}; pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { let state = server.state_mut(); @@ -53,12 +53,19 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event } // Make sure to remove the player from the logged in list. (See AuthProvider) + // And send a disconnected message { let players = state.ecs().read_storage::(); let mut accounts = state.ecs().write_resource::(); + let mut clients = state.ecs().write_storage::(); if let Some(player) = players.get(entity) { accounts.logout(player.uuid()); + + let msg = ServerMsg::broadcast(format!("{} went offline.", &player.alias)); + for client in (&mut clients).join().filter(|c| c.is_registered()) { + client.notify(msg.clone()); + } } } // Delete client entity diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index b524c22fbc..156e13b860 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -1,7 +1,7 @@ use super::SysTimer; use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT}; use common::{ - comp::{Admin, Body, CanBuild, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel}, + comp::{Admin, CanBuild, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel}, event::{EventBus, ServerEvent}, msg::{ validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate, @@ -27,7 +27,6 @@ impl<'a> System<'a> for Sys { ReadExpect<'a, TerrainGrid>, Write<'a, SysTimer>, ReadStorage<'a, Uid>, - ReadStorage<'a, Body>, ReadStorage<'a, CanBuild>, ReadStorage<'a, Admin>, ReadStorage<'a, ForceUpdate>, @@ -51,7 +50,6 @@ impl<'a> System<'a> for Sys { terrain, mut timer, uids, - bodies, can_build, admins, force_updates, @@ -81,7 +79,6 @@ impl<'a> System<'a> for Sys { let mut new_players = Vec::new(); for (entity, client) in (&entities, &mut clients).join() { - let mut disconnect = false; let new_msgs = client.postbox.new_messages(); // Update client ping. @@ -91,7 +88,7 @@ impl<'a> System<'a> for Sys { || client.postbox.error().is_some() // Postbox error { - disconnect = true; + server_emitter.emit(ServerEvent::ClientDisconnect(entity)); } else if time - client.last_ping > CLIENT_TIMEOUT * 0.5 { // Try pinging the client if the timeout is nearing. client.postbox.send_message(ServerMsg::Ping); @@ -285,25 +282,13 @@ impl<'a> System<'a> for Sys { ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong), ClientMsg::Pong => {}, ClientMsg::Disconnect => { - disconnect = true; + client.postbox.send_message(ServerMsg::Disconnect); + }, + ClientMsg::Terminate => { + server_emitter.emit(ServerEvent::ClientDisconnect(entity)); }, } } - - if disconnect { - if let (Some(player), Some(_)) = ( - players.get(entity), - // It only shows a message if you had a body (not in char selection) - bodies.get(entity), - ) { - new_chat_msgs.push(( - None, - ServerMsg::broadcast(format!("{} went offline.", &player.alias)), - )); - } - server_emitter.emit(ServerEvent::ClientDisconnect(entity)); - client.postbox.send_message(ServerMsg::Disconnect); - } } // Handle new players. diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index f7eaae27c2..23b43d7eb9 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -78,7 +78,7 @@ impl PlayState for CharSelectionState { char_data.body, char_data.tool, ); - return PlayStateResult::Push(Box::new(SessionState::new( + return PlayStateResult::Switch(Box::new(SessionState::new( global_state, self.client.clone(), ))); @@ -141,7 +141,7 @@ impl PlayState for CharSelectionState { ) { global_state.info_message = Some(localized_strings.get("common.connection_lost").to_owned()); - error!("[session] Failed to tick the scene: {:?}", err); + error!("[char_selection] Failed to tick the scene: {:?}", err); return PlayStateResult::Pop; } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 0d07361294..1d02998ccd 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -3,6 +3,7 @@ use crate::{ hud::{DebugInfo, Event as HudEvent, Hud}, i18n::{i18n_asset_key, VoxygenLocalization}, key_state::KeyState, + menu::char_selection::CharSelectionState, render::Renderer, scene::{camera, Scene, SceneData}, window::{AnalogGameInput, Event, GameInput}, @@ -24,6 +25,14 @@ use specs::{Join, WorldExt}; use std::{cell::RefCell, rc::Rc, time::Duration}; use vek::*; +/// The action to perform after a tick +enum TickAction { + // Continue executing + Continue, + // Disconnected (i.e. go to main menu) + Disconnect, +} + pub struct SessionState { scene: Scene, client: Rc>, @@ -65,7 +74,7 @@ impl SessionState { impl SessionState { /// Tick the session (and the client attached to it). - fn tick(&mut self, dt: Duration) -> Result<(), Error> { + fn tick(&mut self, dt: Duration) -> Result { self.inputs.tick(dt); for event in self.client.borrow_mut().tick( self.inputs.clone(), @@ -79,7 +88,7 @@ impl SessionState { } => { self.hud.new_message(event); }, - client::Event::Disconnect => {}, // TODO + client::Event::Disconnect => return Ok(TickAction::Disconnect), client::Event::DisconnectionNotification(time) => { let message = match time { 0 => String::from("Goodbye!"), @@ -94,7 +103,7 @@ impl SessionState { } } - Ok(()) + Ok(TickAction::Continue) } /// Clean up the session (and the client attached to it) after a tick. @@ -426,12 +435,16 @@ impl PlayState for SessionState { || !global_state.singleplayer.as_ref().unwrap().is_paused() { // Perform an in-game tick. - if let Err(err) = self.tick(clock.get_avg_delta()) { - global_state.info_message = - Some(localized_strings.get("common.connection_lost").to_owned()); - error!("[session] Failed to tick the scene: {:?}", err); + match self.tick(clock.get_avg_delta()) { + Ok(TickAction::Continue) => {}, // Do nothing + Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu + Err(err) => { + global_state.info_message = + Some(localized_strings.get("common.connection_lost").to_owned()); + error!("[session] Failed to tick the scene: {:?}", err); - return PlayStateResult::Pop; + return PlayStateResult::Pop; + }, } } @@ -704,6 +717,13 @@ impl PlayState for SessionState { current_client_state = self.client.borrow().get_client_state(); } + if let ClientState::Registered = current_client_state { + return PlayStateResult::Switch(Box::new(CharSelectionState::new( + global_state, + self.client.clone(), + ))); + } + PlayStateResult::Pop }