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)
This commit is contained in:
Vantha
2025-02-09 18:31:44 +01:00
parent 40762c257d
commit 78900842b1
5 changed files with 72 additions and 36 deletions
+60 -32
View File
@@ -58,35 +58,43 @@ function getValidPort(port)
/** /**
* Must be kept in sync with source/network/NetHost.h * Must be kept in sync with source/network/NetHost.h
*/ */
function getDisconnectReason(id, wasConnected) function getDisconnectReason(id)
{ {
switch (id) switch (id)
{ {
case 0: return wasConnected ? "" : case 0: return translate("An unknown error ocurred.");
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 connection request has timed out.");
case 1: return translate("The host has ended the game."); case 2: return translate("The host has ended the game.");
case 2: return translate("Incorrect network protocol version."); case 3: return translate("Incorrect network protocol version.");
case 3: return translate("Game is loading, please try again later."); case 4: return translate("Game is loading, please try again later.");
case 4: return translate("Game has already started, no observers allowed."); case 5: return translate("Game has already started, no observers allowed.");
case 5: return translate("You have been kicked."); case 6: return translate("You have been kicked.");
case 6: return translate("You have been banned."); case 7: 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("Player name in use. If you were disconnected, retry in few seconds.");
case 8: return translate("Server full."); case 9: return translate("Server full.");
case 9: return translate("Secure lobby authentication failed. Join via lobby."); case 10: 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: 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 commands were ready for an unexpected game turn.");
case 12: return translate("Error: Client simulated an unexpected game turn."); case 13: return translate("Error: Client simulated an unexpected game turn.");
case 13: return translate("Password is invalid."); case 14: 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 an unused port for the enet STUN client.");
case 15: return translate("Could not find the STUN endpoint."); case 16: return translate("Could not find the STUN endpoint.");
case 16: return translate("Different game engine versions or different mods loaded."); case 17: return translate("Different game engine versions or different mods loaded.");
default: default:
warn("Unknown disconnect-reason ID received: " + id); warn("Unknown disconnect-reason ID received: " + id);
return sprintf(translate("\\[Invalid value %(id)s]"), { "id": 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) switch (mismatchType)
{ {
@@ -107,26 +115,46 @@ function getHandshakeDisconnectMessage(mismatchType, clientMismatchInfo, serverM
/** /**
* Show the disconnect reason in a message box. * 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( if (message.reason === 0)
400, 200, reportConnectionRequestTimeOut();
(wasConnected ? else if (message.reason == 16)
translate("Lost connection to the server.") : reportMismatchingSoftwareVersions(message.mismatch_type, message.client_mismatch, message.server_mismatch);
translate("Failed to connect to the server.") else
) + "\n\n" + getDisconnectReason(reason, wasConnected), messageBox(
translate("Disconnected") 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( messageBox(
400, 200, 400, 200,
translate("Failed to connect to the server.") translate("Failed to connect to the server.")
+ "\n\n" + getHandshakeDisconnectMessage(mismatchType, clientMismatch, serverMismatch), + "\n\n" + getMismatchMessage(mismatchType, clientMismatch, serverMismatch),
translate("Disconnected") translate("Disconnected")
); );
} }
@@ -317,8 +317,8 @@ function pollAndHandleNetworkClient(loadSavedGame)
if (message.reason === 16) if (message.reason === 16)
reportHandshakeDisconnect(message.mismatch_type, message.client_mismatch_component, message.server_mismatch_component); reportHandshakeDisconnect(message.mismatch_type, message.client_mismatch_component, message.server_mismatch_component);
else else
reportDisconnect(message.reason, false); reportDisconnect(message, false);
return true; return;
default: default:
error("Unrecognised netstatus type: " + message.status); error("Unrecognised netstatus type: " + message.status);
+1
View File
@@ -62,6 +62,7 @@ typedef std::map<CStr, PlayerAssignment> PlayerAssignmentMap; // map from GUID -
enum NetDisconnectReason enum NetDisconnectReason
{ {
NDR_UNKNOWN = 0, NDR_UNKNOWN = 0,
NDR_CONNECTION_REQUEST_TIMED_OUT,
NDR_SERVER_SHUTDOWN, NDR_SERVER_SHUTDOWN,
NDR_INCORRECT_PROTOCOL_VERSION, NDR_INCORRECT_PROTOCOL_VERSION,
NDR_SERVER_LOADING, NDR_SERVER_LOADING,
+5 -1
View File
@@ -135,6 +135,7 @@ void CNetClientSession::Poll()
enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname)); 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); LOGMESSAGE("Net client: Connected to %s:%u", hostname, (unsigned int)event.peer->address.port);
m_Connected = true; m_Connected = true;
m_WasConnected = true;
m_IncomingMessages.push(event); m_IncomingMessages.push(event);
} }
@@ -178,7 +179,10 @@ void CNetClientSession::ProcessPolledMessages()
else if (event.type == ENET_EVENT_TYPE_DISCONNECT) else if (event.type == ENET_EVENT_TYPE_DISCONNECT)
{ {
// This deletes the session, so we must break; // 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; break;
} }
else if (event.type == ENET_EVENT_TYPE_RECEIVE) else if (event.type == ENET_EVENT_TYPE_RECEIVE)
+4 -1
View File
@@ -126,9 +126,12 @@ private:
// Net messages to send on the next flush() call. // Net messages to send on the next flush() call.
boost::lockfree::queue<ENetPacket*> m_OutgoingMessages; boost::lockfree::queue<ENetPacket*> m_OutgoingMessages;
// Last know state. If false, flushing errors are silenced. // Last known state. If false, flushing errors are silenced.
bool m_Connected = false; 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. // Wrapper around enet stats - those are atomic as the code is lock-free.
std::atomic<u32> m_LastReceivedTime; std::atomic<u32> m_LastReceivedTime;
std::atomic<u32> m_MeanRTT; std::atomic<u32> m_MeanRTT;