mirror of
https://github.com/vcmi/vcmi.git
synced 2025-06-02 23:07:36 +02:00
Merge pull request #3654 from IvanSavenko/lobby_protocol
Lobby protocol documentation and fixes
This commit is contained in:
commit
94ecef748b
@ -19,10 +19,13 @@
|
||||
#include "../windows/InfoWindows.h"
|
||||
#include "../CServerHandler.h"
|
||||
#include "../mainmenu/CMainMenu.h"
|
||||
#include "../CGameInfo.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/MetaString.h"
|
||||
#include "../../lib/json/JsonUtils.h"
|
||||
#include "../../lib/TextOperations.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
|
||||
GlobalLobbyClient::GlobalLobbyClient() = default;
|
||||
GlobalLobbyClient::~GlobalLobbyClient() = default;
|
||||
@ -118,7 +121,7 @@ void GlobalLobbyClient::receiveLoginSuccess(const JsonNode & json)
|
||||
if(!loginWindowPtr || !GH.windows().topWindow<GlobalLobbyLoginWindow>())
|
||||
throw std::runtime_error("lobby connection finished without active login window!");
|
||||
|
||||
loginWindowPtr->onConnectionSuccess();
|
||||
loginWindowPtr->onLoginSuccess();
|
||||
}
|
||||
|
||||
void GlobalLobbyClient::receiveChatHistory(const JsonNode & json)
|
||||
@ -230,6 +233,8 @@ void GlobalLobbyClient::sendClientRegister(const std::string & accountName)
|
||||
JsonNode toSend;
|
||||
toSend["type"].String() = "clientRegister";
|
||||
toSend["displayName"].String() = accountName;
|
||||
toSend["language"].String() = CGI->generaltexth->getPreferredLanguage();
|
||||
toSend["version"].String() = VCMI_VERSION_STRING;
|
||||
sendMessage(toSend);
|
||||
}
|
||||
|
||||
@ -239,6 +244,8 @@ void GlobalLobbyClient::sendClientLogin()
|
||||
toSend["type"].String() = "clientLogin";
|
||||
toSend["accountID"] = settings["lobby"]["accountID"];
|
||||
toSend["accountCookie"] = settings["lobby"]["accountCookie"];
|
||||
toSend["language"].String() = CGI->generaltexth->getPreferredLanguage();
|
||||
toSend["version"].String() = VCMI_VERSION_STRING;
|
||||
sendMessage(toSend);
|
||||
}
|
||||
|
||||
@ -273,13 +280,14 @@ void GlobalLobbyClient::onDisconnected(const std::shared_ptr<INetworkConnection>
|
||||
|
||||
void GlobalLobbyClient::sendMessage(const JsonNode & data)
|
||||
{
|
||||
assert(JsonUtils::validate(data, "vcmi:lobbyProtocol/" + data["type"].String(), data["type"].String() + " pack"));
|
||||
networkConnection->sendPacket(data.toBytes());
|
||||
}
|
||||
|
||||
void GlobalLobbyClient::sendOpenPublicRoom()
|
||||
{
|
||||
JsonNode toSend;
|
||||
toSend["type"].String() = "openGameRoom";
|
||||
toSend["type"].String() = "activateGameRoom";
|
||||
toSend["hostAccountID"] = settings["lobby"]["accountID"];
|
||||
toSend["roomType"].String() = "public";
|
||||
sendMessage(toSend);
|
||||
@ -288,7 +296,7 @@ void GlobalLobbyClient::sendOpenPublicRoom()
|
||||
void GlobalLobbyClient::sendOpenPrivateRoom()
|
||||
{
|
||||
JsonNode toSend;
|
||||
toSend["type"].String() = "openGameRoom";
|
||||
toSend["type"].String() = "activateGameRoom";
|
||||
toSend["hostAccountID"] = settings["lobby"]["accountID"];
|
||||
toSend["roomType"].String() = "private";
|
||||
sendMessage(toSend);
|
||||
@ -362,5 +370,6 @@ void GlobalLobbyClient::sendProxyConnectionLogin(const NetworkConnectionPtr & ne
|
||||
toSend["accountCookie"] = settings["lobby"]["accountCookie"];
|
||||
toSend["gameRoomID"] = settings["lobby"]["roomID"];
|
||||
|
||||
assert(JsonUtils::validate(toSend, "vcmi:lobbyProtocol/" + toSend["type"].String(), toSend["type"].String() + " pack"));
|
||||
netConnection->sendPacket(toSend.toBytes());
|
||||
}
|
||||
|
26
config/schemas/lobbyProtocol/accountCreated.json
Normal file
26
config/schemas/lobbyProtocol/accountCreated.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: accountCreated",
|
||||
"description" : "Sent by server when player successfully creates a new account. Note that it does not automatically logs in created account",
|
||||
"required" : [ "type", "accountID", "accountCookie" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "accountCreated"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Unique ID of account that client must remember for future login attempts"
|
||||
},
|
||||
"accountCookie" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Private access cookie of account that client must remember for future login attempts"
|
||||
}
|
||||
}
|
||||
}
|
21
config/schemas/lobbyProtocol/accountJoinsRoom.json
Normal file
21
config/schemas/lobbyProtocol/accountJoinsRoom.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: accountJoinsRoom",
|
||||
"description" : "Sent by server to match server when new account joins the room",
|
||||
"required" : [ "type", "accountID" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "accountJoinsRoom"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of account that have joined the room"
|
||||
}
|
||||
}
|
||||
}
|
27
config/schemas/lobbyProtocol/activateGameRoom.json
Normal file
27
config/schemas/lobbyProtocol/activateGameRoom.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: activateGameRoom",
|
||||
"description" : "Sent by client when player wants to activate a game room",
|
||||
"required" : [ "type", "hostAccountID", "roomType" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "activateGameRoom"
|
||||
},
|
||||
"hostAccountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Account ID that hosts match server that player wants to activate"
|
||||
},
|
||||
"roomType" :
|
||||
{
|
||||
"type" : "string",
|
||||
"enum" : [ "public", "private" ],
|
||||
"description" : "Room type to use for activation"
|
||||
}
|
||||
}
|
||||
}
|
45
config/schemas/lobbyProtocol/activeAccounts.json
Normal file
45
config/schemas/lobbyProtocol/activeAccounts.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: activeAccounts",
|
||||
"description" : "Sent by server to initialized or update list of active accounts",
|
||||
"required" : [ "type", "accounts" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "activeAccounts"
|
||||
},
|
||||
"accounts" :
|
||||
{
|
||||
"type" : "array",
|
||||
"description" : "List of accounts that are currently online",
|
||||
"items" :
|
||||
{
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"required" : [ "accountID", "displayName", "status" ],
|
||||
|
||||
"properties" : {
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Unique ID of an account"
|
||||
},
|
||||
"displayName" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Display name of an account"
|
||||
},
|
||||
"status" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Current status of an account, such as in lobby or in match"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
config/schemas/lobbyProtocol/activeGameRooms.json
Normal file
59
config/schemas/lobbyProtocol/activeGameRooms.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: activeGameRooms",
|
||||
"description" : "Sent by server to initialized or update list of game rooms",
|
||||
"required" : [ "type", "gameRooms" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "activeGameRooms"
|
||||
},
|
||||
"gameRooms" :
|
||||
{
|
||||
"type" : "array",
|
||||
"description" : "List of currently available game rooms",
|
||||
"items" :
|
||||
{
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"required" : [ "gameRoomID", "hostAccountID", "hostAccountDisplayName", "description", "playersCount", "playersLimit" ],
|
||||
"properties" : {
|
||||
"gameRoomID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Unique ID of game room"
|
||||
},
|
||||
"hostAccountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of account that created and hosts this game room"
|
||||
},
|
||||
"hostAccountDisplayName" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Display name of account that created and hosts this game room"
|
||||
},
|
||||
"description" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Auto-generated description of this room"
|
||||
},
|
||||
"playersCount" :
|
||||
{
|
||||
"type" : "number",
|
||||
"description" : "Current number of players in this room, including host"
|
||||
},
|
||||
"playersLimit" :
|
||||
{
|
||||
"type" : "number",
|
||||
"description" : "Maximum number of players that can join this room, including host"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
config/schemas/lobbyProtocol/chatHistory.json
Normal file
49
config/schemas/lobbyProtocol/chatHistory.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: chatHistory",
|
||||
"description" : "Sent by server immediately after login to fill initial chat history",
|
||||
"required" : [ "type", "messages" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "chatHistory"
|
||||
},
|
||||
"messages" :
|
||||
{
|
||||
"type" : "array",
|
||||
"description" : "List of recent chat messages",
|
||||
"items" :
|
||||
{
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"required" : [ "messageText", "accountID", "displayName", "ageSeconds" ],
|
||||
"properties" : {
|
||||
"messageText" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Text of sent message"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of account that have sent this message"
|
||||
},
|
||||
"displayName" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Display name of account that have sent this message"
|
||||
},
|
||||
"ageSeconds" :
|
||||
{
|
||||
"type" : "number",
|
||||
"description" : "Age of this message in seconds. For example, 10 means that this message was sent 10 seconds ago"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
config/schemas/lobbyProtocol/chatMessage.json
Normal file
43
config/schemas/lobbyProtocol/chatMessage.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: chatMessage",
|
||||
"description" : "Sent by server to all players in lobby whenever new message is sent to game chat",
|
||||
"required" : [ "type", "messageText", "accountID", "displayName", "roomMode", "roomName" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "chatMessage"
|
||||
},
|
||||
"messageText" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Text of sent message"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of account that have sent this message"
|
||||
},
|
||||
"displayName" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Display name of account that have sent this message"
|
||||
},
|
||||
"roomMode" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "global",
|
||||
"description" : "Type of room to which this message has been set. Right now can only be 'general'"
|
||||
},
|
||||
"roomName" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "english",
|
||||
"description" : "Name of room to which this message has been set. Right now only 'english' is used"
|
||||
}
|
||||
}
|
||||
}
|
37
config/schemas/lobbyProtocol/clientLogin.json
Normal file
37
config/schemas/lobbyProtocol/clientLogin.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: clientLogin",
|
||||
"description" : "Sent by client when player requests lobby login",
|
||||
|
||||
"required" : [ "type", "accountID", "accountCookie", "language", "version" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "clientLogin"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of existing account to login with"
|
||||
},
|
||||
"accountCookie" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Secret UUID for identification"
|
||||
},
|
||||
"language" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Language of client, see lib/Languages.h for full list"
|
||||
},
|
||||
"version" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Version of client, e.g. 1.5.0"
|
||||
}
|
||||
}
|
||||
}
|
31
config/schemas/lobbyProtocol/clientProxyLogin.json
Normal file
31
config/schemas/lobbyProtocol/clientProxyLogin.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: clientProxyLogin",
|
||||
"description" : "Sent by client when player attempt to connect to game room in proxy mode",
|
||||
"required" : [ "type", "accountID", "accountCookie", "gameRoomID" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "clientLogin"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of existing account to login with"
|
||||
},
|
||||
"accountCookie" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Secret UUID for identification"
|
||||
},
|
||||
"gameRoomID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of game room to connect to"
|
||||
}
|
||||
}
|
||||
}
|
31
config/schemas/lobbyProtocol/clientRegister.json
Normal file
31
config/schemas/lobbyProtocol/clientRegister.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: clientRegister",
|
||||
"description" : "Sent by client when player attempts to create a new account",
|
||||
"required" : [ "type", "displayName", "language", "version" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "clientRegister"
|
||||
},
|
||||
"displayName" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Desired name of a new account"
|
||||
},
|
||||
"language" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Language of client, see lib/Languages.h for full list"
|
||||
},
|
||||
"version" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Version of client, e.g. 1.5.0"
|
||||
}
|
||||
}
|
||||
}
|
21
config/schemas/lobbyProtocol/declineInvite.json
Normal file
21
config/schemas/lobbyProtocol/declineInvite.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: declineInvite",
|
||||
"description" : "Sent by client when player declines invite to join a room",
|
||||
"required" : [ "type", "gameRoomID" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "declineInvite"
|
||||
},
|
||||
"gameRoomID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of game room to decline invite"
|
||||
}
|
||||
}
|
||||
}
|
26
config/schemas/lobbyProtocol/inviteReceived.json
Normal file
26
config/schemas/lobbyProtocol/inviteReceived.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: inviteReceived",
|
||||
"description" : "Sent by server when player receives an invite from another player to join the game room",
|
||||
"required" : [ "type", "accountID", "gameRoomID" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "inviteReceived"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of account that sent an invite"
|
||||
},
|
||||
"gameRoomID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of game room that this player is now allowed to join"
|
||||
}
|
||||
}
|
||||
}
|
21
config/schemas/lobbyProtocol/joinGameRoom.json
Normal file
21
config/schemas/lobbyProtocol/joinGameRoom.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: joinGameRoom",
|
||||
"description" : "Sent by client when player attempts to join a game room",
|
||||
"required" : [ "type", "gameRoomID" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "joinGameRoom"
|
||||
},
|
||||
"gameRoomID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of game room to join"
|
||||
}
|
||||
}
|
||||
}
|
26
config/schemas/lobbyProtocol/joinRoomSuccess.json
Normal file
26
config/schemas/lobbyProtocol/joinRoomSuccess.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: joinRoomSuccess",
|
||||
"description" : "Sent by server when player successfully joins the game room",
|
||||
"required" : [ "type", "gameRoomID", "proxyMode" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "joinRoomSuccess"
|
||||
},
|
||||
"gameRoomID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of game room that this player now participates in"
|
||||
},
|
||||
"proxyMode" :
|
||||
{
|
||||
"type" : "boolean",
|
||||
"description" : "If true, account should use proxy mode and connect to server via game lobby"
|
||||
}
|
||||
}
|
||||
}
|
21
config/schemas/lobbyProtocol/leaveGameRoom.json
Normal file
21
config/schemas/lobbyProtocol/leaveGameRoom.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: leaveGameRoom",
|
||||
"description" : "Sent by server when player disconnects from the game room",
|
||||
"required" : [ "type", "accountID" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "leaveGameRoom"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of account that have left the room"
|
||||
}
|
||||
}
|
||||
}
|
26
config/schemas/lobbyProtocol/loginSuccess.json
Normal file
26
config/schemas/lobbyProtocol/loginSuccess.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: loginSuccess",
|
||||
"description" : "Sent by server once player sucesfully logs into the lobby",
|
||||
"required" : [ "type", "accountCookie", "displayName" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "loginSuccess"
|
||||
},
|
||||
"accountCookie" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Security cookie that should be stored by client and used for future operations"
|
||||
},
|
||||
"displayName" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Account display name - how client should display this account"
|
||||
}
|
||||
}
|
||||
}
|
21
config/schemas/lobbyProtocol/operationFailed.json
Normal file
21
config/schemas/lobbyProtocol/operationFailed.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: operationFailed",
|
||||
"description" : "Sent by server whenever operation requested by client fails",
|
||||
"required" : [ "type", "reason" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "operationFailed"
|
||||
},
|
||||
"reason" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Human-readable description of reason behind operation failure, in English"
|
||||
}
|
||||
}
|
||||
}
|
21
config/schemas/lobbyProtocol/sendChatMessage.json
Normal file
21
config/schemas/lobbyProtocol/sendChatMessage.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: sendChatMessage",
|
||||
"description" : "Sent by client when player requests lobby login",
|
||||
"required" : [ "type", "messageText" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "sendChatMessage"
|
||||
},
|
||||
"messageText" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Text of sent message"
|
||||
}
|
||||
}
|
||||
}
|
21
config/schemas/lobbyProtocol/sendInvite.json
Normal file
21
config/schemas/lobbyProtocol/sendInvite.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: sendInvite",
|
||||
"description" : "Sent by client when player invites another player into his current game room",
|
||||
"required" : [ "type", "accountID" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "sendInvite"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of account to invite into the room"
|
||||
}
|
||||
}
|
||||
}
|
37
config/schemas/lobbyProtocol/serverLogin.json
Normal file
37
config/schemas/lobbyProtocol/serverLogin.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: serverLogin",
|
||||
"description" : "Sent by match server when starting up in lobby mode",
|
||||
|
||||
"required" : [ "type", "gameRoomID", "accountID", "accountCookie", "version" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "serverLogin"
|
||||
},
|
||||
"gameRoomID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "UUID generated by server to represent this game room"
|
||||
},
|
||||
"accountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Account ID of game host"
|
||||
},
|
||||
"accountCookie" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Security cookie of host account"
|
||||
},
|
||||
"version" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Version of match server, e.g. 1.5.0"
|
||||
}
|
||||
}
|
||||
}
|
32
config/schemas/lobbyProtocol/serverProxyLogin.json
Normal file
32
config/schemas/lobbyProtocol/serverProxyLogin.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"type" : "object",
|
||||
"$schema" : "http://json-schema.org/draft-06/schema",
|
||||
"title" : "Lobby protocol: serverProxyLogin",
|
||||
"description" : "Sent by match server to establish proxy connection to be used by player",
|
||||
|
||||
"required" : [ "type", "gameRoomID", "guestAccountID", "accountCookie" ],
|
||||
"additionalProperties" : false,
|
||||
|
||||
"properties" : {
|
||||
"type" :
|
||||
{
|
||||
"type" : "string",
|
||||
"const" : "serverProxyLogin"
|
||||
},
|
||||
"gameRoomID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "ID of server game room to login with"
|
||||
},
|
||||
"guestAccountID" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Account ID of player that should use this connection"
|
||||
},
|
||||
"accountCookie" :
|
||||
{
|
||||
"type" : "string",
|
||||
"description" : "Secret UUID for identification of account that hosts match server"
|
||||
}
|
||||
}
|
||||
}
|
104
docs/developers/Networking.md
Normal file
104
docs/developers/Networking.md
Normal file
@ -0,0 +1,104 @@
|
||||
< [Documentation](../Readme.md) / Networking
|
||||
|
||||
# The big picture
|
||||
|
||||
For implementation details see files located at `lib/network` directory.
|
||||
|
||||
VCMI uses connection using TCP to communicate with server, even in single-player games. However, even though TCP is stream-based protocol, VCMI uses atomic messages for communication. Each message is a serialized stream of bytes, preceded by 4-byte message size:
|
||||
```
|
||||
int32_t messageSize;
|
||||
byte messagePayload[messageSize];
|
||||
```
|
||||
|
||||
Networking can be used by:
|
||||
- game client (vcmiclient / VCMI_Client.exe). Actual application that player interacts with directly using UI.
|
||||
- match server (vcmiserver / VCMI_Server.exe / part of game client). This app controls game logic and coordinates multiplayer games.
|
||||
- lobby server (vcmilobby). This app provides access to global lobby through which players can play game over Internet.
|
||||
|
||||
Following connections can be established during game lifetime:
|
||||
|
||||
- game client -> match server: This is main connection for use during gameplay, created once player requests to move from main menu to pregame / match lobby (e.g. after pressing New Game / Load Game)
|
||||
- game client -> lobby server: This connection is used to access global lobby, for multiplayer over Internet. Created when player logs into a lobby (Multiplayer -> Connect to global service)
|
||||
- match server -> lobby server: This connection is established when player creates new multiplayer room via lobby. It is used by lobby server to send commands to match server
|
||||
|
||||
# Gameplay communication
|
||||
|
||||
For gameplay, VCMI serializes data into a binary stream. See [Serialization](Serialization.md) for more information.
|
||||
|
||||
# Global lobby communication
|
||||
|
||||
For implementation details see:
|
||||
- game client: `client/globalLobby/GlobalLobbyClient.h
|
||||
- match server: `server/GlobalLobbyProcessor.h
|
||||
- lobby server: `client/globalLobby/GlobalLobbyClient.h
|
||||
|
||||
In case of global lobby, message payload uses plaintext json format - utf-8 encoded string:
|
||||
```
|
||||
int32_t messageSize;
|
||||
char jsonString[messageSize];
|
||||
```
|
||||
|
||||
Every message must be a struct (json object) that contains "type" field. Unlike rest of VCMI codebase, this message is validated as strict json, without any extensions, such as comments.
|
||||
|
||||
## Communication flow
|
||||
|
||||
Notes:
|
||||
- invalid message, such as corrupted json format or failure to validate message will result in no reply from server
|
||||
- in addition to specified messages, match server will send `operationFailed` message on failure to apply player request
|
||||
|
||||
### New Account Creation
|
||||
|
||||
- client -> lobby: `clientRegister`
|
||||
- lobby -> client: `accountCreated`
|
||||
|
||||
### Login
|
||||
- client -> lobby: `clientLogin`
|
||||
- lobby -> client: `loginSuccess`
|
||||
- lobby -> client: `chatHistory`
|
||||
- lobby -> client: `activeAccounts`
|
||||
- lobby -> client: `activeGameRooms`
|
||||
|
||||
### Chat Message
|
||||
- client -> lobby: `sendChatMessage`
|
||||
- lobby -> every client: `chatMessage`
|
||||
|
||||
### New Game Room
|
||||
- client starts match server instance
|
||||
- match -> lobby: `serverLogin`
|
||||
- lobby -> match: `loginSuccess`
|
||||
- match accepts connection from client
|
||||
- client -> lobby: `activateGameRoom`
|
||||
- lobby -> client: `joinRoomSuccess`
|
||||
- lobby -> every client: `activeAccounts`
|
||||
- lobby -> every client: `activeGameRooms`
|
||||
|
||||
### Joining a game room
|
||||
See [#Proxy mode](proxy-mode)
|
||||
|
||||
### Leaving a game room
|
||||
- client closes connection to match server
|
||||
- match -> lobby: `leaveGameRoom`
|
||||
|
||||
### Sending an invite:
|
||||
- client -> lobby: `sendInvite`
|
||||
- lobby -> target client: `inviteReceived`
|
||||
|
||||
Note: there is no dedicated procedure to accept an invite. Instead, invited player will use same flow as when joining public game room
|
||||
|
||||
### Logout
|
||||
- client closes connection
|
||||
- lobby -> every client: `activeAccounts`
|
||||
|
||||
## Proxy mode
|
||||
|
||||
In order to connect players located behind NAT, VCMI lobby can operate in "proxy" mode. In this mode, connection will be act as proxy and will transmit gameplay data from client to a match server, without any data processing on lobby server.
|
||||
|
||||
Currently, process to establish connection using proxy mode is:
|
||||
- Player attempt to join open game room using `joinGameRoom` message
|
||||
- Lobby server validates requests and on success - notifies match server about new player in lobby using control connection
|
||||
- Match server receives request, establishes new connection to game lobby, sends `serverProxyLogin` message to lobby server and immediately transfers this connection to VCMIServer class to use as connection for gameplay communication
|
||||
- Lobby server accepts new connection and then sends reply to client using `joinRoomSuccess` message.
|
||||
- Game client receives message and establishes own side of proxy connection - connects to lobby, sends `clientProxyLogin` message and transfers to ServerHandler class to use as connection for gameplay communication
|
||||
- Lobby server accepts new connection and moves it into a proxy mode - all packages that will be received by one side of this connection will be re-sent to another side without any processing.
|
||||
|
||||
Once the game is over (or if one side disconnects) lobby server will close another side of the connection and erase proxy connection
|
@ -140,12 +140,7 @@ CLoadFile/CSaveFile classes allow to read data to file and store data to file. T
|
||||
|
||||
### Networking
|
||||
|
||||
CConnection class provides support for sending data structures over TCP/IP protocol. It provides 3 constructors:
|
||||
1. connect to given hostname at given port (host must be accepting connection)
|
||||
2. accept connection (takes boost.asio acceptor and io_service)
|
||||
3. adapt boost.asio socket with already established connection
|
||||
|
||||
All three constructors take as the last parameter the name that will be used to identify the peer.
|
||||
See [Networking](Networking.md)
|
||||
|
||||
## Additional features
|
||||
|
||||
|
@ -105,6 +105,14 @@ static std::string enumCheck(JsonValidator & validator, const JsonNode & baseSch
|
||||
return validator.makeErrorMessage("Key must have one of predefined values");
|
||||
}
|
||||
|
||||
static std::string constCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
|
||||
{
|
||||
if (data == schema)
|
||||
return "";
|
||||
|
||||
return validator.makeErrorMessage("Key must have have constant value");
|
||||
}
|
||||
|
||||
static std::string typeCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
|
||||
{
|
||||
static const std::unordered_map<std::string, JsonNode::JsonType> stringToType =
|
||||
@ -496,6 +504,7 @@ JsonValidator::TValidatorMap createCommonFields()
|
||||
ret["anyOf"] = anyOfCheck;
|
||||
ret["oneOf"] = oneOfCheck;
|
||||
ret["enum"] = enumCheck;
|
||||
ret["const"] = constCheck;
|
||||
ret["type"] = typeCheck;
|
||||
ret["not"] = notCheck;
|
||||
ret["$ref"] = refCheck;
|
||||
@ -513,7 +522,6 @@ JsonValidator::TValidatorMap createCommonFields()
|
||||
// Not implemented
|
||||
ret["propertyNames"] = notImplementedCheck;
|
||||
ret["contains"] = notImplementedCheck;
|
||||
ret["const"] = notImplementedCheck;
|
||||
ret["examples"] = notImplementedCheck;
|
||||
|
||||
return ret;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* EInfoWindowMode.h, part of VCMI engine
|
||||
* EntityChanges.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
|
@ -12,12 +12,17 @@
|
||||
#include "LobbyServer.h"
|
||||
|
||||
#include "../lib/logging/CBasicLogConfigurator.h"
|
||||
#include "../lib/filesystem/CFilesystemLoader.h"
|
||||
#include "../lib/filesystem/Filesystem.h"
|
||||
#include "../lib/VCMIDirs.h"
|
||||
|
||||
static const int LISTENING_PORT = 30303;
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
CResourceHandler::initialize();
|
||||
CResourceHandler::load("config/filesystem.json"); // FIXME: we actually need only config directory for schemas, can be reduced
|
||||
|
||||
#ifndef VCMI_IOS
|
||||
console = new CConsoleHandler();
|
||||
#endif
|
||||
|
@ -12,7 +12,9 @@
|
||||
|
||||
#include "LobbyDatabase.h"
|
||||
|
||||
#include "../lib/json/JsonFormatException.h"
|
||||
#include "../lib/json/JsonNode.h"
|
||||
#include "../lib/json/JsonUtils.h"
|
||||
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
@ -58,6 +60,7 @@ NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) c
|
||||
|
||||
void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json)
|
||||
{
|
||||
assert(JsonUtils::validate(json, "vcmi:lobbyProtocol/" + json["type"].String(), json["type"].String() + " pack"));
|
||||
target->sendPacket(json.toBytes());
|
||||
}
|
||||
|
||||
@ -101,6 +104,7 @@ void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "chatHistory";
|
||||
reply["messages"].Vector(); // force creation of empty vector
|
||||
|
||||
for(const auto & message : boost::adaptors::reverse(history))
|
||||
{
|
||||
@ -123,6 +127,7 @@ void LobbyServer::broadcastActiveAccounts()
|
||||
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "activeAccounts";
|
||||
reply["accounts"].Vector(); // force creation of empty vector
|
||||
|
||||
for(const auto & account : activeAccountsStats)
|
||||
{
|
||||
@ -142,6 +147,7 @@ JsonNode LobbyServer::prepareActiveGameRooms()
|
||||
auto activeGameRoomStats = database->getActiveGameRooms();
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "activeGameRooms";
|
||||
reply["gameRooms"].Vector(); // force creation of empty vector
|
||||
|
||||
for(const auto & gameRoom : activeGameRoomStats)
|
||||
{
|
||||
@ -230,6 +236,45 @@ void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const
|
||||
broadcastActiveGameRooms();
|
||||
}
|
||||
|
||||
JsonNode LobbyServer::parseAndValidateMessage(const std::vector<std::byte> & message) const
|
||||
{
|
||||
JsonParsingSettings parserSettings;
|
||||
parserSettings.mode = JsonParsingSettings::JsonFormatMode::JSON;
|
||||
parserSettings.maxDepth = 2;
|
||||
parserSettings.strict = true;
|
||||
|
||||
JsonNode json;
|
||||
try
|
||||
{
|
||||
JsonNode jsonTemp(message.data(), message.size());
|
||||
json = std::move(jsonTemp);
|
||||
}
|
||||
catch (const JsonFormatException & e)
|
||||
{
|
||||
logGlobal->info(std::string("Json parsing error encountered: ") + e.what());
|
||||
return JsonNode();
|
||||
}
|
||||
|
||||
std::string messageType = json["type"].String();
|
||||
|
||||
if (messageType.empty())
|
||||
{
|
||||
logGlobal->info("Json parsing error encountered: Message type not set!");
|
||||
return JsonNode();
|
||||
}
|
||||
|
||||
std::string schemaName = "vcmi:lobbyProtocol/" + messageType;
|
||||
|
||||
if (!JsonUtils::validate(json, schemaName, messageType + " pack"))
|
||||
{
|
||||
logGlobal->info("Json validation error encountered!");
|
||||
assert(0);
|
||||
return JsonNode();
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector<std::byte> & message)
|
||||
{
|
||||
// proxy connection - no processing, only redirect
|
||||
@ -242,10 +287,7 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons
|
||||
logGlobal->info("Received unexpected message for inactive proxy!");
|
||||
}
|
||||
|
||||
JsonNode json(message.data(), message.size());
|
||||
|
||||
// TODO: check for json parsing errors
|
||||
// TODO: validate json based on received message type
|
||||
JsonNode json = parseAndValidateMessage(message);
|
||||
|
||||
std::string messageType = json["type"].String();
|
||||
|
||||
@ -258,8 +300,8 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons
|
||||
if(messageType == "sendChatMessage")
|
||||
return receiveSendChatMessage(connection, json);
|
||||
|
||||
if(messageType == "openGameRoom")
|
||||
return receiveOpenGameRoom(connection, json);
|
||||
if(messageType == "activateGameRoom")
|
||||
return receiveActivateGameRoom(connection, json);
|
||||
|
||||
if(messageType == "joinGameRoom")
|
||||
return receiveJoinGameRoom(connection, json);
|
||||
@ -330,7 +372,7 @@ void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection,
|
||||
std::string displayName = json["displayName"].String();
|
||||
std::string language = json["language"].String();
|
||||
|
||||
if(isAccountNameValid(displayName))
|
||||
if(!isAccountNameValid(displayName))
|
||||
return sendOperationFailed(connection, "Illegal account name");
|
||||
|
||||
if(database->isAccountNameExists(displayName))
|
||||
@ -464,7 +506,7 @@ void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connectio
|
||||
//connection->close();
|
||||
}
|
||||
|
||||
void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
void LobbyServer::receiveActivateGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string hostAccountID = json["hostAccountID"].String();
|
||||
std::string accountID = activeAccounts[connection];
|
||||
|
@ -61,6 +61,10 @@ class LobbyServer final : public INetworkServerListener
|
||||
|
||||
JsonNode prepareActiveGameRooms();
|
||||
|
||||
/// Attempts to load json from incoming byte stream and validate it
|
||||
/// Returns parsed json on success or empty json node on failure
|
||||
JsonNode parseAndValidateMessage(const std::vector<std::byte> & message) const;
|
||||
|
||||
void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, const std::string & displayName, const std::string & messageText);
|
||||
void sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie);
|
||||
void sendOperationFailed(const NetworkConnectionPtr & target, const std::string & reason);
|
||||
@ -77,7 +81,7 @@ class LobbyServer final : public INetworkServerListener
|
||||
void receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
|
||||
void receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveActivateGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "CVCMIServer.h"
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/json/JsonUtils.h"
|
||||
|
||||
GlobalLobbyProcessor::GlobalLobbyProcessor(CVCMIServer & owner)
|
||||
: owner(owner)
|
||||
@ -45,6 +46,8 @@ void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr<INetworkConnecti
|
||||
JsonNode message;
|
||||
message["type"].String() = "leaveGameRoom";
|
||||
message["accountID"].String() = proxy.first;
|
||||
|
||||
assert(JsonUtils::validate(message, "vcmi:lobbyProtocol/" + message["type"].String(), message["type"].String() + " pack"));
|
||||
controlConnection->sendPacket(message.toBytes());
|
||||
break;
|
||||
}
|
||||
@ -122,6 +125,9 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr<INetwor
|
||||
toSend["gameRoomID"].String() = owner.uuid;
|
||||
toSend["accountID"] = settings["lobby"]["accountID"];
|
||||
toSend["accountCookie"] = settings["lobby"]["accountCookie"];
|
||||
toSend["version"].String() = VCMI_VERSION_STRING;
|
||||
|
||||
assert(JsonUtils::validate(toSend, "vcmi:lobbyProtocol/" + toSend["type"].String(), toSend["type"].String() + " pack"));
|
||||
connection->sendPacket(toSend.toBytes());
|
||||
}
|
||||
else
|
||||
@ -137,6 +143,8 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr<INetwor
|
||||
toSend["gameRoomID"].String() = owner.uuid;
|
||||
toSend["guestAccountID"].String() = guestAccountID;
|
||||
toSend["accountCookie"] = settings["lobby"]["accountCookie"];
|
||||
|
||||
assert(JsonUtils::validate(toSend, "vcmi:lobbyProtocol/" + toSend["type"].String(), toSend["type"].String() + " pack"));
|
||||
connection->sendPacket(toSend.toBytes());
|
||||
|
||||
proxyConnections[guestAccountID] = connection;
|
||||
|
Loading…
x
Reference in New Issue
Block a user