From 78900842b1e0c45a333dff431c4a4505ca5b69db Mon Sep 17 00:00:00 2001 From: Vantha Date: Sun, 9 Feb 2025 18:31:44 +0100 Subject: [PATCH] Improve the "UDP port 20595" connection error dialog Add a unique disconnect reason for timeouts of connection attempts. Rewrite the displayed error message and provide a link directly to the FAQ entry. (The old message was very misleading and only brought players on the wrong track during troubleshooting) --- .../data/mods/public/gui/common/network.js | 92 ++++++++++++------- .../public/gui/gamesetup_mp/gamesetup_mp.js | 4 +- source/network/NetHost.h | 1 + source/network/NetSession.cpp | 6 +- source/network/NetSession.h | 5 +- 5 files changed, 72 insertions(+), 36 deletions(-) diff --git a/binaries/data/mods/public/gui/common/network.js b/binaries/data/mods/public/gui/common/network.js index 4b037400c4..43e484d6f4 100644 --- a/binaries/data/mods/public/gui/common/network.js +++ b/binaries/data/mods/public/gui/common/network.js @@ -58,35 +58,43 @@ function getValidPort(port) /** * Must be kept in sync with source/network/NetHost.h */ -function getDisconnectReason(id, wasConnected) +function getDisconnectReason(id) { switch (id) { - case 0: return wasConnected ? "" : - translate("This is often caused by UDP port 20595 not being forwarded on the host side, by a firewall, or anti-virus software."); - case 1: return translate("The host has ended the game."); - case 2: return translate("Incorrect network protocol version."); - case 3: return translate("Game is loading, please try again later."); - case 4: return translate("Game has already started, no observers allowed."); - case 5: return translate("You have been kicked."); - case 6: return translate("You have been banned."); - case 7: return translate("Player name in use. If you were disconnected, retry in few seconds."); - case 8: return translate("Server full."); - case 9: return translate("Secure lobby authentication failed. Join via lobby."); - case 10: return translate("Error: Server failed to allocate a unique client identifier."); - case 11: return translate("Error: Client commands were ready for an unexpected game turn."); - case 12: return translate("Error: Client simulated an unexpected game turn."); - case 13: return translate("Password is invalid."); - case 14: return translate("Could not find an unused port for the enet STUN client."); - case 15: return translate("Could not find the STUN endpoint."); - case 16: return translate("Different game engine versions or different mods loaded."); + case 0: return translate("An unknown error ocurred."); + case 1: return translate("The connection request has timed out."); + case 2: return translate("The host has ended the game."); + case 3: return translate("Incorrect network protocol version."); + case 4: return translate("Game is loading, please try again later."); + case 5: return translate("Game has already started, no observers allowed."); + case 6: return translate("You have been kicked."); + case 7: return translate("You have been banned."); + case 8: return translate("Player name in use. If you were disconnected, retry in few seconds."); + case 9: return translate("Server full."); + case 10: return translate("Secure lobby authentication failed. Join via lobby."); + case 11: return translate("Error: Server failed to allocate a unique client identifier."); + case 12: return translate("Error: Client commands were ready for an unexpected game turn."); + case 13: return translate("Error: Client simulated an unexpected game turn."); + case 14: return translate("Password is invalid."); + case 15: return translate("Could not find an unused port for the enet STUN client."); + case 16: return translate("Could not find the STUN endpoint."); + case 17: return translate("Different game engine versions or different mods loaded."); + default: warn("Unknown disconnect-reason ID received: " + id); return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id }); } } -function getHandshakeDisconnectMessage(mismatchType, clientMismatchInfo, serverMismatchInfo) +function getRequestTimeOutMessage() +{ + return (Engine.HasXmppClient() ? "" : translate("Please ensure that you entered the correct server name or IP address, and port number.\n\n")) + + translate("If you haven't yet, try to connect again and to different hosts." + + "\nIf the issue persists or occurs regularly, visit the official FAQ for detailed guidance and troubleshooting steps."); +} + +function getMismatchMessage(mismatchType, clientMismatchInfo, serverMismatchInfo) { switch (mismatchType) { @@ -107,26 +115,46 @@ function getHandshakeDisconnectMessage(mismatchType, clientMismatchInfo, serverM /** * Show the disconnect reason in a message box. * - * @param {number} reason + * @param {Object} message + * @param {boolean} wasConnected */ -function reportDisconnect(reason, wasConnected) +function reportDisconnect(message, wasConnected) { - messageBox( - 400, 200, - (wasConnected ? - translate("Lost connection to the server.") : - translate("Failed to connect to the server.") - ) + "\n\n" + getDisconnectReason(reason, wasConnected), - translate("Disconnected") - ); + if (message.reason === 0) + reportConnectionRequestTimeOut(); + else if (message.reason == 16) + reportMismatchingSoftwareVersions(message.mismatch_type, message.client_mismatch, message.server_mismatch); + else + messageBox( + 400, 200, + (wasConnected ? + translate("Lost connection to the server.") : + translate("Failed to connect to the server.") + ) + "\n\n" + getDisconnectReason(message.reason), + translate("Disconnected") + ); } -function reportHandshakeDisconnect(mismatchType, clientMismatch, serverMismatch) +async function reportConnectionRequestTimeOut() +{ + const buttonIndex = await messageBox( + 600, 230, + translate("Failed to connect to the server.") + " " + getDisconnectReason(1) + "\n\n" + getRequestTimeOutMessage(), + translate("Connection Error"), + [translate("Close"), translate("Open FAQ")] + ); + if (buttonIndex === 1) + openURL("https://gitea.wildfiregames.com/0ad/0ad/wiki/FAQ#what-shall-i-do-when-joining-multiplayer-matches-fails-with-an-error-message"); + + return; +} + +function reportMismatchingSoftwareVersions(mismatchType, clientMismatch, serverMismatch) { messageBox( 400, 200, translate("Failed to connect to the server.") - + "\n\n" + getHandshakeDisconnectMessage(mismatchType, clientMismatch, serverMismatch), + + "\n\n" + getMismatchMessage(mismatchType, clientMismatch, serverMismatch), translate("Disconnected") ); } diff --git a/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js b/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js index 6433822af8..368a44c86e 100644 --- a/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js +++ b/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js @@ -317,8 +317,8 @@ function pollAndHandleNetworkClient(loadSavedGame) if (message.reason === 16) reportHandshakeDisconnect(message.mismatch_type, message.client_mismatch_component, message.server_mismatch_component); else - reportDisconnect(message.reason, false); - return true; + reportDisconnect(message, false); + return; default: error("Unrecognised netstatus type: " + message.status); diff --git a/source/network/NetHost.h b/source/network/NetHost.h index d2cdd0a573..47584f45a6 100644 --- a/source/network/NetHost.h +++ b/source/network/NetHost.h @@ -62,6 +62,7 @@ typedef std::map PlayerAssignmentMap; // map from GUID - enum NetDisconnectReason { NDR_UNKNOWN = 0, + NDR_CONNECTION_REQUEST_TIMED_OUT, NDR_SERVER_SHUTDOWN, NDR_INCORRECT_PROTOCOL_VERSION, NDR_SERVER_LOADING, diff --git a/source/network/NetSession.cpp b/source/network/NetSession.cpp index 952e748048..60d9b61fd0 100644 --- a/source/network/NetSession.cpp +++ b/source/network/NetSession.cpp @@ -135,6 +135,7 @@ void CNetClientSession::Poll() enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname)); LOGMESSAGE("Net client: Connected to %s:%u", hostname, (unsigned int)event.peer->address.port); m_Connected = true; + m_WasConnected = true; m_IncomingMessages.push(event); } @@ -178,7 +179,10 @@ void CNetClientSession::ProcessPolledMessages() else if (event.type == ENET_EVENT_TYPE_DISCONNECT) { // This deletes the session, so we must break; - m_Client.HandleDisconnect(event.data); + if (event.data == 0 && !m_WasConnected) + m_Client.HandleDisconnect(NDR_CONNECTION_REQUEST_TIMED_OUT); + else + m_Client.HandleDisconnect(event.data); break; } else if (event.type == ENET_EVENT_TYPE_RECEIVE) diff --git a/source/network/NetSession.h b/source/network/NetSession.h index 5f079d8090..a5ee4c2b33 100644 --- a/source/network/NetSession.h +++ b/source/network/NetSession.h @@ -126,9 +126,12 @@ private: // Net messages to send on the next flush() call. boost::lockfree::queue m_OutgoingMessages; - // Last know state. If false, flushing errors are silenced. + // Last known state. If false, flushing errors are silenced. bool m_Connected = false; + // Whether this session was ever connected to the server. + bool m_WasConnected = false; + // Wrapper around enet stats - those are atomic as the code is lock-free. std::atomic m_LastReceivedTime; std::atomic m_MeanRTT;