diff --git a/src/network/core/tcp_game.cpp b/src/network/core/tcp_game.cpp
index 4bd32ec153..f1b4b05e2a 100644
--- a/src/network/core/tcp_game.cpp
+++ b/src/network/core/tcp_game.cpp
@@ -81,6 +81,7 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet &p)
 		case PACKET_CLIENT_GAME_INFO:             return this->Receive_CLIENT_GAME_INFO(p);
 		case PACKET_SERVER_GAME_INFO:             return this->Receive_SERVER_GAME_INFO(p);
 		case PACKET_SERVER_CLIENT_INFO:           return this->Receive_SERVER_CLIENT_INFO(p);
+		case PACKET_CLIENT_IDENTIFY:              return this->Receive_CLIENT_IDENTIFY(p);
 		case PACKET_SERVER_NEED_GAME_PASSWORD:    return this->Receive_SERVER_NEED_GAME_PASSWORD(p);
 		case PACKET_SERVER_NEED_COMPANY_PASSWORD: return this->Receive_SERVER_NEED_COMPANY_PASSWORD(p);
 		case PACKET_CLIENT_GAME_PASSWORD:         return this->Receive_CLIENT_GAME_PASSWORD(p);
@@ -162,6 +163,7 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_ERROR(Packet &) { ret
 NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GAME_INFO); }
 NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_GAME_INFO); }
 NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_CLIENT_INFO); }
+NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_IDENTIFY); }
 NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_GAME_PASSWORD); }
 NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_COMPANY_PASSWORD); }
 NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(Packet &) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GAME_PASSWORD); }
diff --git a/src/network/core/tcp_game.h b/src/network/core/tcp_game.h
index ce031aa07b..7aed964d51 100644
--- a/src/network/core/tcp_game.h
+++ b/src/network/core/tcp_game.h
@@ -56,7 +56,10 @@ enum PacketGameType : uint8_t {
 	 * the map and other important data.
 	 */
 
-	/* After the join step, the first is checking NewGRFs. */
+	/* After the initial join, the next step is identification. */
+	PACKET_CLIENT_IDENTIFY,              ///< Client telling the server the client's name and requested company.
+
+	/* After the identify step, the next is checking NewGRFs. */
 	PACKET_SERVER_CHECK_NEWGRFS,         ///< Server sends NewGRF IDs and MD5 checksums for the client to check.
 	PACKET_CLIENT_NEWGRFS_CHECKED,       ///< Client acknowledges that it has all required NewGRFs.
 
@@ -162,10 +165,13 @@ protected:
 
 	/**
 	 * Try to join the server:
-	 * string  OpenTTD revision (norev000 if no revision).
-	 * string  Name of the client (max NETWORK_NAME_LENGTH).
-	 * uint8_t   ID of the company to play as (1..MAX_COMPANIES).
-	 * uint8_t   ID of the clients Language.
+	 * string   OpenTTD revision (norev000 if no revision).
+	 * uint32_t NewGRF version (added in 1.2).
+	 * string   Name of the client (max NETWORK_NAME_LENGTH) (removed in 15).
+	 * uint8_t  ID of the company to play as (1..MAX_COMPANIES) (removed in 15).
+	 * uint8_t  ID of the clients Language (removed in 15).
+	 * string   Client's unique identifier (removed in 1.0).
+	 *
 	 * @param p The packet that was just received.
 	 */
 	virtual NetworkRecvStatus Receive_CLIENT_JOIN(Packet &p);
@@ -199,6 +205,14 @@ protected:
 	 */
 	virtual NetworkRecvStatus Receive_SERVER_CLIENT_INFO(Packet &p);
 
+	/**
+	 * The client tells the server about the identity of the client:
+	 * string  Name of the client (max NETWORK_NAME_LENGTH).
+	 * uint8_t ID of the company to play as (1..MAX_COMPANIES).
+	 * @param p The packet that was just received.
+	 */
+	virtual NetworkRecvStatus Receive_CLIENT_IDENTIFY(Packet &p);
+
 	/**
 	 * Indication to the client that the server needs a game password.
 	 * @param p The packet that was just received.
diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp
index ded54f0bae..db56f0b209 100644
--- a/src/network/network_client.cpp
+++ b/src/network/network_client.cpp
@@ -347,9 +347,18 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin()
 	auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_JOIN);
 	p->Send_string(GetNetworkRevisionString());
 	p->Send_uint32(_openttd_newgrf_version);
+	my_client->SendPacket(std::move(p));
+
+	return ClientNetworkGameSocketHandler::SendIdentify();
+}
+
+NetworkRecvStatus ClientNetworkGameSocketHandler::SendIdentify()
+{
+	Debug(net, 9, "Client::SendIdentify()");
+
+	auto p = std::make_unique<Packet>(my_client, PACKET_CLIENT_IDENTIFY);
 	p->Send_string(_settings_client.network.client_name); // Client name
-	p->Send_uint8 (_network_join.company);     // PlayAs
-	p->Send_uint8 (0); // Used to be language
+	p->Send_uint8(_network_join.company); // PlayAs
 	my_client->SendPacket(std::move(p));
 	return NETWORK_RECV_STATUS_OKAY;
 }
diff --git a/src/network/network_client.h b/src/network/network_client.h
index d314ddc41a..72da871700 100644
--- a/src/network/network_client.h
+++ b/src/network/network_client.h
@@ -71,6 +71,7 @@ protected:
 	static NetworkRecvStatus SendNewGRFsOk();
 	static NetworkRecvStatus SendGetMap();
 	static NetworkRecvStatus SendMapOk();
+	static NetworkRecvStatus SendIdentify();
 	void CheckConnection();
 public:
 	ClientNetworkGameSocketHandler(SOCKET s, const std::string &connection_string);
diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp
index 2e6c51920e..72567c2ef9 100644
--- a/src/network/network_server.cpp
+++ b/src/network/network_server.cpp
@@ -400,8 +400,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck()
 {
 	Debug(net, 9, "client[{}] SendNewGRFCheck()", this->client_id);
 
-	/* Invalid packet when status is anything but STATUS_INACTIVE. */
-	if (this->status != STATUS_INACTIVE) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET);
+	/* Invalid packet when status is anything but STATUS_IDENTIFY. */
+	if (this->status != STATUS_IDENTIFY) return this->CloseConnection(NETWORK_RECV_STATUS_MALFORMED_PACKET);
 
 	Debug(net, 9, "client[{}] status = NEWGRFS_CHECK", this->client_id);
 	this->status = STATUS_NEWGRFS_CHECK;
@@ -891,6 +891,21 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet &p)
 		return this->SendError(NETWORK_ERROR_WRONG_REVISION);
 	}
 
+	Debug(net, 9, "client[{}] status = IDENTIFY", this->client_id);
+	this->status = STATUS_IDENTIFY;
+
+	/* Reset 'lag' counters */
+	this->last_frame = this->last_frame_server = _frame_counter;
+
+	return NETWORK_RECV_STATUS_OKAY;
+}
+
+NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet &p)
+{
+	if (this->status != STATUS_IDENTIFY) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
+
+	Debug(net, 9, "client[{}] Receive_CLIENT_IDENTIFY()", this->client_id);
+
 	std::string client_name = p.Recv_string(NETWORK_CLIENT_NAME_LENGTH);
 	CompanyID playas = (Owner)p.Recv_uint8();
 
@@ -905,7 +920,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet &p)
 			break;
 		case COMPANY_SPECTATOR: // Spectator
 			break;
-		default: // Join another company (companies 1-8 (index 0-7))
+		default: // Join another company (companies 1..MAX_COMPANIES (index 0..(MAX_COMPANIES-1)))
 			if (!Company::IsValidHumanID(playas)) {
 				return this->SendError(NETWORK_ERROR_COMPANY_MISMATCH);
 			}
@@ -1770,6 +1785,7 @@ void NetworkServer_Tick(bool send_frame)
 				break;
 
 			case NetworkClientSocket::STATUS_INACTIVE:
+			case NetworkClientSocket::STATUS_IDENTIFY:
 			case NetworkClientSocket::STATUS_NEWGRFS_CHECK:
 			case NetworkClientSocket::STATUS_AUTHORIZED:
 				/* NewGRF check and authorized states should be handled almost instantly.
@@ -1962,6 +1978,7 @@ void NetworkServerShowStatusToConsole()
 {
 	static const char * const stat_str[] = {
 		"inactive",
+		"identifing client",
 		"checking NewGRFs",
 		"authorizing (server password)",
 		"authorizing (company password)",
diff --git a/src/network/network_server.h b/src/network/network_server.h
index bbf9817248..f67aeeb404 100644
--- a/src/network/network_server.h
+++ b/src/network/network_server.h
@@ -24,6 +24,7 @@ extern NetworkClientSocketPool _networkclientsocket_pool;
 class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<&_networkclientsocket_pool>, public NetworkGameSocketHandler, public TCPListenHandler<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED> {
 protected:
 	NetworkRecvStatus Receive_CLIENT_JOIN(Packet &p) override;
+	NetworkRecvStatus Receive_CLIENT_IDENTIFY(Packet &p) override;
 	NetworkRecvStatus Receive_CLIENT_GAME_INFO(Packet &p) override;
 	NetworkRecvStatus Receive_CLIENT_GAME_PASSWORD(Packet &p) override;
 	NetworkRecvStatus Receive_CLIENT_COMPANY_PASSWORD(Packet &p) override;
@@ -50,6 +51,7 @@ public:
 	/** Status of a client */
 	enum ClientStatus {
 		STATUS_INACTIVE,      ///< The client is not connected nor active.
+		STATUS_IDENTIFY,      ///< The client is identifying itself.
 		STATUS_NEWGRFS_CHECK, ///< The client is checking NewGRFs.
 		STATUS_AUTH_GAME,     ///< The client is authorizing with game (server) password.
 		STATUS_AUTH_COMPANY,  ///< The client is authorizing with company password.