mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-22 22:13:35 +02:00
Extension of lobby server functionality
Support for: - listing of active players - listing of active rooms - joining and leaving rooms - placeholder support for multiple chat rooms - proxy connections - invites into private rooms (only lobby server side for now, client and match server need work)
This commit is contained in:
parent
50c1452221
commit
4271fb3c95
@ -75,7 +75,7 @@
|
||||
"vcmi.lobby.login.title" : "VCMI Lobby",
|
||||
"vcmi.lobby.login.username" : "Username:",
|
||||
"vcmi.lobby.login.connecting" : "Connecting...",
|
||||
// "vcmi.lobby.login.connectionFailed" : "Connection failed: %s",
|
||||
"vcmi.lobby.login.error" : "Connection error: %s",
|
||||
|
||||
"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
|
||||
"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
|
||||
|
@ -15,6 +15,9 @@ class NetworkConnection;
|
||||
class NetworkServer;
|
||||
class NetworkClient;
|
||||
|
||||
using NetworkConnectionPtr = std::shared_ptr<NetworkConnection>;
|
||||
using NetworkConnectionWeakPtr = std::weak_ptr<NetworkConnection>;
|
||||
|
||||
class DLL_LINKAGE INetworkConnectionListener
|
||||
{
|
||||
friend class NetworkConnection;
|
||||
@ -22,7 +25,8 @@ protected:
|
||||
virtual void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) = 0;
|
||||
virtual void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) = 0;
|
||||
|
||||
~INetworkConnectionListener() = default;
|
||||
public:
|
||||
virtual ~INetworkConnectionListener() = default;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE INetworkServerListener : public INetworkConnectionListener
|
||||
@ -32,7 +36,8 @@ protected:
|
||||
virtual void onNewConnection(const std::shared_ptr<NetworkConnection> &) = 0;
|
||||
virtual void onTimer() = 0;
|
||||
|
||||
~INetworkServerListener() = default;
|
||||
public:
|
||||
virtual ~INetworkServerListener() = default;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE INetworkClientListener : public INetworkConnectionListener
|
||||
@ -43,7 +48,8 @@ protected:
|
||||
virtual void onConnectionEstablished(const std::shared_ptr<NetworkConnection> &) = 0;
|
||||
virtual void onTimer() = 0;
|
||||
|
||||
~INetworkClientListener() = default;
|
||||
public:
|
||||
virtual ~INetworkClientListener() = default;
|
||||
};
|
||||
|
||||
|
||||
|
@ -11,6 +11,7 @@ set(lobby_HEADERS
|
||||
StdInc.h
|
||||
|
||||
LobbyDatabase.h
|
||||
LobbyDefines.h
|
||||
LobbyServer.h
|
||||
SQLiteConnection.h
|
||||
)
|
||||
|
@ -11,14 +11,15 @@
|
||||
|
||||
#include "LobbyServer.h"
|
||||
|
||||
static const std::string DATABASE_PATH = "/home/ivan/vcmi.db";
|
||||
#include "../lib/VCMIDirs.h"
|
||||
|
||||
static const int LISTENING_PORT = 30303;
|
||||
//static const std::string SERVER_NAME = GameConstants::VCMI_VERSION + " (server)";
|
||||
//static const std::string SERVER_UUID = boost::uuids::to_string(boost::uuids::random_generator()());
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
LobbyServer server(DATABASE_PATH);
|
||||
auto databasePath = VCMIDirs::get().userDataPath() / "vcmiLobby.db";
|
||||
|
||||
LobbyServer server(databasePath);
|
||||
|
||||
server.start(LISTENING_PORT);
|
||||
server.run();
|
||||
|
@ -12,76 +12,256 @@
|
||||
|
||||
#include "SQLiteConnection.h"
|
||||
|
||||
void LobbyDatabase::createTables()
|
||||
{
|
||||
static const std::string createChatMessages = R"(
|
||||
CREATE TABLE IF NOT EXISTS chatMessages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
senderName TEXT,
|
||||
roomType TEXT,
|
||||
messageText TEXT,
|
||||
creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
)";
|
||||
|
||||
static const std::string createTableGameRooms = R"(
|
||||
CREATE TABLE IF NOT EXISTS gameRooms (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
roomID TEXT,
|
||||
hostAccountID TEXT,
|
||||
status INTEGER NOT NULL DEFAULT 0,
|
||||
playerLimit INTEGER NOT NULL,
|
||||
creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
)";
|
||||
|
||||
static const std::string createTableGameRoomPlayers = R"(
|
||||
CREATE TABLE IF NOT EXISTS gameRoomPlayers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
roomID TEXT,
|
||||
accountID TEXT
|
||||
);
|
||||
)";
|
||||
|
||||
static const std::string createTableAccounts = R"(
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
accountID TEXT,
|
||||
displayName TEXT,
|
||||
online INTEGER NOT NULL,
|
||||
lastLoginTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
)";
|
||||
|
||||
static const std::string createTableAccountCookies = R"(
|
||||
CREATE TABLE IF NOT EXISTS accountCookies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
accountID TEXT,
|
||||
cookieUUID TEXT,
|
||||
creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
)";
|
||||
|
||||
static const std::string createTableGameRoomInvites = R"(
|
||||
CREATE TABLE IF NOT EXISTS gameRoomInvites (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
roomID TEXT,
|
||||
accountID TEXT
|
||||
);
|
||||
)";
|
||||
|
||||
database->prepare(createChatMessages)->execute();
|
||||
database->prepare(createTableGameRoomPlayers)->execute();
|
||||
database->prepare(createTableGameRooms)->execute();
|
||||
database->prepare(createTableAccounts)->execute();
|
||||
database->prepare(createTableAccountCookies)->execute();
|
||||
database->prepare(createTableGameRoomInvites)->execute();
|
||||
}
|
||||
|
||||
void LobbyDatabase::prepareStatements()
|
||||
{
|
||||
// INSERT INTO
|
||||
|
||||
static const std::string insertChatMessageText = R"(
|
||||
INSERT INTO chatMessages(senderName, messageText) VALUES( ?, ?);
|
||||
)";
|
||||
|
||||
static const std::string insertAccountText = R"(
|
||||
INSERT INTO accounts(accountID, displayName, online) VALUES(?,?,0);
|
||||
)";
|
||||
|
||||
static const std::string insertAccessCookieText = R"(
|
||||
INSERT INTO accountCookies(accountID, cookieUUID) VALUES(?,?);
|
||||
)";
|
||||
|
||||
static const std::string insertGameRoomText = R"(
|
||||
INSERT INTO gameRooms(roomID, hostAccountID, status, playerLimit) VALUES(?, ?, 'empty', 8);
|
||||
)";
|
||||
|
||||
static const std::string insertGameRoomPlayersText = R"(
|
||||
INSERT INTO gameRoomPlayers(roomID, accountID) VALUES(?,?);
|
||||
)";
|
||||
|
||||
static const std::string insertGameRoomInvitesText = R"(
|
||||
INSERT INTO gameRoomInvites(roomID, accountID) VALUES(?,?);
|
||||
)";
|
||||
|
||||
// DELETE FROM
|
||||
|
||||
static const std::string deleteGameRoomPlayersText = R"(
|
||||
DELETE FROM gameRoomPlayers WHERE gameRoomID = ? AND accountID = ?
|
||||
)";
|
||||
|
||||
static const std::string deleteGameRoomInvitesText = R"(
|
||||
DELETE FROM gameRoomInvites WHERE gameRoomID = ? AND accountID = ?
|
||||
)";
|
||||
|
||||
// UPDATE
|
||||
|
||||
static const std::string setGameRoomStatusText = R"(
|
||||
UPDATE gameRooms
|
||||
SET status = ?
|
||||
WHERE roomID = ?
|
||||
)";
|
||||
|
||||
static const std::string setGameRoomPlayerLimitText = R"(
|
||||
UPDATE gameRooms
|
||||
SET playerLimit = ?
|
||||
WHERE roomID = ?
|
||||
)";
|
||||
|
||||
// SELECT FROM
|
||||
|
||||
static const std::string getRecentMessageHistoryText = R"(
|
||||
SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',sendTime) AS secondsElapsed
|
||||
SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) AS secondsElapsed
|
||||
FROM chatMessages
|
||||
WHERE secondsElapsed < 60*60*24
|
||||
ORDER BY sendTime DESC
|
||||
WHERE secondsElapsed < 60*60*18
|
||||
ORDER BY creationTime DESC
|
||||
LIMIT 100
|
||||
)";
|
||||
|
||||
insertChatMessageStatement = database->prepare(insertChatMessageText);
|
||||
getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText);
|
||||
}
|
||||
|
||||
void LobbyDatabase::createTableChatMessages()
|
||||
{
|
||||
static const std::string statementText = R"(
|
||||
CREATE TABLE IF NOT EXISTS chatMessages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
senderName TEXT,
|
||||
messageText TEXT,
|
||||
sendTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
static const std::string getIdleGameRoomText = R"(
|
||||
SELECT roomID
|
||||
FROM gameRooms
|
||||
WHERE hostAccountID = ? AND status = 'idle'
|
||||
LIMIT 1
|
||||
)";
|
||||
|
||||
auto statement = database->prepare(statementText);
|
||||
statement->execute();
|
||||
}
|
||||
static const std::string getAccountGameRoomText = R"(
|
||||
SELECT roomID
|
||||
FROM gameRoomPlayers grp
|
||||
LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID
|
||||
WHERE accountID = ? AND status IN ('public', 'private', 'busy')
|
||||
LIMIT 1
|
||||
)";
|
||||
|
||||
void LobbyDatabase::initializeDatabase()
|
||||
{
|
||||
createTableChatMessages();
|
||||
static const std::string getActiveAccountsText = R"(
|
||||
SELECT accountID, displayName
|
||||
FROM accounts
|
||||
WHERE online <> 1
|
||||
)";
|
||||
|
||||
static const std::string isAccountCookieValidText = R"(
|
||||
SELECT COUNT(accountID)
|
||||
FROM accountCookies
|
||||
WHERE accountID = ? AND cookieUUID = ? AND strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) < ?
|
||||
)";
|
||||
|
||||
static const std::string isGameRoomCookieValidText = R"(
|
||||
SELECT COUNT(roomID)
|
||||
FROM gameRooms
|
||||
LEFT JOIN accountCookies ON accountCookies.accountID = gameRooms.hostAccountID
|
||||
WHERE roomID = ? AND cookieUUID = ? AND strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) < ?
|
||||
)";
|
||||
|
||||
static const std::string isPlayerInGameRoomText = R"(
|
||||
SELECT COUNT(accountID)
|
||||
FROM gameRoomPlayers
|
||||
WHERE accountID = ? AND roomID = ?
|
||||
)";
|
||||
|
||||
static const std::string isPlayerInAnyGameRoomText = R"(
|
||||
SELECT COUNT(accountID)
|
||||
FROM gameRoomPlayers
|
||||
WHERE accountID = ?
|
||||
)";
|
||||
|
||||
static const std::string isAccountExistsText = R"(
|
||||
SELECT COUNT(accountID)
|
||||
FROM accounts
|
||||
WHERE accountID = ?
|
||||
)";
|
||||
|
||||
insertChatMessageStatement = database->prepare(insertChatMessageText);
|
||||
insertAccountStatement = database->prepare(insertAccountText);
|
||||
insertAccessCookieStatement = database->prepare(insertAccessCookieText);
|
||||
insertGameRoomStatement = database->prepare(insertGameRoomText);
|
||||
insertGameRoomPlayersStatement = database->prepare(insertGameRoomPlayersText);
|
||||
insertGameRoomInvitesStatement = database->prepare(insertGameRoomInvitesText);
|
||||
|
||||
deleteGameRoomPlayersStatement = database->prepare(deleteGameRoomPlayersText);
|
||||
deleteGameRoomInvitesStatement = database->prepare(deleteGameRoomInvitesText);
|
||||
|
||||
setGameRoomStatusStatement = database->prepare(setGameRoomStatusText);
|
||||
setGameRoomPlayerLimitStatement = database->prepare(setGameRoomPlayerLimitText);
|
||||
|
||||
getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText);
|
||||
getIdleGameRoomStatement = database->prepare(getIdleGameRoomText);
|
||||
getAccountGameRoomStatement = database->prepare(getAccountGameRoomText);
|
||||
getActiveAccountsStatement = database->prepare(getActiveAccountsText);
|
||||
|
||||
isAccountCookieValidStatement = database->prepare(isAccountCookieValidText);
|
||||
isPlayerInGameRoomStatement = database->prepare(isPlayerInGameRoomText);
|
||||
isPlayerInAnyGameRoomStatement = database->prepare(isPlayerInAnyGameRoomText);
|
||||
isAccountExistsStatement = database->prepare(isAccountExistsText);
|
||||
}
|
||||
|
||||
LobbyDatabase::~LobbyDatabase() = default;
|
||||
|
||||
LobbyDatabase::LobbyDatabase(const std::string & databasePath)
|
||||
LobbyDatabase::LobbyDatabase(const boost::filesystem::path & databasePath)
|
||||
{
|
||||
database = SQLiteInstance::open(databasePath, true);
|
||||
|
||||
if(!database)
|
||||
throw std::runtime_error("Failed to open SQLite database!");
|
||||
|
||||
initializeDatabase();
|
||||
createTables();
|
||||
prepareStatements();
|
||||
}
|
||||
|
||||
void LobbyDatabase::insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomName, const std::string & messageText)
|
||||
{
|
||||
insertChatMessageStatement->setBinds(sender, messageText);
|
||||
insertChatMessageStatement->execute();
|
||||
insertChatMessageStatement->reset();
|
||||
insertChatMessageStatement->executeOnce(sender, messageText);
|
||||
}
|
||||
|
||||
bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountName)
|
||||
bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID)
|
||||
{
|
||||
return false; //TODO
|
||||
bool result = false;
|
||||
|
||||
isPlayerInAnyGameRoomStatement->setBinds(accountID);
|
||||
if (isPlayerInAnyGameRoomStatement->execute())
|
||||
isPlayerInAnyGameRoomStatement->getColumns(result);
|
||||
isPlayerInAnyGameRoomStatement->reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<LobbyDatabase::ChatMessage> LobbyDatabase::getRecentMessageHistory()
|
||||
bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID, const std::string & roomID)
|
||||
{
|
||||
std::vector<LobbyDatabase::ChatMessage> result;
|
||||
bool result = false;
|
||||
|
||||
isPlayerInGameRoomStatement->setBinds(accountID, roomID);
|
||||
if (isPlayerInGameRoomStatement->execute())
|
||||
isPlayerInGameRoomStatement->getColumns(result);
|
||||
isPlayerInGameRoomStatement->reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<LobbyChatMessage> LobbyDatabase::getRecentMessageHistory()
|
||||
{
|
||||
std::vector<LobbyChatMessage> result;
|
||||
|
||||
while(getRecentMessageHistoryStatement->execute())
|
||||
{
|
||||
LobbyDatabase::ChatMessage message;
|
||||
LobbyChatMessage message;
|
||||
getRecentMessageHistoryStatement->getColumns(message.sender, message.messageText, message.age);
|
||||
result.push_back(message);
|
||||
}
|
||||
@ -89,3 +269,144 @@ std::vector<LobbyDatabase::ChatMessage> LobbyDatabase::getRecentMessageHistory()
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LobbyDatabase::setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus)
|
||||
{
|
||||
setGameRoomStatusStatement->executeOnce(vstd::to_underlying(roomStatus), roomID);
|
||||
}
|
||||
|
||||
void LobbyDatabase::setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit)
|
||||
{
|
||||
setGameRoomPlayerLimitStatement->executeOnce(playerLimit, roomID);
|
||||
}
|
||||
|
||||
void LobbyDatabase::insertPlayerIntoGameRoom(const std::string & accountID, const std::string & roomID)
|
||||
{
|
||||
insertGameRoomPlayersStatement->executeOnce(roomID, accountID);
|
||||
}
|
||||
|
||||
void LobbyDatabase::deletePlayerFromGameRoom(const std::string & accountID, const std::string & roomID)
|
||||
{
|
||||
deleteGameRoomPlayersStatement->executeOnce(roomID, accountID);
|
||||
}
|
||||
|
||||
void LobbyDatabase::deleteGameRoomInvite(const std::string & targetAccountID, const std::string & roomID)
|
||||
{
|
||||
deleteGameRoomInvitesStatement->executeOnce(roomID, targetAccountID);
|
||||
}
|
||||
|
||||
void LobbyDatabase::insertGameRoomInvite(const std::string & targetAccountID, const std::string & roomID)
|
||||
{
|
||||
insertGameRoomInvitesStatement->executeOnce(roomID, targetAccountID);
|
||||
}
|
||||
|
||||
void LobbyDatabase::insertGameRoom(const std::string & roomID, const std::string & hostAccountID)
|
||||
{
|
||||
insertGameRoomStatement->executeOnce(roomID, hostAccountID);
|
||||
}
|
||||
|
||||
void LobbyDatabase::insertAccount(const std::string & accountID, const std::string & displayName)
|
||||
{
|
||||
insertAccountStatement->executeOnce(accountID, displayName);
|
||||
}
|
||||
|
||||
void LobbyDatabase::insertAccessCookie(const std::string & accountID, const std::string & accessCookieUUID)
|
||||
{
|
||||
insertAccessCookieStatement->executeOnce(accountID, accessCookieUUID);
|
||||
}
|
||||
|
||||
void LobbyDatabase::updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LobbyDatabase::updateAccountLoginTime(const std::string & accountID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LobbyDatabase::updateActiveAccount(const std::string & accountID, bool isActive)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::string LobbyDatabase::getAccountDisplayName(const std::string & accountID)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
LobbyCookieStatus LobbyDatabase::getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, std::chrono::seconds cookieLifetime)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
LobbyInviteStatus LobbyDatabase::getAccountInviteStatus(const std::string & accountID, const std::string & roomID)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
LobbyRoomState LobbyDatabase::getGameRoomStatus(const std::string & roomID)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t LobbyDatabase::getGameRoomFreeSlots(const std::string & roomID)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool LobbyDatabase::isAccountExists(const std::string & accountID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<LobbyGameRoom> LobbyDatabase::getActiveGameRooms()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<LobbyAccount> LobbyDatabase::getActiveAccounts()
|
||||
{
|
||||
std::vector<LobbyAccount> result;
|
||||
|
||||
while(getActiveAccountsStatement->execute())
|
||||
{
|
||||
LobbyAccount entry;
|
||||
getActiveAccountsStatement->getColumns(entry.accountID, entry.displayName);
|
||||
result.push_back(entry);
|
||||
}
|
||||
getActiveAccountsStatement->reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string LobbyDatabase::getIdleGameRoom(const std::string & hostAccountID)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if (getIdleGameRoomStatement->execute())
|
||||
getIdleGameRoomStatement->getColumns(result);
|
||||
|
||||
getIdleGameRoomStatement->reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string LobbyDatabase::getAccountGameRoom(const std::string & accountID)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if (getAccountGameRoomStatement->execute())
|
||||
getAccountGameRoomStatement->getColumns(result);
|
||||
|
||||
getAccountGameRoomStatement->reset();
|
||||
return result;
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "LobbyDefines.h"
|
||||
|
||||
class SQLiteInstance;
|
||||
class SQLiteStatement;
|
||||
|
||||
@ -23,61 +25,68 @@ class LobbyDatabase
|
||||
SQLiteStatementPtr insertAccountStatement;
|
||||
SQLiteStatementPtr insertAccessCookieStatement;
|
||||
SQLiteStatementPtr insertGameRoomStatement;
|
||||
SQLiteStatementPtr insertGameRoomPlayersStatement;
|
||||
SQLiteStatementPtr insertGameRoomInvitesStatement;
|
||||
|
||||
SQLiteStatementPtr checkAccessCookieStatement;
|
||||
SQLiteStatementPtr isPlayerInGameRoomStatement;
|
||||
SQLiteStatementPtr isAccountNameAvailableStatement;
|
||||
SQLiteStatementPtr deleteGameRoomPlayersStatement;
|
||||
SQLiteStatementPtr deleteGameRoomInvitesStatement;
|
||||
|
||||
SQLiteStatementPtr getRecentMessageHistoryStatement;
|
||||
SQLiteStatementPtr setGameRoomStatusStatement;
|
||||
SQLiteStatementPtr setGameRoomPlayerLimitStatement;
|
||||
SQLiteStatementPtr insertPlayerIntoGameRoomStatement;
|
||||
SQLiteStatementPtr removePlayerFromGameRoomStatement;
|
||||
|
||||
void initializeDatabase();
|
||||
SQLiteStatementPtr getRecentMessageHistoryStatement;
|
||||
SQLiteStatementPtr getIdleGameRoomStatement;
|
||||
SQLiteStatementPtr getAccountGameRoomStatement;
|
||||
SQLiteStatementPtr getActiveAccountsStatement;
|
||||
|
||||
SQLiteStatementPtr isAccountCookieValidStatement;
|
||||
SQLiteStatementPtr isGameRoomCookieValidStatement;
|
||||
SQLiteStatementPtr isPlayerInGameRoomStatement;
|
||||
SQLiteStatementPtr isPlayerInAnyGameRoomStatement;
|
||||
SQLiteStatementPtr isAccountExistsStatement;
|
||||
|
||||
void prepareStatements();
|
||||
|
||||
void createTableChatMessages();
|
||||
void createTableGameRoomPlayers();
|
||||
void createTableGameRooms();
|
||||
void createTableAccounts();
|
||||
void createTableAccountCookies();
|
||||
void createTables();
|
||||
|
||||
public:
|
||||
struct GameRoom
|
||||
{
|
||||
std::string roomUUID;
|
||||
std::string roomStatus;
|
||||
std::chrono::seconds age;
|
||||
uint32_t playersCount;
|
||||
uint32_t playersLimit;
|
||||
};
|
||||
|
||||
struct ChatMessage
|
||||
{
|
||||
std::string sender;
|
||||
std::string messageText;
|
||||
std::chrono::seconds age;
|
||||
};
|
||||
|
||||
explicit LobbyDatabase(const std::string & databasePath);
|
||||
explicit LobbyDatabase(const boost::filesystem::path & databasePath);
|
||||
~LobbyDatabase();
|
||||
|
||||
void setGameRoomStatus(const std::string & roomUUID, const std::string & roomStatus);
|
||||
void setGameRoomPlayerLimit(const std::string & roomUUID, uint32_t playerLimit);
|
||||
void setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus);
|
||||
void setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit);
|
||||
|
||||
void insertPlayerIntoGameRoom(const std::string & accountName, const std::string & roomUUID);
|
||||
void removePlayerFromGameRoom(const std::string & accountName, const std::string & roomUUID);
|
||||
void insertPlayerIntoGameRoom(const std::string & accountID, const std::string & roomID);
|
||||
void deletePlayerFromGameRoom(const std::string & accountID, const std::string & roomID);
|
||||
|
||||
void insertGameRoom(const std::string & roomUUID, const std::string & hostAccountName);
|
||||
void insertAccount(const std::string & accountName);
|
||||
void insertAccessCookie(const std::string & accountName, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime);
|
||||
void insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomName, const std::string & messageText);
|
||||
void deleteGameRoomInvite(const std::string & targetAccountID, const std::string & roomID);
|
||||
void insertGameRoomInvite(const std::string & targetAccountID, const std::string & roomID);
|
||||
|
||||
std::vector<GameRoom> getActiveGameRooms();
|
||||
std::vector<ChatMessage> getRecentMessageHistory();
|
||||
void insertGameRoom(const std::string & roomID, const std::string & hostAccountID);
|
||||
void insertAccount(const std::string & accountID, const std::string & displayName);
|
||||
void insertAccessCookie(const std::string & accountID, const std::string & accessCookieUUID);
|
||||
void insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomID, const std::string & messageText);
|
||||
|
||||
bool checkAccessCookie(const std::string & accountName, const std::string & accessCookieUUID);
|
||||
bool isPlayerInGameRoom(const std::string & accountName);
|
||||
bool isAccountNameAvailable(const std::string & accountName);
|
||||
void updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID);
|
||||
void updateAccountLoginTime(const std::string & accountID);
|
||||
void updateActiveAccount(const std::string & accountID, bool isActive);
|
||||
|
||||
std::vector<LobbyGameRoom> getActiveGameRooms();
|
||||
std::vector<LobbyAccount> getActiveAccounts();
|
||||
std::vector<LobbyAccount> getAccountsInRoom(const std::string & roomID);
|
||||
std::vector<LobbyChatMessage> getRecentMessageHistory();
|
||||
|
||||
std::string getIdleGameRoom(const std::string & hostAccountID);
|
||||
std::string getAccountGameRoom(const std::string & accountID);
|
||||
std::string getAccountDisplayName(const std::string & accountID);
|
||||
|
||||
LobbyCookieStatus getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime);
|
||||
LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime);
|
||||
LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, std::chrono::seconds cookieLifetime);
|
||||
LobbyInviteStatus getAccountInviteStatus(const std::string & accountID, const std::string & roomID);
|
||||
LobbyRoomState getGameRoomStatus(const std::string & roomID);
|
||||
uint32_t getGameRoomFreeSlots(const std::string & roomID);
|
||||
|
||||
bool isPlayerInGameRoom(const std::string & accountID);
|
||||
bool isPlayerInGameRoom(const std::string & accountID, const std::string & roomID);
|
||||
bool isAccountExists(const std::string & accountID);
|
||||
};
|
||||
|
56
lobby/LobbyDefines.h
Normal file
56
lobby/LobbyDefines.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* LobbyDefines.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
struct LobbyAccount
|
||||
{
|
||||
std::string accountID;
|
||||
std::string displayName;
|
||||
//std::string status;
|
||||
};
|
||||
|
||||
struct LobbyGameRoom
|
||||
{
|
||||
std::string roomUUID;
|
||||
std::string roomStatus;
|
||||
uint32_t playersCount;
|
||||
uint32_t playersLimit;
|
||||
};
|
||||
|
||||
struct LobbyChatMessage
|
||||
{
|
||||
std::string sender;
|
||||
std::string messageText;
|
||||
std::chrono::seconds age;
|
||||
};
|
||||
|
||||
enum class LobbyCookieStatus : int32_t
|
||||
{
|
||||
INVALID,
|
||||
EXPIRED,
|
||||
VALID
|
||||
};
|
||||
|
||||
enum class LobbyInviteStatus : int32_t
|
||||
{
|
||||
NOT_INVITED,
|
||||
INVITED,
|
||||
DECLINED
|
||||
};
|
||||
|
||||
enum class LobbyRoomState : int32_t
|
||||
{
|
||||
IDLE, // server is ready but no players are in the room
|
||||
PUBLIC, // host has joined and allows anybody to join
|
||||
PRIVATE, // host has joined but only allows those he invited to join
|
||||
//BUSY, // match is ongoing
|
||||
//CANCELLED, // game room was cancelled without starting the game
|
||||
//CLOSED, // game room was closed after playing for some time
|
||||
};
|
@ -14,126 +14,530 @@
|
||||
|
||||
#include "../lib/JsonNode.h"
|
||||
#include "../lib/network/NetworkServer.h"
|
||||
#include "../lib/network/NetworkConnection.h"
|
||||
|
||||
void LobbyServer::sendMessage(const std::shared_ptr<NetworkConnection> & target, const JsonNode & json)
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
|
||||
static const auto accountCookieLifetime = std::chrono::hours(24*7);
|
||||
|
||||
bool LobbyServer::isAccountNameValid(const std::string & accountName)
|
||||
{
|
||||
//FIXME: copy-paste from LobbyClient::sendMessage
|
||||
if (accountName.size() < 4)
|
||||
return false;
|
||||
|
||||
if (accountName.size() < 20)
|
||||
return false;
|
||||
|
||||
for (auto const & c : accountName)
|
||||
if (!std::isalnum(c))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) const
|
||||
{
|
||||
// TODO: sanitize message and remove any "weird" symbols from it
|
||||
return inputString;
|
||||
}
|
||||
|
||||
NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID)
|
||||
{
|
||||
for (auto const & account : activeAccounts)
|
||||
if (account.second.accountID == accountID)
|
||||
return account.first;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID)
|
||||
{
|
||||
for (auto const & account : activeGameRooms)
|
||||
if (account.second.roomID == gameRoomID)
|
||||
return account.first;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json)
|
||||
{
|
||||
//NOTE: copy-paste from LobbyClient::sendMessage
|
||||
std::string payloadString = json.toJson(true);
|
||||
|
||||
// FIXME: find better approach
|
||||
uint8_t * payloadBegin = reinterpret_cast<uint8_t *>(payloadString.data());
|
||||
uint8_t * payloadEnd = payloadBegin + payloadString.size();
|
||||
// TODO: find better approach
|
||||
const uint8_t * payloadBegin = reinterpret_cast<uint8_t *>(payloadString.data());
|
||||
const uint8_t * payloadEnd = payloadBegin + payloadString.size();
|
||||
|
||||
std::vector<uint8_t> payloadBuffer(payloadBegin, payloadEnd);
|
||||
|
||||
networkServer->sendPacket(target, payloadBuffer);
|
||||
}
|
||||
|
||||
void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie)
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "accountCreated";
|
||||
reply["accountID"].String() = accountID;
|
||||
reply["accountCookie"].String() = accountCookie;
|
||||
sendMessage(target, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID)
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "inviteReceived";
|
||||
reply["accountID"].String() = accountID;
|
||||
reply["gameRoomID"].String() = gameRoomID;
|
||||
sendMessage(target, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason)
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "loginFailed";
|
||||
reply["reason"].String() = reason;
|
||||
sendMessage(target, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie)
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "loginSuccess";
|
||||
reply["accountCookie"].String() = accountCookie;
|
||||
sendMessage(target, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std::vector<LobbyChatMessage> & history)
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "chatHistory";
|
||||
|
||||
for(const auto & message : boost::adaptors::reverse(history))
|
||||
{
|
||||
JsonNode jsonEntry;
|
||||
|
||||
jsonEntry["messageText"].String() = message.messageText;
|
||||
jsonEntry["senderName"].String() = message.sender;
|
||||
jsonEntry["ageSeconds"].Integer() = message.age.count();
|
||||
|
||||
reply["messages"].Vector().push_back(jsonEntry);
|
||||
}
|
||||
|
||||
sendMessage(target, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::broadcastActiveAccounts()
|
||||
{
|
||||
auto activeAccountsStats = database->getActiveAccounts();
|
||||
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "activeAccounts";
|
||||
|
||||
for(const auto & account : activeAccountsStats)
|
||||
{
|
||||
JsonNode jsonEntry;
|
||||
jsonEntry["accountID"].String() = account.accountID;
|
||||
jsonEntry["displayName"].String() = account.displayName;
|
||||
// jsonEntry["status"].String() = account.status;
|
||||
reply["accounts"].Vector().push_back(jsonEntry);
|
||||
}
|
||||
|
||||
for(const auto & connection : activeAccounts)
|
||||
sendMessage(connection.first, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::broadcastActiveGameRooms()
|
||||
{
|
||||
auto activeGameRoomStats = database->getActiveGameRooms();
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "activeGameRooms";
|
||||
|
||||
for(const auto & gameRoom : activeGameRoomStats)
|
||||
{
|
||||
JsonNode jsonEntry;
|
||||
jsonEntry["gameRoomID"].String() = gameRoom.roomUUID;
|
||||
jsonEntry["status"].String() = gameRoom.roomStatus;
|
||||
jsonEntry["status"].Integer() = gameRoom.playersCount;
|
||||
jsonEntry["status"].Integer() = gameRoom.playersLimit;
|
||||
reply["gameRooms"].Vector().push_back(jsonEntry);
|
||||
}
|
||||
|
||||
for(const auto & connection : activeAccounts)
|
||||
sendMessage(connection.first, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID)
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "accountJoinsRoom";
|
||||
reply["accountID"].String() = accountID;
|
||||
sendMessage(target, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID)
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "joinRoomSuccess";
|
||||
reply["gameRoomID"].String() = gameRoomID;
|
||||
sendMessage(target, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & senderName, const std::string & messageText)
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "chatMessage";
|
||||
reply["messageText"].String() = messageText;
|
||||
reply["senderName"].String() = senderName;
|
||||
reply["roomMode"].String() = roomMode;
|
||||
reply["roomName"].String() = roomName;
|
||||
|
||||
sendMessage(target, reply);
|
||||
}
|
||||
|
||||
void LobbyServer::onTimer()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
void LobbyServer::onNewConnection(const std::shared_ptr<NetworkConnection> & connection)
|
||||
{}
|
||||
|
||||
void LobbyServer::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
|
||||
void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection)
|
||||
{
|
||||
activeAccounts.erase(connection);
|
||||
// no-op - waiting for incoming data
|
||||
}
|
||||
|
||||
void LobbyServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
|
||||
void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection)
|
||||
{
|
||||
// FIXME: find better approach
|
||||
// NOTE: lost connection can be in only one of these lists (or in none of them)
|
||||
// calling on all possible containers since calling std::map::erase() with non-existing key is legal
|
||||
activeAccounts.erase(connection);
|
||||
activeProxies.erase(connection);
|
||||
activeGameRooms.erase(connection);
|
||||
|
||||
broadcastActiveAccounts();
|
||||
broadcastActiveGameRooms();
|
||||
}
|
||||
|
||||
void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector<uint8_t> & message)
|
||||
{
|
||||
// proxy connection - no processing, only redirect
|
||||
if (activeProxies.count(connection))
|
||||
{
|
||||
auto lockedPtr = activeProxies.at(connection).lock();
|
||||
if (lockedPtr)
|
||||
lockedPtr->sendPacket(message);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode json(message.data(), message.size());
|
||||
|
||||
if(json["type"].String() == "sendChatMessage")
|
||||
return receiveSendChatMessage(connection, json);
|
||||
// TODO: check for json parsing errors
|
||||
// TODO: validate json based on received message type
|
||||
|
||||
if(json["type"].String() == "authentication")
|
||||
return receiveAuthentication(connection, json);
|
||||
// communication messages from vcmiclient
|
||||
if (activeAccounts.count(connection))
|
||||
{
|
||||
if(json["type"].String() == "sendChatMessage")
|
||||
return receiveSendChatMessage(connection, json);
|
||||
|
||||
if(json["type"].String() == "joinGameRoom")
|
||||
return receiveJoinGameRoom(connection, json);
|
||||
if(json["type"].String() == "openGameRoom")
|
||||
return receiveOpenGameRoom(connection, json);
|
||||
|
||||
if(json["type"].String() == "joinGameRoom")
|
||||
return receiveJoinGameRoom(connection, json);
|
||||
|
||||
if(json["type"].String() == "sendInvite")
|
||||
return receiveSendInvite(connection, json);
|
||||
|
||||
if(json["type"].String() == "declineInvite")
|
||||
return receiveDeclineInvite(connection, json);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// communication messages from vcmiserver
|
||||
if (activeGameRooms.count(connection))
|
||||
{
|
||||
if(json["type"].String() == "leaveGameRoom")
|
||||
return receiveLeaveGameRoom(connection, json);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// unauthorized connections - permit only login or register attempts
|
||||
if(json["type"].String() == "clientLogin")
|
||||
return receiveClientLogin(connection, json);
|
||||
|
||||
if(json["type"].String() == "clientRegister")
|
||||
return receiveClientRegister(connection, json);
|
||||
|
||||
if(json["type"].String() == "serverLogin")
|
||||
return receiveServerLogin(connection, json);
|
||||
|
||||
if(json["type"].String() == "clientProxyLogin")
|
||||
return receiveClientProxyLogin(connection, json);
|
||||
|
||||
if(json["type"].String() == "serverProxyLogin")
|
||||
return receiveServerProxyLogin(connection, json);
|
||||
|
||||
// TODO: add logging of suspicious connections.
|
||||
networkServer->closeConnection(connection);
|
||||
}
|
||||
|
||||
void LobbyServer::receiveSendChatMessage(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json)
|
||||
void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
if(activeAccounts.count(connection) == 0)
|
||||
return; // unauthenticated
|
||||
|
||||
std::string senderName = activeAccounts[connection].accountName;
|
||||
std::string senderName = activeAccounts[connection].accountID;
|
||||
std::string messageText = json["messageText"].String();
|
||||
std::string messageTextClean = sanitizeChatMessage(messageText);
|
||||
|
||||
database->insertChatMessage(senderName, "general", "english", messageText);
|
||||
if (messageTextClean.empty())
|
||||
return;
|
||||
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "chatMessage";
|
||||
reply["messageText"].String() = messageText;
|
||||
reply["senderName"].String() = senderName;
|
||||
database->insertChatMessage(senderName, "global", "english", messageText);
|
||||
|
||||
for(const auto & connection : activeAccounts)
|
||||
sendMessage(connection.first, reply);
|
||||
sendChatMessage(connection.first, "global", "english", senderName, messageText);
|
||||
}
|
||||
|
||||
void LobbyServer::receiveAuthentication(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json)
|
||||
void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string accountName = json["accountName"].String();
|
||||
std::string accountID = json["accountID"].String();
|
||||
std::string displayName = json["displayName"].String();
|
||||
std::string language = json["language"].String();
|
||||
|
||||
// TODO: account cookie check
|
||||
// TODO: account password check
|
||||
// TODO: protocol version number
|
||||
// TODO: client/server mode flag
|
||||
// TODO: client language
|
||||
if (database->isAccountExists(accountID))
|
||||
return sendLoginFailed(connection, "Account name already in use");
|
||||
|
||||
activeAccounts[connection].accountName = accountName;
|
||||
if (isAccountNameValid(accountID))
|
||||
return sendLoginFailed(connection, "Illegal account name");
|
||||
|
||||
std::string accountCookie = boost::uuids::to_string(boost::uuids::random_generator()());
|
||||
|
||||
database->insertAccount(accountID, displayName);
|
||||
database->insertAccessCookie(accountID, accountCookie);
|
||||
|
||||
sendAccountCreated(connection, accountID, accountCookie);
|
||||
}
|
||||
|
||||
void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string accountID = json["accountID"].String();
|
||||
std::string accountCookie = json["accountCookie"].String();
|
||||
std::string language = json["language"].String();
|
||||
std::string version = json["version"].String();
|
||||
|
||||
if (!database->isAccountExists(accountID))
|
||||
return sendLoginFailed(connection, "Account not found");
|
||||
|
||||
auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime);
|
||||
|
||||
if (clientCookieStatus == LobbyCookieStatus::INVALID)
|
||||
return sendLoginFailed(connection, "Authentification failure");
|
||||
|
||||
// prolong existing cookie
|
||||
database->updateAccessCookie(accountID, accountCookie);
|
||||
database->updateAccountLoginTime(accountID);
|
||||
|
||||
std::string displayName = database->getAccountDisplayName(accountID);
|
||||
|
||||
activeAccounts[connection].accountID = accountID;
|
||||
activeAccounts[connection].displayName = displayName;
|
||||
activeAccounts[connection].version = version;
|
||||
activeAccounts[connection].language = language;
|
||||
|
||||
sendLoginSuccess(connection, accountCookie);
|
||||
sendChatHistory(connection, database->getRecentMessageHistory());
|
||||
|
||||
// send active accounts list to new account
|
||||
// and update acount list to everybody else
|
||||
broadcastActiveAccounts();
|
||||
}
|
||||
|
||||
void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string gameRoomID = json["gameRoomID"].String();
|
||||
std::string accountID = json["accountID"].String();
|
||||
std::string accountCookie = json["accountCookie"].String();
|
||||
std::string version = json["version"].String();
|
||||
|
||||
auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime);
|
||||
|
||||
if (clientCookieStatus == LobbyCookieStatus::INVALID)
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "authentication";
|
||||
|
||||
sendMessage(connection, reply);
|
||||
sendLoginFailed(connection, "Invalid credentials");
|
||||
}
|
||||
|
||||
auto history = database->getRecentMessageHistory();
|
||||
|
||||
else
|
||||
{
|
||||
JsonNode reply;
|
||||
reply["type"].String() = "chatHistory";
|
||||
database->insertGameRoom(gameRoomID, accountID);
|
||||
activeGameRooms[connection].roomID = gameRoomID;
|
||||
sendLoginSuccess(connection, accountCookie);
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto & message : boost::adaptors::reverse(history))
|
||||
void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string gameRoomID = json["gameRoomID"].String();
|
||||
std::string accountID = json["accountID"].String();
|
||||
std::string accountCookie = json["accountCookie"].String();
|
||||
|
||||
auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime);
|
||||
|
||||
if (clientCookieStatus != LobbyCookieStatus::INVALID)
|
||||
{
|
||||
for (auto & proxyEntry : awaitingProxies)
|
||||
{
|
||||
JsonNode jsonEntry;
|
||||
if (proxyEntry.accountID != accountID)
|
||||
continue;
|
||||
if (proxyEntry.roomID != gameRoomID)
|
||||
continue;
|
||||
|
||||
jsonEntry["messageText"].String() = message.messageText;
|
||||
jsonEntry["senderName"].String() = message.sender;
|
||||
jsonEntry["ageSeconds"].Integer() = message.age.count();
|
||||
proxyEntry.accountConnection = connection;
|
||||
|
||||
reply["messages"].Vector().push_back(jsonEntry);
|
||||
auto gameRoomConnection = proxyEntry.roomConnection.lock();
|
||||
|
||||
if (gameRoomConnection)
|
||||
{
|
||||
activeProxies[gameRoomConnection] = connection;
|
||||
activeProxies[connection] = gameRoomConnection;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessage(connection, reply);
|
||||
}
|
||||
|
||||
networkServer->closeConnection(connection);
|
||||
}
|
||||
|
||||
void LobbyServer::receiveJoinGameRoom(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json)
|
||||
void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
if(activeAccounts.count(connection) == 0)
|
||||
return; // unauthenticated
|
||||
std::string gameRoomID = json["gameRoomID"].String();
|
||||
std::string guestAccountID = json["guestAccountID"].String();
|
||||
std::string hostCookie = json["hostCookie"].String();
|
||||
|
||||
std::string senderName = activeAccounts[connection].accountName;
|
||||
auto clientCookieStatus = database->getGameRoomCookieStatus(gameRoomID, hostCookie, accountCookieLifetime);
|
||||
|
||||
if(database->isPlayerInGameRoom(senderName))
|
||||
if (clientCookieStatus != LobbyCookieStatus::INVALID)
|
||||
{
|
||||
NetworkConnectionPtr targetAccount = findAccount(guestAccountID);
|
||||
|
||||
if (targetAccount == nullptr)
|
||||
return; // unknown / disconnected account
|
||||
|
||||
sendJoinRoomSuccess(targetAccount, gameRoomID);
|
||||
|
||||
AwaitingProxyState proxy;
|
||||
proxy.accountID = guestAccountID;
|
||||
proxy.roomID = gameRoomID;
|
||||
proxy.roomConnection = connection;
|
||||
awaitingProxies.push_back(proxy);
|
||||
return;
|
||||
}
|
||||
|
||||
networkServer->closeConnection(connection);
|
||||
}
|
||||
|
||||
void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string hostAccountID = json["hostAccountID"].String();
|
||||
std::string accountID = activeAccounts[connection].accountID;
|
||||
|
||||
if(database->isPlayerInGameRoom(accountID))
|
||||
return; // only 1 room per player allowed
|
||||
|
||||
// TODO: roomType: private, public
|
||||
// TODO: additional flags, e.g. allowCheats
|
||||
// TODO: connection mode: direct or proxy
|
||||
std::string gameRoomID = database->getIdleGameRoom(hostAccountID);
|
||||
if (gameRoomID.empty())
|
||||
return;
|
||||
|
||||
std::string roomType = json["roomType"].String();
|
||||
if (roomType == "public")
|
||||
database->setGameRoomStatus(gameRoomID, LobbyRoomState::PUBLIC);
|
||||
if (roomType == "private")
|
||||
database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE);
|
||||
|
||||
// TODO: additional flags / initial settings, e.g. allowCheats
|
||||
// TODO: connection mode: direct or proxy. For now direct is assumed
|
||||
|
||||
broadcastActiveGameRooms();
|
||||
sendJoinRoomSuccess(connection, gameRoomID);
|
||||
}
|
||||
|
||||
void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string gameRoomID = json["gameRoomID"].String();
|
||||
std::string accountID = activeAccounts[connection].accountID;
|
||||
|
||||
if(database->isPlayerInGameRoom(accountID))
|
||||
return; // only 1 room per player allowed
|
||||
|
||||
NetworkConnectionPtr targetRoom = findGameRoom(gameRoomID);
|
||||
|
||||
if (targetRoom == nullptr)
|
||||
return; // unknown / disconnected room
|
||||
|
||||
auto roomStatus = database->getGameRoomStatus(gameRoomID);
|
||||
|
||||
if (roomStatus == LobbyRoomState::PRIVATE)
|
||||
{
|
||||
if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
|
||||
return;
|
||||
}
|
||||
|
||||
if (database->getGameRoomFreeSlots(gameRoomID) == 0)
|
||||
return;
|
||||
|
||||
sendAccountJoinsRoom(targetRoom, accountID);
|
||||
//No reply to client - will be sent once match server establishes proxy connection with lobby
|
||||
|
||||
broadcastActiveGameRooms();
|
||||
}
|
||||
|
||||
void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string gameRoomID = json["gameRoomID"].String();
|
||||
std::string senderName = activeAccounts[connection].accountID;
|
||||
|
||||
if(!database->isPlayerInGameRoom(senderName, gameRoomID))
|
||||
return;
|
||||
|
||||
broadcastActiveGameRooms();
|
||||
}
|
||||
|
||||
void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string senderName = activeAccounts[connection].accountID;
|
||||
std::string accountID = json["accountID"].String();
|
||||
std::string gameRoomID = database->getAccountGameRoom(senderName);
|
||||
|
||||
auto targetAccount = findAccount(accountID);
|
||||
|
||||
if (!targetAccount)
|
||||
return; // target player does not exists or offline
|
||||
|
||||
if(!database->isPlayerInGameRoom(senderName))
|
||||
return; // current player is not in room
|
||||
|
||||
if(database->isPlayerInGameRoom(accountID))
|
||||
return; // target player is busy
|
||||
|
||||
if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::NOT_INVITED)
|
||||
return; // already has invite
|
||||
|
||||
database->insertGameRoomInvite(accountID, gameRoomID);
|
||||
sendInviteReceived(targetAccount, senderName, gameRoomID);
|
||||
}
|
||||
|
||||
void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
|
||||
{
|
||||
std::string accountID = activeAccounts[connection].accountID;
|
||||
std::string gameRoomID = json["gameRoomID"].String();
|
||||
|
||||
if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
|
||||
return; // already has invite
|
||||
|
||||
database->deleteGameRoomInvite(accountID, gameRoomID);
|
||||
}
|
||||
|
||||
LobbyServer::~LobbyServer() = default;
|
||||
|
||||
LobbyServer::LobbyServer(const std::string & databasePath)
|
||||
LobbyServer::LobbyServer(const boost::filesystem::path & databasePath)
|
||||
: database(new LobbyDatabase(databasePath))
|
||||
, networkServer(new NetworkServer(*this))
|
||||
{
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "LobbyDefines.h"
|
||||
#include "../lib/network/NetworkListener.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
@ -21,29 +22,78 @@ class LobbyServer : public INetworkServerListener
|
||||
{
|
||||
struct AccountState
|
||||
{
|
||||
std::string accountName;
|
||||
std::string accountID;
|
||||
std::string displayName;
|
||||
std::string version;
|
||||
std::string language;
|
||||
};
|
||||
struct GameRoomState
|
||||
{
|
||||
std::string roomID;
|
||||
};
|
||||
struct AwaitingProxyState
|
||||
{
|
||||
std::string accountID;
|
||||
std::string roomID;
|
||||
std::weak_ptr<NetworkConnection> accountConnection;
|
||||
std::weak_ptr<NetworkConnection> roomConnection;
|
||||
};
|
||||
|
||||
std::map<std::shared_ptr<NetworkConnection>, AccountState> activeAccounts;
|
||||
/// list of connected proxies. All messages received from (key) will be redirected to (value) connection
|
||||
std::map<NetworkConnectionPtr, std::weak_ptr<NetworkConnection>> activeProxies;
|
||||
|
||||
/// list of half-established proxies from server that are still waiting for client to connect
|
||||
std::vector<AwaitingProxyState> awaitingProxies;
|
||||
|
||||
/// list of logged in accounts (vcmiclient's)
|
||||
std::map<NetworkConnectionPtr, AccountState> activeAccounts;
|
||||
|
||||
/// list of currently logged in game rooms (vcmiserver's)
|
||||
std::map<NetworkConnectionPtr, GameRoomState> activeGameRooms;
|
||||
|
||||
std::unique_ptr<LobbyDatabase> database;
|
||||
std::unique_ptr<NetworkServer> networkServer;
|
||||
|
||||
std::string sanitizeChatMessage(const std::string & inputString) const;
|
||||
bool isAccountNameValid(const std::string & accountName);
|
||||
|
||||
void onNewConnection(const std::shared_ptr<NetworkConnection> &) override;
|
||||
void onDisconnected(const std::shared_ptr<NetworkConnection> &) override;
|
||||
void onPacketReceived(const std::shared_ptr<NetworkConnection> &, const std::vector<uint8_t> & message) override;
|
||||
NetworkConnectionPtr findAccount(const std::string & accountID);
|
||||
NetworkConnectionPtr findGameRoom(const std::string & gameRoomID);
|
||||
|
||||
void onNewConnection(const NetworkConnectionPtr & connection) override;
|
||||
void onDisconnected(const NetworkConnectionPtr & connection) override;
|
||||
void onPacketReceived(const NetworkConnectionPtr & connection, const std::vector<uint8_t> & message) override;
|
||||
void onTimer() override;
|
||||
|
||||
void sendMessage(const std::shared_ptr<NetworkConnection> & target, const JsonNode & json);
|
||||
void sendMessage(const NetworkConnectionPtr & target, const JsonNode & json);
|
||||
|
||||
void receiveSendChatMessage(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json);
|
||||
void receiveAuthentication(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json);
|
||||
void receiveJoinGameRoom(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json);
|
||||
void broadcastActiveAccounts();
|
||||
void broadcastActiveGameRooms();
|
||||
|
||||
void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & senderName, const std::string & messageText);
|
||||
void sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie);
|
||||
void sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason);
|
||||
void sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie);
|
||||
void sendChatHistory(const NetworkConnectionPtr & target, const std::vector<LobbyChatMessage> &);
|
||||
void sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID);
|
||||
void sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID);
|
||||
void sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID);
|
||||
|
||||
void receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
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 receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
void receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json);
|
||||
|
||||
public:
|
||||
LobbyServer(const std::string & databasePath);
|
||||
explicit LobbyServer(const boost::filesystem::path & databasePath);
|
||||
~LobbyServer();
|
||||
|
||||
void start(uint16_t port);
|
||||
|
@ -70,6 +70,12 @@ void SQLiteStatement::setBindSingle(size_t index, const double & value)
|
||||
checkSQLiteError(m_instance.m_connection, result);
|
||||
}
|
||||
|
||||
void SQLiteStatement::setBindSingle(size_t index, const bool & value)
|
||||
{
|
||||
int result = sqlite3_bind_int(m_statement, static_cast<int>(value), value);
|
||||
checkSQLiteError(m_instance.m_connection, result);
|
||||
}
|
||||
|
||||
void SQLiteStatement::setBindSingle(size_t index, const uint8_t & value)
|
||||
{
|
||||
int result = sqlite3_bind_int(m_statement, static_cast<int>(index), value);
|
||||
@ -116,6 +122,11 @@ void SQLiteStatement::getColumnSingle(size_t index, double & value)
|
||||
value = sqlite3_column_double(m_statement, static_cast<int>(index));
|
||||
}
|
||||
|
||||
void SQLiteStatement::getColumnSingle(size_t index, bool & value)
|
||||
{
|
||||
value = sqlite3_column_int(m_statement, static_cast<int>(index)) != 0;
|
||||
}
|
||||
|
||||
void SQLiteStatement::getColumnSingle(size_t index, uint8_t & value)
|
||||
{
|
||||
value = static_cast<uint8_t>(sqlite3_column_int(m_statement, static_cast<int>(index)));
|
||||
@ -147,7 +158,7 @@ void SQLiteStatement::getColumnSingle(size_t index, std::string & value)
|
||||
value = reinterpret_cast<const char *>(value_raw);
|
||||
}
|
||||
|
||||
SQLiteInstancePtr SQLiteInstance::open(const std::string & db_path, bool allow_write)
|
||||
SQLiteInstancePtr SQLiteInstance::open(const boost::filesystem::path & db_path, bool allow_write)
|
||||
{
|
||||
int flags = allow_write ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) : SQLITE_OPEN_READONLY;
|
||||
|
||||
|
@ -29,6 +29,14 @@ public:
|
||||
|
||||
~SQLiteStatement();
|
||||
|
||||
template<typename... Args>
|
||||
void executeOnce(const Args &... args)
|
||||
{
|
||||
setBinds(args...);
|
||||
execute();
|
||||
reset();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void setBinds(const Args &... args)
|
||||
{
|
||||
@ -43,6 +51,7 @@ public:
|
||||
|
||||
private:
|
||||
void setBindSingle(size_t index, const double & value);
|
||||
void setBindSingle(size_t index, const bool & value);
|
||||
void setBindSingle(size_t index, const uint8_t & value);
|
||||
void setBindSingle(size_t index, const uint16_t & value);
|
||||
void setBindSingle(size_t index, const uint32_t & value);
|
||||
@ -52,6 +61,7 @@ private:
|
||||
void setBindSingle(size_t index, const char * value);
|
||||
|
||||
void getColumnSingle(size_t index, double & value);
|
||||
void getColumnSingle(size_t index, bool & value);
|
||||
void getColumnSingle(size_t index, uint8_t & value);
|
||||
void getColumnSingle(size_t index, uint16_t & value);
|
||||
void getColumnSingle(size_t index, uint32_t & value);
|
||||
@ -92,7 +102,7 @@ class SQLiteInstance : boost::noncopyable
|
||||
public:
|
||||
friend class SQLiteStatement;
|
||||
|
||||
static SQLiteInstancePtr open(const std::string & db_path, bool allow_write);
|
||||
static SQLiteInstancePtr open(const boost::filesystem::path & db_path, bool allow_write);
|
||||
|
||||
~SQLiteInstance();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user