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.
This commit is contained in:
Patric Stout 2021-04-29 15:37:02 +02:00 committed by Patric Stout
parent 1baec41542
commit e4d216e44b
15 changed files with 422 additions and 29 deletions

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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:

View File

@ -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); }

View File

@ -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:
/**

View File

@ -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();

View File

@ -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);
}
@ -229,6 +343,123 @@ void ClientNetworkCoordinatorSocketHandler::GetListing()
this->SendPacket(p);
}
/**
* 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.

View File

@ -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;

View File

@ -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;
@ -1632,6 +1632,11 @@ static const NWidgetPart _nested_client_list_widgets[] = {
NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(1, 0), SetResize(1, 0),
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),
@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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.