summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatric Stout <truebrain@openttd.org>2021-04-29 15:37:02 +0200
committerPatric Stout <github@truebrain.nl>2021-07-11 20:38:42 +0200
commite4d216e44b4a5d87094b4478ea4cf18109f99a35 (patch)
tree837b4b027ebaf0a328d0336ac93c110379a015ba
parent1baec41542780cf4fc898df7d2fc9925d823fb44 (diff)
downloadopenttd-e4d216e44b4a5d87094b4478ea4cf18109f99a35.tar.xz
Feature: join servers based on their invite code
This removes the need to know a server IP to join it. Invite codes are small (~7 characters) indentifiers for servers, which can be exchanged with other players to join the servers.
-rw-r--r--src/console_cmds.cpp1
-rw-r--r--src/lang/english.txt6
-rw-r--r--src/network/core/config.h7
-rw-r--r--src/network/core/game_info.cpp6
-rw-r--r--src/network/core/tcp_connect.cpp4
-rw-r--r--src/network/core/tcp_coordinator.cpp24
-rw-r--r--src/network/core/tcp_coordinator.h99
-rw-r--r--src/network/network.cpp10
-rw-r--r--src/network/network_coordinator.cpp239
-rw-r--r--src/network/network_coordinator.h24
-rw-r--r--src/network/network_gui.cpp13
-rw-r--r--src/network/network_internal.h1
-rw-r--r--src/settings_type.h2
-rw-r--r--src/table/settings/network_secrets_settings.ini14
-rw-r--r--src/widgets/network_widget.h1
15 files changed, 422 insertions, 29 deletions
diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp
index 9d03f3deb..0fcac4488 100644
--- a/src/console_cmds.cpp
+++ b/src/console_cmds.cpp
@@ -702,6 +702,7 @@ DEF_CONSOLE_CMD(ConServerInfo)
return true;
}
+ IConsolePrint(CC_DEFAULT, "Invite code: {}", _network_server_invite_code);
IConsolePrint(CC_DEFAULT, "Current/maximum clients: {:3d}/{:3d}", _network_game_info.clients_on, _settings_client.network.max_clients);
IConsolePrint(CC_DEFAULT, "Current/maximum companies: {:3d}/{:3d}", Company::GetNumItems(), _settings_client.network.max_companies);
IConsolePrint(CC_DEFAULT, "Current/maximum spectators: {:3d}/{:3d}", NetworkSpectatorCount(), _settings_client.network.max_spectators);
diff --git a/src/lang/english.txt b/src/lang/english.txt
index 2e84d4b3a..813705958 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -2045,12 +2045,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}Search i
STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}Search LAN
STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK}Search local area network for servers
STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Add server
-STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Adds a server to the list which will always be checked for running games
+STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Adds a server to the list. This can either be a server address or an invite code
STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Start server
STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Start your own server
STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Enter your name
-STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Enter the address of the host
+STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}Enter server address or invite code
# Start new multiplayer server
STR_NETWORK_START_SERVER_CAPTION :{WHITE}Start new multiplayer game
@@ -2136,6 +2136,8 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Edit the
STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Name of the server
STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Visibility
STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Whether other people can see your server in the public listing
+STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}Invite code
+STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}Invite code other players can use to join this server
STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Connection type
STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Whether and how your server can be reached by others
STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Player
diff --git a/src/network/core/config.h b/src/network/core/config.h
index d37210e19..06df93140 100644
--- a/src/network/core/config.h
+++ b/src/network/core/config.h
@@ -47,7 +47,7 @@ static const uint16 COMPAT_MTU = 1460; ///< Numbe
static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use?
static const byte NETWORK_GAME_INFO_VERSION = 4; ///< What version of game-info do we use?
static const byte NETWORK_COMPANY_INFO_VERSION = 6; ///< What version of company info is this?
-static const byte NETWORK_COORDINATOR_VERSION = 1; ///< What version of game-coordinator-protocol do we use?
+static const byte NETWORK_COORDINATOR_VERSION = 2; ///< What version of game-coordinator-protocol do we use?
static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0'
static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0'
@@ -67,7 +67,10 @@ static const uint NETWORK_CONTENT_VERSION_LENGTH = 16; ///< The m
static const uint NETWORK_CONTENT_URL_LENGTH = 96; ///< The maximum length of a content's url, in bytes including '\0'.
static const uint NETWORK_CONTENT_DESC_LENGTH = 512; ///< The maximum length of a content's description, in bytes including '\0'.
static const uint NETWORK_CONTENT_TAG_LENGTH = 32; ///< The maximum length of a content's tag, in bytes including '\0'.
-static const uint NETWORK_ERROR_DETAIL_LENGTH = 100; ///< The maximum length of the error detail, in bytes including '\0'
+static const uint NETWORK_ERROR_DETAIL_LENGTH = 100; ///< The maximum length of the error detail, in bytes including '\0'.
+static const uint NETWORK_INVITE_CODE_LENGTH = 64; ///< The maximum length of the invite code, in bytes including '\0'.
+static const uint NETWORK_INVITE_CODE_SECRET_LENGTH = 80; ///< The maximum length of the invite code secret, in bytes including '\0'.
+static const uint NETWORK_TOKEN_LENGTH = 64; ///< The maximum length of a token, in bytes including '\0'.
static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF
diff --git a/src/network/core/game_info.cpp b/src/network/core/game_info.cpp
index b4c487fba..17f00c5f5 100644
--- a/src/network/core/game_info.cpp
+++ b/src/network/core/game_info.cpp
@@ -141,7 +141,11 @@ void FillStaticNetworkServerGameInfo()
*/
const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo()
{
- /* Client_on is used as global variable to keep track on the number of clients. */
+ /* These variables are updated inside _network_game_info as if they are global variables:
+ * - clients_on
+ * - invite_code
+ * These don't need to be updated manually here.
+ */
_network_game_info.companies_on = (byte)Company::GetNumItems();
_network_game_info.spectators_on = NetworkSpectatorCount();
_network_game_info.game_date = _date;
diff --git a/src/network/core/tcp_connect.cpp b/src/network/core/tcp_connect.cpp
index d9b6bb781..0e8e2e125 100644
--- a/src/network/core/tcp_connect.cpp
+++ b/src/network/core/tcp_connect.cpp
@@ -13,6 +13,7 @@
#include "../../thread.h"
#include "tcp.h"
+#include "../network_coordinator.h"
#include "../network_internal.h"
#include <deque>
@@ -48,8 +49,7 @@ TCPServerConnecter::TCPServerConnecter(const std::string &connection_string, uin
case SERVER_ADDRESS_INVITE_CODE:
this->status = Status::CONNECTING;
-
- // TODO -- The next commit will add this functionality.
+ _network_coordinator_client.ConnectToServer(this->server_address.connection_string, this);
break;
default:
diff --git a/src/network/core/tcp_coordinator.cpp b/src/network/core/tcp_coordinator.cpp
index bbcb59b14..dfd73147e 100644
--- a/src/network/core/tcp_coordinator.cpp
+++ b/src/network/core/tcp_coordinator.cpp
@@ -27,12 +27,18 @@ bool NetworkCoordinatorSocketHandler::HandlePacket(Packet *p)
PacketCoordinatorType type = (PacketCoordinatorType)p->Recv_uint8();
switch (type) {
- case PACKET_COORDINATOR_GC_ERROR: return this->Receive_GC_ERROR(p);
- case PACKET_COORDINATOR_SERVER_REGISTER: return this->Receive_SERVER_REGISTER(p);
- case PACKET_COORDINATOR_GC_REGISTER_ACK: return this->Receive_GC_REGISTER_ACK(p);
- case PACKET_COORDINATOR_SERVER_UPDATE: return this->Receive_SERVER_UPDATE(p);
- case PACKET_COORDINATOR_CLIENT_LISTING: return this->Receive_CLIENT_LISTING(p);
- case PACKET_COORDINATOR_GC_LISTING: return this->Receive_GC_LISTING(p);
+ case PACKET_COORDINATOR_GC_ERROR: return this->Receive_GC_ERROR(p);
+ case PACKET_COORDINATOR_SERVER_REGISTER: return this->Receive_SERVER_REGISTER(p);
+ case PACKET_COORDINATOR_GC_REGISTER_ACK: return this->Receive_GC_REGISTER_ACK(p);
+ case PACKET_COORDINATOR_SERVER_UPDATE: return this->Receive_SERVER_UPDATE(p);
+ case PACKET_COORDINATOR_CLIENT_LISTING: return this->Receive_CLIENT_LISTING(p);
+ case PACKET_COORDINATOR_GC_LISTING: return this->Receive_GC_LISTING(p);
+ case PACKET_COORDINATOR_CLIENT_CONNECT: return this->Receive_CLIENT_CONNECT(p);
+ case PACKET_COORDINATOR_GC_CONNECTING: return this->Receive_GC_CONNECTING(p);
+ case PACKET_COORDINATOR_SERCLI_CONNECT_FAILED: return this->Receive_SERCLI_CONNECT_FAILED(p);
+ case PACKET_COORDINATOR_GC_CONNECT_FAILED: return this->Receive_GC_CONNECT_FAILED(p);
+ case PACKET_COORDINATOR_CLIENT_CONNECTED: return this->Receive_CLIENT_CONNECTED(p);
+ case PACKET_COORDINATOR_GC_DIRECT_CONNECT: return this->Receive_GC_DIRECT_CONNECT(p);
default:
Debug(net, 0, "[tcp/coordinator] Received invalid packet type {}", type);
@@ -82,3 +88,9 @@ bool NetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) { retur
bool NetworkCoordinatorSocketHandler::Receive_SERVER_UPDATE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_UPDATE); }
bool NetworkCoordinatorSocketHandler::Receive_CLIENT_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_LISTING); }
bool NetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_LISTING); }
+bool NetworkCoordinatorSocketHandler::Receive_CLIENT_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_CONNECT); }
+bool NetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_CONNECTING); }
+bool NetworkCoordinatorSocketHandler::Receive_SERCLI_CONNECT_FAILED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED); }
+bool NetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_CONNECT_FAILED); }
+bool NetworkCoordinatorSocketHandler::Receive_CLIENT_CONNECTED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_CONNECTED); }
+bool NetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_DIRECT_CONNECT); }
diff --git a/src/network/core/tcp_coordinator.h b/src/network/core/tcp_coordinator.h
index e95916816..2d793b1b6 100644
--- a/src/network/core/tcp_coordinator.h
+++ b/src/network/core/tcp_coordinator.h
@@ -23,15 +23,22 @@
* GC -> packets from Game Coordinator to either Client or Server.
* SERVER -> packets from Server to Game Coordinator.
* CLIENT -> packets from Client to Game Coordinator.
+ * SERCLI -> packets from either the Server or Client to Game Coordinator.
**/
enum PacketCoordinatorType {
- PACKET_COORDINATOR_GC_ERROR, ///< Game Coordinator indicates there was an error.
- PACKET_COORDINATOR_SERVER_REGISTER, ///< Server registration.
- PACKET_COORDINATOR_GC_REGISTER_ACK, ///< Game Coordinator accepts the registration.
- PACKET_COORDINATOR_SERVER_UPDATE, ///< Server sends an set intervals an update of the server.
- PACKET_COORDINATOR_CLIENT_LISTING, ///< Client is requesting a listing of all public servers.
- PACKET_COORDINATOR_GC_LISTING, ///< Game Coordinator returns a listing of all public servers.
- PACKET_COORDINATOR_END, ///< Must ALWAYS be on the end of this list!! (period).
+ PACKET_COORDINATOR_GC_ERROR, ///< Game Coordinator indicates there was an error.
+ PACKET_COORDINATOR_SERVER_REGISTER, ///< Server registration.
+ PACKET_COORDINATOR_GC_REGISTER_ACK, ///< Game Coordinator accepts the registration.
+ PACKET_COORDINATOR_SERVER_UPDATE, ///< Server sends an set intervals an update of the server.
+ PACKET_COORDINATOR_CLIENT_LISTING, ///< Client is requesting a listing of all public servers.
+ PACKET_COORDINATOR_GC_LISTING, ///< Game Coordinator returns a listing of all public servers.
+ PACKET_COORDINATOR_CLIENT_CONNECT, ///< Client wants to connect to a server based on an invite code.
+ PACKET_COORDINATOR_GC_CONNECTING, ///< Game Coordinator informs the client of the token assigned to the connection attempt.
+ PACKET_COORDINATOR_SERCLI_CONNECT_FAILED, ///< Client/server tells the Game Coordinator the current connection attempt failed.
+ PACKET_COORDINATOR_GC_CONNECT_FAILED, ///< Game Coordinator informs client/server it has given up on the connection attempt.
+ PACKET_COORDINATOR_CLIENT_CONNECTED, ///< Client informs the Game Coordinator the connection with the server is established.
+ PACKET_COORDINATOR_GC_DIRECT_CONNECT, ///< Game Coordinator tells client to directly connect to the hostname:port of the server.
+ PACKET_COORDINATOR_END, ///< Must ALWAYS be on the end of this list!! (period)
};
/**
@@ -49,6 +56,7 @@ enum ConnectionType {
enum NetworkCoordinatorErrorType {
NETWORK_COORDINATOR_ERROR_UNKNOWN, ///< There was an unknown error.
NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED, ///< Your request for registration failed.
+ NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE, ///< The invite code given is invalid.
};
/** Base socket handler for all Game Coordinator TCP sockets. */
@@ -76,6 +84,8 @@ protected:
* uint8 Game Coordinator protocol version.
* uint8 Type of game (see ServerGameType).
* uint16 Local port of the server.
+ * string Invite code the server wants to use (can be empty; coordinator will assign a new invite code).
+ * string Secret that belongs to the invite code (empty if invite code is empty).
*
* @param p The packet that was just received.
* @return True upon success, otherwise false.
@@ -85,6 +95,8 @@ protected:
/**
* Game Coordinator acknowledges the registration.
*
+ * string Invite code that can be used to join this server.
+ * string Secret that belongs to the invite code (only needed if reusing the invite code on next SERVER_REGISTER).
* uint8 Type of connection was detected (see ConnectionType).
*
* @param p The packet that was just received.
@@ -130,6 +142,79 @@ protected:
*/
virtual bool Receive_GC_LISTING(Packet *p);
+ /**
+ * Client wants to connect to a Server.
+ *
+ * uint8 Game Coordinator protocol version.
+ * string Invite code of the Server to join.
+ *
+ * @param p The packet that was just received.
+ * @return True upon success, otherwise false.
+ */
+ virtual bool Receive_CLIENT_CONNECT(Packet *p);
+
+ /**
+ * Game Coordinator informs the Client under what token it will start the
+ * attempt to connect the Server and Client together.
+ *
+ * string Token to track the current connect request.
+ * string Invite code of the Server to join.
+ *
+ * @param p The packet that was just received.
+ * @return True upon success, otherwise false.
+ */
+ virtual bool Receive_GC_CONNECTING(Packet *p);
+
+ /**
+ * Client or Server failed to connect to the remote side.
+ *
+ * uint8 Game Coordinator protocol version.
+ * string Token to track the current connect request.
+ * uint8 Tracking number to track current connect request.
+ *
+ * @param p The packet that was just received.
+ * @return True upon success, otherwise false.
+ */
+ virtual bool Receive_SERCLI_CONNECT_FAILED(Packet *p);
+
+ /**
+ * Game Coordinator informs the Client that it failed to find a way to
+ * connect the Client to the Server. Any open connections for this token
+ * should be closed now.
+ *
+ * string Token to track the current connect request.
+ *
+ * @param p The packet that was just received.
+ * @return True upon success, otherwise false.
+ */
+ virtual bool Receive_GC_CONNECT_FAILED(Packet *p);
+
+ /**
+ * Client informs the Game Coordinator the connection with the Server is
+ * established. The Client will disconnect from the Game Coordinator next.
+ *
+ * uint8 Game Coordinator protocol version.
+ * string Token to track the current connect request.
+ *
+ * @param p The packet that was just received.
+ * @return True upon success, otherwise false.
+ */
+ virtual bool Receive_CLIENT_CONNECTED(Packet *p);
+
+ /**
+ * Game Coordinator requests that the Client makes a direct connection to
+ * the indicated peer, which is a Server.
+ *
+ * string Token to track the current connect request.
+ * uint8 Tracking number to track current connect request.
+ * string Hostname of the peer.
+ * uint16 Port of the peer.
+ *
+ * @param p The packet that was just received.
+ * @return True upon success, otherwise false.
+ */
+ virtual bool Receive_GC_DIRECT_CONNECT(Packet *p);
+
bool HandlePacket(Packet *p);
public:
/**
diff --git a/src/network/network.cpp b/src/network/network.cpp
index 3a33e5096..f8138bbbc 100644
--- a/src/network/network.cpp
+++ b/src/network/network.cpp
@@ -589,9 +589,13 @@ void NetworkClose(bool close_admins)
ServerNetworkAdminSocketHandler::CloseListeners();
_network_coordinator_client.CloseConnection();
- } else if (MyClient::my_client != nullptr) {
- MyClient::SendQuit();
- MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT);
+ } else {
+ if (MyClient::my_client != nullptr) {
+ MyClient::SendQuit();
+ MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT);
+ }
+
+ _network_coordinator_client.CloseAllTokens();
}
TCPConnecter::KillAll();
diff --git a/src/network/network_coordinator.cpp b/src/network/network_coordinator.cpp
index b3d304952..8bd81b6f6 100644
--- a/src/network/network_coordinator.cpp
+++ b/src/network/network_coordinator.cpp
@@ -1,4 +1,3 @@
-
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
@@ -27,13 +26,41 @@
static const auto NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES = std::chrono::seconds(30); ///< How many time between updates the server sends to the Game Coordinator.
ClientNetworkCoordinatorSocketHandler _network_coordinator_client; ///< The connection to the Game Coordinator.
ConnectionType _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; ///< What type of connection the Game Coordinator detected we are on.
+std::string _network_server_invite_code = ""; ///< Our invite code as indicated by the Game Coordinator.
+
+/** Connect to a game server by IP:port. */
+class NetworkDirectConnecter : public TCPConnecter {
+private:
+ std::string token; ///< Token of this connection.
+ uint8 tracking_number; ///< Tracking number of this connection.
+
+public:
+ /**
+ * Try to establish a direct (hostname:port based) connection.
+ * @param hostname The hostname of the server.
+ * @param port The port of the server.
+ * @param token The token as given by the Game Coordinator to track this connection attempt.
+ * @param tracking_number The tracking number as given by the Game Coordinator to track this connection attempt.
+ */
+ NetworkDirectConnecter(const std::string &hostname, uint16 port, const std::string &token, uint8 tracking_number) : TCPConnecter(hostname, port), token(token), tracking_number(tracking_number) {}
+
+ void OnFailure() override
+ {
+ _network_coordinator_client.ConnectFailure(this->token, this->tracking_number);
+ }
+
+ void OnConnect(SOCKET s) override
+ {
+ _network_coordinator_client.ConnectSuccess(this->token, s);
+ }
+};
/** Connect to the Game Coordinator server. */
class NetworkCoordinatorConnecter : TCPConnecter {
public:
/**
* Initiate the connecting.
- * @param address The address of the Game Coordinator server.
+ * @param connection_string The address of the Game Coordinator server.
*/
NetworkCoordinatorConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_COORDINATOR_SERVER_PORT) {}
@@ -73,6 +100,20 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p)
this->CloseConnection();
return false;
+ case NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE: {
+ /* Find the connecter based on the invite code. */
+ auto connecter_it = this->connecter_pre.find(detail);
+ if (connecter_it == this->connecter_pre.end()) return true;
+ this->connecter_pre.erase(connecter_it);
+
+ /* Mark the server as offline. */
+ NetworkGameList *item = NetworkGameListAddItem(detail);
+ item->online = false;
+
+ UpdateNetworkGameWindow();
+ return true;
+ }
+
default:
Debug(net, 0, "Invalid error type {} received from Game Coordinator", error);
this->CloseConnection();
@@ -85,12 +126,21 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p)
/* Schedule sending an update. */
this->next_update = std::chrono::steady_clock::now();
+ _settings_client.network.server_invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH);
+ _settings_client.network.server_invite_code_secret = p->Recv_string(NETWORK_INVITE_CODE_SECRET_LENGTH);
_network_server_connection_type = (ConnectionType)p->Recv_uint8();
if (_network_server_connection_type == CONNECTION_TYPE_ISOLATED) {
ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_ISOLATED, STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL, WL_ERROR);
}
+ /* Users can change the invite code in the settings, but this has no effect
+ * on the invite code as assigned by the server. So
+ * _network_server_invite_code contains the current invite code,
+ * and _settings_client.network.server_invite_code contains the one we will
+ * attempt to re-use when registering again. */
+ _network_server_invite_code = _settings_client.network.server_invite_code;
+
SetWindowDirty(WC_CLIENT_LIST, 0);
if (_network_dedicated) {
@@ -107,7 +157,10 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p)
Debug(net, 3, "Your server is now registered with the Game Coordinator:");
Debug(net, 3, " Game type: Public");
Debug(net, 3, " Connection type: {}", connection_type);
+ Debug(net, 3, " Invite code: {}", _network_server_invite_code);
Debug(net, 3, "----------------------------------------");
+ } else {
+ Debug(net, 3, "Game Coordinator registered our server with invite code '{}'", _network_server_invite_code);
}
return true;
@@ -130,7 +183,7 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p)
NetworkGameInfo ngi = {};
DeserializeNetworkGameInfo(p, &ngi);
- /* Now we know the join-key, we can add it to our list. */
+ /* Now we know the connection string, we can add it to our list. */
NetworkGameList *item = NetworkGameListAddItem(connection_string);
/* Clear any existing GRFConfig chain. */
@@ -149,6 +202,58 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p)
return true;
}
+bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet *p)
+{
+ std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
+ std::string invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH);
+
+ /* Find the connecter based on the invite code. */
+ auto connecter_it = this->connecter_pre.find(invite_code);
+ if (connecter_it == this->connecter_pre.end()) {
+ this->CloseConnection();
+ return false;
+ }
+
+ /* Now store it based on the token. */
+ this->connecter[token] = connecter_it->second;
+ this->connecter_pre.erase(connecter_it);
+
+ return true;
+}
+
+bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet *p)
+{
+ std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
+
+ auto connecter_it = this->connecter.find(token);
+ if (connecter_it != this->connecter.end()) {
+ connecter_it->second->SetFailure();
+ this->connecter.erase(connecter_it);
+ }
+
+ /* Close all remaining connections. */
+ this->CloseToken(token);
+
+ return true;
+}
+
+bool ClientNetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet *p)
+{
+ std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
+ uint8 tracking_number = p->Recv_uint8();
+ std::string hostname = p->Recv_string(NETWORK_HOSTNAME_LENGTH);
+ uint16 port = p->Recv_uint16();
+
+ /* Ensure all other pending connection attempts are killed. */
+ if (this->game_connecter != nullptr) {
+ this->game_connecter->Kill();
+ this->game_connecter = nullptr;
+ }
+
+ this->game_connecter = new NetworkDirectConnecter(hostname, port, token, tracking_number);
+ return true;
+}
+
void ClientNetworkCoordinatorSocketHandler::Connect()
{
/* We are either already connected or are trying to connect. */
@@ -172,13 +277,15 @@ NetworkRecvStatus ClientNetworkCoordinatorSocketHandler::CloseConnection(bool er
_network_server_connection_type = CONNECTION_TYPE_UNKNOWN;
this->next_update = {};
+ this->CloseAllTokens();
+
SetWindowDirty(WC_CLIENT_LIST, 0);
return NETWORK_RECV_STATUS_OKAY;
}
/**
- * Register our server to receive our join-key.
+ * Register our server to receive our invite code.
*/
void ClientNetworkCoordinatorSocketHandler::Register()
{
@@ -193,6 +300,13 @@ void ClientNetworkCoordinatorSocketHandler::Register()
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_uint8(SERVER_GAME_TYPE_PUBLIC);
p->Send_uint16(_settings_client.network.server_port);
+ if (_settings_client.network.server_invite_code.empty() || _settings_client.network.server_invite_code_secret.empty()) {
+ p->Send_string("");
+ p->Send_string("");
+ } else {
+ p->Send_string(_settings_client.network.server_invite_code);
+ p->Send_string(_settings_client.network.server_invite_code_secret);
+ }
this->SendPacket(p);
}
@@ -230,6 +344,123 @@ void ClientNetworkCoordinatorSocketHandler::GetListing()
}
/**
+ * Join a server based on an invite code.
+ * @param invite_code The invite code of the server to connect to.
+ * @param connecter The connecter of the request.
+ */
+void ClientNetworkCoordinatorSocketHandler::ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter)
+{
+ assert(StrStartsWith(invite_code, "+"));
+
+ if (this->connecter_pre.find(invite_code) != this->connecter_pre.end()) {
+ /* If someone is hammering the refresh key, one can sent out two
+ * requests for the same invite code. There isn't really a great way
+ * of handling this, so just ignore this request. */
+ connecter->SetFailure();
+ return;
+ }
+
+ /* Initially we store based on invite code; on first reply we know the
+ * token, and will start using that key instead. */
+ this->connecter_pre[invite_code] = connecter;
+
+ this->Connect();
+
+ Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECT);
+ p->Send_uint8(NETWORK_COORDINATOR_VERSION);
+ p->Send_string(invite_code);
+
+ this->SendPacket(p);
+}
+
+/**
+ * Callback from a Connecter to let the Game Coordinator know the connection failed.
+ * @param token Token of the connecter that failed.
+ * @param tracking_number Tracking number of the connecter that failed.
+ */
+void ClientNetworkCoordinatorSocketHandler::ConnectFailure(const std::string &token, uint8 tracking_number)
+{
+ /* Connecter will destroy itself. */
+ this->game_connecter = nullptr;
+
+ Packet *p = new Packet(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED);
+ p->Send_uint8(NETWORK_COORDINATOR_VERSION);
+ p->Send_string(token);
+ p->Send_uint8(tracking_number);
+
+ this->SendPacket(p);
+
+ auto connecter_it = this->connecter.find(token);
+ assert(connecter_it != this->connecter.end());
+
+ connecter_it->second->SetFailure();
+ this->connecter.erase(connecter_it);
+}
+
+/**
+ * Callback from a Connecter to let the Game Coordinator know the connection
+ * to the game server is established.
+ * @param token Token of the connecter that succeeded.
+ * @param sock The socket that the connecter can now use.
+ */
+void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string &token, SOCKET sock)
+{
+ /* Connecter will destroy itself. */
+ this->game_connecter = nullptr;
+
+ assert(!_network_server);
+
+ Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECTED);
+ p->Send_uint8(NETWORK_COORDINATOR_VERSION);
+ p->Send_string(token);
+ this->SendPacket(p);
+
+ auto connecter_it = this->connecter.find(token);
+ assert(connecter_it != this->connecter.end());
+
+ connecter_it->second->SetConnected(sock);
+ this->connecter.erase(connecter_it);
+
+ /* Close all remaining connections. */
+ this->CloseToken(token);
+}
+
+/**
+ * Close everything related to this connection token.
+ * @param token The connection token to close.
+ */
+void ClientNetworkCoordinatorSocketHandler::CloseToken(const std::string &token)
+{
+ /* Ensure all other pending connection attempts are also killed. */
+ if (this->game_connecter != nullptr) {
+ this->game_connecter->Kill();
+ this->game_connecter = nullptr;
+ }
+}
+
+/**
+ * Close all pending connection tokens.
+ */
+void ClientNetworkCoordinatorSocketHandler::CloseAllTokens()
+{
+ /* Ensure all other pending connection attempts are also killed. */
+ if (this->game_connecter != nullptr) {
+ this->game_connecter->Kill();
+ this->game_connecter = nullptr;
+ }
+
+ /* Mark any pending connecters as failed. */
+ for (auto &[token, it] : this->connecter) {
+ it->SetFailure();
+ }
+ for (auto &[invite_code, it] : this->connecter_pre) {
+ it->SetFailure();
+ }
+ this->connecter.clear();
+ this->connecter_pre.clear();
+}
+
+/**
* Check whether we received/can send some data from/to the Game Coordinator server and
* when that's the case handle it appropriately.
*/
diff --git a/src/network/network_coordinator.h b/src/network/network_coordinator.h
index 7399ce1fc..f846958bf 100644
--- a/src/network/network_coordinator.h
+++ b/src/network/network_coordinator.h
@@ -1,4 +1,3 @@
-
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
@@ -12,6 +11,7 @@
#define NETWORK_COORDINATOR_H
#include "core/tcp_coordinator.h"
+#include <map>
/**
* Game Coordinator communication.
@@ -25,17 +25,32 @@
* For clients (listing):
* - Client sends CLIENT_LISTING.
* - Game Coordinator returns the full list of public servers via GC_LISTING (multiple packets).
+ *
+ * For clients (connecting):
+ * - Client sends CLIENT_CONNECT.
+ * - Game Coordinator checks what type of connections the servers supports:
+ * 1) Direct connect?
+ * - Send the client a GC_CONNECT with the peer address.
+ * - a) Client connects, client sends CLIENT_CONNECTED to Game Coordinator.
+ * - b) Client connect fails, client sends CLIENT_CONNECT_FAILED to Game Coordinator.
+ * - If all fails, Game Coordinator sends GC_CONNECT_FAILED to indicate no connection is possible.
*/
/** Class for handling the client side of the Game Coordinator connection. */
class ClientNetworkCoordinatorSocketHandler : public NetworkCoordinatorSocketHandler {
private:
std::chrono::steady_clock::time_point next_update; ///< When to send the next update (if server and public).
+ std::map<std::string, TCPServerConnecter *> connecter; ///< Based on tokens, the current connecters that are pending.
+ std::map<std::string, TCPServerConnecter *> connecter_pre; ///< Based on invite codes, the current connecters that are pending.
+ TCPConnecter *game_connecter = nullptr; ///< Pending connecter to the game server.
protected:
bool Receive_GC_ERROR(Packet *p) override;
bool Receive_GC_REGISTER_ACK(Packet *p) override;
bool Receive_GC_LISTING(Packet *p) override;
+ bool Receive_GC_CONNECTING(Packet *p) override;
+ bool Receive_GC_CONNECT_FAILED(Packet *p) override;
+ bool Receive_GC_DIRECT_CONNECT(Packet *p) override;
public:
/** The idle timeout; when to close the connection because it's idle. */
@@ -49,11 +64,18 @@ public:
NetworkRecvStatus CloseConnection(bool error = true) override;
void SendReceive();
+ void ConnectFailure(const std::string &token, uint8 tracking_number);
+ void ConnectSuccess(const std::string &token, SOCKET sock);
+
void Connect();
+ void CloseToken(const std::string &token);
+ void CloseAllTokens();
void Register();
void SendServerUpdate();
void GetListing();
+
+ void ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter);
};
extern ClientNetworkCoordinatorSocketHandler _network_coordinator_client;
diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp
index 24884ed16..a0d2d47c8 100644
--- a/src/network/network_gui.cpp
+++ b/src/network/network_gui.cpp
@@ -751,7 +751,7 @@ public:
SetDParamStr(0, _settings_client.network.connect_to_ip);
ShowQueryString(
STR_JUST_RAW_STRING,
- STR_NETWORK_SERVER_LIST_ENTER_IP,
+ STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS,
NETWORK_HOSTNAME_PORT_LENGTH, // maximum number of characters including '\0'
this, CS_ALPHANUMERAL, QSF_ACCEPT_UNCHANGED);
break;
@@ -1633,6 +1633,11 @@ static const NWidgetPart _nested_client_list_widgets[] = {
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_CL_SERVER_VISIBILITY), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
+ NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE, STR_NULL),
+ NWidget(NWID_SPACER), SetMinimalSize(10, 0),
+ NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_INVITE_CODE), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE, STR_NULL),
NWidget(NWID_SPACER), SetMinimalSize(10, 0),
NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_CONNECTION_TYPE), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
@@ -2071,6 +2076,12 @@ public:
SetDParam(0, _server_visibility_dropdown[_settings_client.network.server_advertise]);
break;
+ case WID_CL_SERVER_INVITE_CODE: {
+ static std::string empty = {};
+ SetDParamStr(0, _network_server_connection_type == CONNECTION_TYPE_UNKNOWN ? empty : _network_server_invite_code);
+ break;
+ }
+
case WID_CL_SERVER_CONNECTION_TYPE:
SetDParam(0, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN + _network_server_connection_type);
break;
diff --git a/src/network/network_internal.h b/src/network/network_internal.h
index 95226286c..2c6639961 100644
--- a/src/network/network_internal.h
+++ b/src/network/network_internal.h
@@ -84,6 +84,7 @@ extern uint8 _network_join_waiting;
extern uint32 _network_join_bytes;
extern uint32 _network_join_bytes_total;
extern ConnectionType _network_server_connection_type;
+extern std::string _network_server_invite_code;
extern uint8 _network_reconnect;
diff --git a/src/settings_type.h b/src/settings_type.h
index 3377bdede..79e462c3e 100644
--- a/src/settings_type.h
+++ b/src/settings_type.h
@@ -266,6 +266,8 @@ struct NetworkSettings {
uint16 server_port; ///< port the server listens on
uint16 server_admin_port; ///< port the server listens on for the admin network
bool server_admin_chat; ///< allow private chat for the server to be distributed to the admin network
+ std::string server_invite_code; ///< Invite code to use when registering as server.
+ std::string server_invite_code_secret; ///< Secret to proof we got this invite code from the Game Coordinator.
std::string server_name; ///< name of the server
std::string server_password; ///< password for joining this server
std::string rcon_password; ///< password for rconsole (server side)
diff --git a/src/table/settings/network_secrets_settings.ini b/src/table/settings/network_secrets_settings.ini
index fced9240e..4613636a8 100644
--- a/src/table/settings/network_secrets_settings.ini
+++ b/src/table/settings/network_secrets_settings.ini
@@ -74,3 +74,17 @@ type = SLE_STR
length = NETWORK_SERVER_ID_LENGTH
flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY
def = nullptr
+
+[SDTC_SSTR]
+var = network.server_invite_code
+type = SLE_STR
+length = NETWORK_INVITE_CODE_LENGTH
+flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY
+def = nullptr
+
+[SDTC_SSTR]
+var = network.server_invite_code_secret
+type = SLE_STR
+length = NETWORK_INVITE_CODE_SECRET_LENGTH
+flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY
+def = nullptr
diff --git a/src/widgets/network_widget.h b/src/widgets/network_widget.h
index a665afdb7..c8ec22e86 100644
--- a/src/widgets/network_widget.h
+++ b/src/widgets/network_widget.h
@@ -101,6 +101,7 @@ enum ClientListWidgets {
WID_CL_SERVER_NAME, ///< Server name.
WID_CL_SERVER_NAME_EDIT, ///< Edit button for server name.
WID_CL_SERVER_VISIBILITY, ///< Server visibility.
+ WID_CL_SERVER_INVITE_CODE, ///< Invite code for this server.
WID_CL_SERVER_CONNECTION_TYPE, ///< The type of connection the Game Coordinator detected for this server.
WID_CL_CLIENT_NAME, ///< Client name.
WID_CL_CLIENT_NAME_EDIT, ///< Edit button for client name.