Added client/server version mismatch message when a network error is encountered during client init.

Added warning banner on character select when successfully connected to a server with a mismatched version.
This commit is contained in:
Ben Wallis 2021-04-22 18:27:38 +01:00 committed by Avi Weinstock
parent 53025a861e
commit 76daf7a395
9 changed files with 137 additions and 42 deletions

View File

@ -24,6 +24,7 @@
"char_selection.eyeshape": "Eye Details",
"char_selection.accessories": "Accessories",
"char_selection.create_info_name": "Your Character needs a name!",
"char_selection.version_mismatch": "WARNING! This server is running a different, possibly incompatible game version. Please update your game."
},
vector_map: {

View File

@ -50,7 +50,7 @@ https://veloren.net/account/."#,
"main.login.timeout": "Timeout: Server did not respond in time. (Overloaded or network issues).",
"main.login.server_shut_down": "Server shut down",
"main.login.network_error": "Network error",
"main.login.network_wrong_version": "The server is running a different version than you are. Check your version and update your game.",
"main.login.network_wrong_version": "Mismatched server and client version, please update your game client.",
"main.login.failed_sending_request": "Request to Auth server failed",
"main.login.invalid_character": "The selected character is invalid",
"main.login.client_crashed": "Client crashed",
@ -58,7 +58,8 @@ https://veloren.net/account/."#,
"main.login.banned": "You have been banned with the following reason",
"main.login.kicked": "You have been kicked with the following reason",
"main.login.select_language": "Select a language",
"main.login.client_version": "Client Version",
"main.login.server_version": "Server Version",
"main.servers.select_server": "Select a server",
/// End Main screen section
},

View File

@ -52,7 +52,7 @@ fn main() {
let addr = ConnectionArgs::resolve(&server_addr, false)
.await
.expect("dns resolve failed");
Client::new(addr, None, runtime2).await
Client::new(addr, None, runtime2, &mut None).await
})
.expect("Failed to create client instance");

View File

@ -59,7 +59,7 @@ pub fn make_client(runtime: &Arc<Runtime>, server: &str) -> Client {
let connection_args = ConnectionArgs::resolve(server, false)
.await
.expect("DNS resolution failed");
Client::new(connection_args, view_distance, runtime2)
Client::new(connection_args, view_distance, runtime2, &mut None)
.await
.expect("Failed to connect to server")
})

View File

@ -9,6 +9,7 @@ pub mod error;
// Reexports
pub use crate::error::Error;
pub use authc::AuthClientError;
pub use common_net::msg::ServerInfo;
pub use specs::{
join::Join,
saveload::{Marker, MarkerAllocator},
@ -49,7 +50,7 @@ use common_net::{
world_msg::{EconomyInfo, SiteId, SiteInfo},
ChatMsgValidationError, ClientGeneral, ClientMsg, ClientRegister, ClientType,
DisconnectReason, InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate,
PresenceKind, RegisterError, ServerGeneral, ServerInfo, ServerInit, ServerRegisterAnswer,
PresenceKind, RegisterError, ServerGeneral, ServerInit, ServerRegisterAnswer,
MAX_BYTES_CHAT_MSG,
},
sync::WorldSyncExt,
@ -66,6 +67,7 @@ use rayon::prelude::*;
use specs::Component;
use std::{
collections::{BTreeMap, VecDeque},
mem,
sync::Arc,
time::{Duration, Instant},
};
@ -204,6 +206,8 @@ impl Client {
addr: ConnectionArgs,
view_distance: Option<u32>,
runtime: Arc<Runtime>,
// TODO: refactor to avoid needing to use this out parameter
mismatched_server_info: &mut Option<ServerInfo>,
) -> Result<Self, Error> {
let network = Network::new(Pid::new(), &runtime);
@ -235,8 +239,6 @@ impl Client {
register_stream.send(ClientType::Game)?;
let server_info: ServerInfo = register_stream.recv().await?;
// TODO: Display that versions don't match in Voxygen
if server_info.git_hash != *common::util::GIT_HASH {
warn!(
"Server is running {}[{}], you are running {}[{}], versions might be incompatible!",
@ -245,6 +247,10 @@ impl Client {
common::util::GIT_HASH.to_string(),
common::util::GIT_DATE.to_string(),
);
// Pass the server info back to the caller to ensure they can access it even
// if this function errors.
mem::swap(mismatched_server_info, &mut Some(server_info.clone()));
}
debug!("Auth Server: {:?}", server_info.auth_provider);
@ -2449,6 +2455,7 @@ mod tests {
ConnectionArgs::IpAndPort(vec![socket]),
view_distance,
runtime2,
&mut None,
));
let _ = veloren_client.map(|mut client| {

View File

@ -1055,7 +1055,7 @@ where
handle.spawn(async move {
match finished_receiver.await {
Ok(data) => f(data),
Err(e) => panic!("{}{}: {}", name, CHANNEL_ERR, e),
Err(e) => error!("{}{}: {}", name, CHANNEL_ERR, e),
}
});
} else {

View File

@ -20,7 +20,7 @@ use crate::{
},
window, GlobalState,
};
use client::Client;
use client::{Client, ServerInfo};
use common::{
assets::AssetHandle,
character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER, MAX_NAME_LENGTH},
@ -223,7 +223,7 @@ struct Controls {
version: String,
// Alpha disclaimer
alpha: String,
server_mismatched_version: Option<String>,
tooltip_manager: TooltipManager,
// Zone for rotating the character with the mouse
mouse_detector: mouse_detector::State,
@ -264,16 +264,25 @@ enum Message {
}
impl Controls {
fn new(fonts: Fonts, imgs: Imgs, selected: Option<CharacterId>, default_name: String) -> Self {
fn new(
fonts: Fonts,
imgs: Imgs,
selected: Option<CharacterId>,
default_name: String,
server_info: &ServerInfo,
) -> Self {
let version = common::util::DISPLAY_VERSION_LONG.clone();
let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str());
let server_mismatched_version = (common::util::GIT_HASH.to_string()
!= server_info.git_hash)
.then(|| server_info.git_hash.clone());
Self {
fonts,
imgs,
version,
alpha,
server_mismatched_version,
tooltip_manager: TooltipManager::new(TOOLTIP_HOVER_DUR, TOOLTIP_FADE_DUR),
mouse_detector: Default::default(),
mode: Mode::select(Some(InfoContent::LoadingCharacters)),
@ -1210,14 +1219,46 @@ impl Controls {
},
};
Container::new(
Column::with_children(vec![top_text.into(), content])
.spacing(3)
.width(Length::Fill)
.height(Length::Fill),
)
.padding(3)
.into()
// TODO: There is probably a better way to conditionally add in the warning box
// here
if let Some(mismatched_version) = &self.server_mismatched_version {
let warning = iced::Text::<IcedRenderer>::new(format!(
"{}\n{}: {} {}: {}",
i18n.get("char_selection.version_mismatch"),
i18n.get("main.login.server_version"),
mismatched_version,
i18n.get("main.login.client_version"),
common::util::GIT_HASH.to_string()
))
.size(self.fonts.cyri.scale(18))
.color(iced::Color::from_rgb(1.0, 0.0, 0.0))
.width(Length::Fill)
.horizontal_alignment(HorizontalAlignment::Center);
let warning_container =
Container::new(Row::with_children(vec![warning.into()]).width(Length::Fill))
.style(style::container::Style::color(Rgba::new(0, 0, 0, 217)))
.padding(12)
.center_x()
.width(Length::Fill);
Container::new(
Column::with_children(vec![top_text.into(), warning_container.into(), content])
.spacing(3)
.width(Length::Fill)
.height(Length::Fill),
)
.padding(3)
.into()
} else {
Container::new(
Column::with_children(vec![top_text.into(), content])
.spacing(3)
.width(Length::Fill)
.height(Length::Fill),
)
.padding(3)
.into()
}
}
fn update(&mut self, message: Message, events: &mut Vec<Event>, characters: &[CharacterItem]) {
@ -1451,6 +1492,7 @@ impl CharSelectionUi {
Imgs::load(&mut ui).expect("Failed to load images"),
selected_character,
default_name,
client.server_info(),
);
Self {

View File

@ -1,7 +1,7 @@
use client::{
addr::ConnectionArgs,
error::{Error as ClientError, NetworkConnectError, NetworkError},
Client,
Client, ServerInfo,
};
use crossbeam::channel::{unbounded, Receiver, Sender, TryRecvError};
use std::{
@ -17,7 +17,10 @@ use tracing::{trace, warn};
#[derive(Debug)]
pub enum Error {
NoAddress,
ClientError(ClientError),
ClientError {
error: ClientError,
mismatched_server_info: Option<ServerInfo>,
},
ClientCrashed,
}
@ -103,16 +106,21 @@ impl ClientInit {
if cancel2.load(Ordering::Relaxed) {
break;
}
let mut mismatched_server_info = None;
match Client::new(
connection_args.clone(),
view_distance,
Arc::clone(&runtime2),
&mut mismatched_server_info,
)
.await
{
Ok(mut client) => {
if let Err(e) = client.register(username, password, trust_fn).await {
last_err = Some(Error::ClientError(e));
last_err = Some(Error::ClientError {
error: e,
mismatched_server_info: None,
});
break 'tries;
}
let _ = tx.send(Msg::Done(Ok(client)));
@ -126,7 +134,10 @@ impl ClientInit {
},
Err(e) => {
trace!(?e, "Aborting server connection attempt");
last_err = Some(Error::ClientError(e));
last_err = Some(Error::ClientError {
error: e,
mismatched_server_info,
});
break 'tries;
},
}

View File

@ -14,12 +14,15 @@ use crate::{
};
#[cfg(feature = "singleplayer")]
use client::addr::ConnectionArgs;
use client::error::{InitProtocolError, NetworkConnectError, NetworkError};
use client::{
error::{InitProtocolError, NetworkConnectError, NetworkError},
ServerInfo,
};
use client_init::{ClientConnArgs, ClientInit, Error as InitError, Msg as InitMsg};
use common::{assets::AssetExt, comp};
use common_base::span;
use scene::Scene;
use std::sync::Arc;
use std::{fmt::Debug, sync::Arc};
use tokio::runtime;
use tracing::error;
use ui::{Event as MainMenuEvent, MainMenuUi};
@ -129,7 +132,10 @@ impl PlayState for MainMenuState {
InitError::NoAddress => {
localized_strings.get("main.login.server_not_found").into()
},
InitError::ClientError(err) => match err {
InitError::ClientError {
error,
mismatched_server_info,
} => match error {
client::Error::SpecsErr(e) => format!(
"{}: {}",
localized_strings.get("main.login.internal_error"),
@ -169,23 +175,25 @@ impl PlayState for MainMenuState {
},
client::Error::NetworkErr(NetworkError::ConnectFailed(
NetworkConnectError::Handshake(InitProtocolError::WrongVersion(_)),
)) => localized_strings
.get("main.login.network_wrong_version")
.into(),
client::Error::NetworkErr(e) => format!(
"{}: {:?}",
localized_strings.get("main.login.network_error"),
e
)) => get_network_error_text(
&localized_strings,
localized_strings.get("main.login.network_wrong_version"),
mismatched_server_info,
),
client::Error::ParticipantErr(e) => format!(
"{}: {:?}",
localized_strings.get("main.login.network_error"),
e
client::Error::NetworkErr(e) => get_network_error_text(
&localized_strings,
e,
mismatched_server_info,
),
client::Error::StreamErr(e) => format!(
"{}: {:?}",
localized_strings.get("main.login.network_error"),
e
client::Error::ParticipantErr(e) => get_network_error_text(
&localized_strings,
e,
mismatched_server_info,
),
client::Error::StreamErr(e) => get_network_error_text(
&localized_strings,
e,
mismatched_server_info,
),
client::Error::Other(e) => {
format!("{}: {}", localized_strings.get("common.error"), e)
@ -349,6 +357,31 @@ impl PlayState for MainMenuState {
}
}
/// When a network error is received and there is a mismatch between the client
/// and server version it is almost definitely due to this mismatch rather than
/// a true networking error.
fn get_network_error_text(
localization: &Localization,
error: impl Debug,
mismatched_server_info: Option<ServerInfo>,
) -> String {
if let Some(server_info) = mismatched_server_info {
format!(
"{} {}: {} {}: {}",
localization.get("main.login.network_wrong_version"),
localization.get("main.login.client_version"),
common::util::GIT_HASH.to_string(),
localization.get("main.login.server_version"),
server_info.git_hash
)
} else {
format!(
"{}: {:?}",
localization.get("main.login.network_error"),
error
)
}
}
fn attempt_login(
settings: &mut Settings,
info_message: &mut Option<String>,