2023-11-11 16:43:58 +02:00
|
|
|
/*
|
|
|
|
* LobbyServer.cpp, 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
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include "StdInc.h"
|
|
|
|
#include "LobbyServer.h"
|
|
|
|
|
|
|
|
#include "SQLiteConnection.h"
|
|
|
|
|
2023-11-12 15:32:54 +02:00
|
|
|
#include "../lib/JsonNode.h"
|
|
|
|
|
2023-11-11 16:43:58 +02:00
|
|
|
#include <boost/uuid/uuid_generators.hpp>
|
|
|
|
#include <boost/uuid/uuid_io.hpp>
|
|
|
|
|
2023-11-12 13:27:22 +02:00
|
|
|
static const std::string DATABASE_PATH = "/home/ivan/vcmi.db";
|
2023-11-11 16:43:58 +02:00
|
|
|
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()());
|
|
|
|
|
2023-11-12 15:32:54 +02:00
|
|
|
void LobbyDatabase::prepareStatements()
|
|
|
|
{
|
|
|
|
static const std::string insertChatMessageText = R"(
|
|
|
|
INSERT INTO chatMessages(senderName, messageText) VALUES( ?, ?);
|
|
|
|
)";
|
|
|
|
|
2023-11-12 16:35:53 +02:00
|
|
|
static const std::string getRecentMessageHistoryText = R"(
|
|
|
|
SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',sendTime) AS secondsElapsed
|
|
|
|
FROM chatMessages
|
|
|
|
WHERE secondsElapsed < 60*60*24
|
|
|
|
ORDER BY sendTime DESC
|
|
|
|
LIMIT 100
|
|
|
|
)";
|
|
|
|
|
2023-11-12 15:32:54 +02:00
|
|
|
insertChatMessageStatement = database->prepare(insertChatMessageText);
|
2023-11-12 16:35:53 +02:00
|
|
|
getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText);
|
2023-11-12 15:32:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
);
|
|
|
|
)";
|
|
|
|
|
|
|
|
auto statement = database->prepare(statementText);
|
|
|
|
statement->execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LobbyDatabase::initializeDatabase()
|
|
|
|
{
|
|
|
|
createTableChatMessages();
|
|
|
|
}
|
|
|
|
|
|
|
|
LobbyDatabase::LobbyDatabase()
|
|
|
|
{
|
|
|
|
database = SQLiteInstance::open(DATABASE_PATH, true);
|
|
|
|
|
|
|
|
if (!database)
|
|
|
|
throw std::runtime_error("Failed to open SQLite database!");
|
|
|
|
|
|
|
|
initializeDatabase();
|
|
|
|
prepareStatements();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LobbyDatabase::insertChatMessage(const std::string & sender, const std::string & messageText)
|
|
|
|
{
|
|
|
|
insertChatMessageStatement->setBinds(sender, messageText);
|
|
|
|
insertChatMessageStatement->execute();
|
|
|
|
insertChatMessageStatement->reset();
|
|
|
|
}
|
|
|
|
|
2023-12-27 19:07:49 +02:00
|
|
|
bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountName)
|
|
|
|
{
|
|
|
|
return false; //TODO
|
|
|
|
}
|
|
|
|
|
2023-11-12 16:35:53 +02:00
|
|
|
std::vector<LobbyDatabase::ChatMessage> LobbyDatabase::getRecentMessageHistory()
|
|
|
|
{
|
|
|
|
std::vector<LobbyDatabase::ChatMessage> result;
|
|
|
|
|
|
|
|
while(getRecentMessageHistoryStatement->execute())
|
|
|
|
{
|
|
|
|
LobbyDatabase::ChatMessage message;
|
|
|
|
getRecentMessageHistoryStatement->getColumns(message.sender, message.messageText, message.messageAgeSeconds);
|
|
|
|
result.push_back(message);
|
|
|
|
}
|
|
|
|
getRecentMessageHistoryStatement->reset();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LobbyServer::sendMessage(const std::shared_ptr<NetworkConnection> & target, const JsonNode & json)
|
|
|
|
{
|
|
|
|
//FIXME: 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();
|
|
|
|
|
|
|
|
std::vector<uint8_t> payloadBuffer(payloadBegin, payloadEnd);
|
|
|
|
|
2023-11-18 16:34:18 +02:00
|
|
|
networkServer->sendPacket(target, payloadBuffer);
|
2023-11-12 16:35:53 +02:00
|
|
|
}
|
|
|
|
|
2023-12-26 20:54:32 +02:00
|
|
|
void LobbyServer::onTimer()
|
|
|
|
{
|
|
|
|
// no-op
|
|
|
|
}
|
|
|
|
|
2023-11-12 16:35:53 +02:00
|
|
|
void LobbyServer::onNewConnection(const std::shared_ptr<NetworkConnection> & connection)
|
2023-11-11 16:43:58 +02:00
|
|
|
{
|
2023-11-12 21:23:42 +02:00
|
|
|
}
|
2023-11-11 16:43:58 +02:00
|
|
|
|
2023-11-18 16:34:18 +02:00
|
|
|
void LobbyServer::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
|
2023-11-12 21:23:42 +02:00
|
|
|
{
|
|
|
|
activeAccounts.erase(connection);
|
2023-11-11 16:43:58 +02:00
|
|
|
}
|
|
|
|
|
2023-11-12 21:23:42 +02:00
|
|
|
void LobbyServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
|
2023-11-11 16:43:58 +02:00
|
|
|
{
|
2023-11-12 15:32:54 +02:00
|
|
|
// FIXME: find better approach
|
|
|
|
const char * payloadBegin = reinterpret_cast<const char*>(message.data());
|
|
|
|
JsonNode json(payloadBegin, message.size());
|
2023-11-11 16:43:58 +02:00
|
|
|
|
2023-11-12 15:32:54 +02:00
|
|
|
if (json["type"].String() == "sendChatMessage")
|
2023-12-27 14:24:49 +02:00
|
|
|
return receiveSendChatMessage(connection, json);
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
if (json["type"].String() == "authentication")
|
|
|
|
return receiveAuthentication(connection, json);
|
2023-12-27 19:07:49 +02:00
|
|
|
|
|
|
|
if (json["type"].String() == "joinGameRoom")
|
|
|
|
return receiveJoinGameRoom(connection, json);
|
2023-12-27 14:24:49 +02:00
|
|
|
}
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
void LobbyServer::receiveSendChatMessage(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json)
|
|
|
|
{
|
|
|
|
if (activeAccounts.count(connection) == 0)
|
|
|
|
return; // unauthenticated
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
std::string senderName = activeAccounts[connection].accountName;
|
|
|
|
std::string messageText = json["messageText"].String();
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
database->insertChatMessage(senderName, messageText);
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
JsonNode reply;
|
|
|
|
reply["type"].String() = "chatMessage";
|
|
|
|
reply["messageText"].String() = messageText;
|
|
|
|
reply["senderName"].String() = senderName;
|
|
|
|
|
|
|
|
for (auto const & connection : activeAccounts)
|
|
|
|
sendMessage(connection.first, reply);
|
|
|
|
}
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
void LobbyServer::receiveAuthentication(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json)
|
|
|
|
{
|
|
|
|
std::string accountName = json["accountName"].String();
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 19:07:49 +02:00
|
|
|
// TODO: account cookie check
|
|
|
|
// TODO: account password check
|
|
|
|
// TODO: protocol version number
|
|
|
|
// TODO: client/server mode flag
|
|
|
|
// TODO: client language
|
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
activeAccounts[connection].accountName = accountName;
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
auto history = database->getRecentMessageHistory();
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
JsonNode reply;
|
|
|
|
reply["type"].String() = "chatHistory";
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
for (auto const & message : boost::adaptors::reverse(history))
|
|
|
|
{
|
|
|
|
JsonNode jsonEntry;
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
jsonEntry["messageText"].String() = message.messageText;
|
|
|
|
jsonEntry["senderName"].String() = message.sender;
|
|
|
|
jsonEntry["ageSeconds"].Integer() = message.messageAgeSeconds;
|
2023-11-12 21:23:42 +02:00
|
|
|
|
2023-12-27 14:24:49 +02:00
|
|
|
reply["messages"].Vector().push_back(jsonEntry);
|
2023-11-12 15:32:54 +02:00
|
|
|
}
|
2023-12-27 14:24:49 +02:00
|
|
|
|
|
|
|
sendMessage(connection, reply);
|
2023-11-11 16:43:58 +02:00
|
|
|
}
|
|
|
|
|
2023-12-27 19:07:49 +02:00
|
|
|
void LobbyServer::receiveJoinGameRoom(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json)
|
|
|
|
{
|
|
|
|
if (activeAccounts.count(connection) == 0)
|
|
|
|
return; // unauthenticated
|
|
|
|
|
|
|
|
std::string senderName = activeAccounts[connection].accountName;
|
|
|
|
|
|
|
|
if (database->isPlayerInGameRoom(senderName))
|
|
|
|
return; // only 1 room per player allowed
|
|
|
|
|
|
|
|
// TODO: roomType: private, public
|
|
|
|
// TODO: additional flags, e.g. allowCheats
|
|
|
|
// TODO: connection mode: direct or proxy
|
|
|
|
}
|
|
|
|
|
2023-11-11 16:43:58 +02:00
|
|
|
LobbyServer::LobbyServer()
|
2023-11-12 15:32:54 +02:00
|
|
|
: database(new LobbyDatabase())
|
2023-11-18 16:34:18 +02:00
|
|
|
, networkServer(new NetworkServer(*this))
|
2023-11-11 16:43:58 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-11-18 16:34:18 +02:00
|
|
|
void LobbyServer::start(uint16_t port)
|
|
|
|
{
|
|
|
|
networkServer->start(port);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LobbyServer::run()
|
|
|
|
{
|
|
|
|
networkServer->run();
|
|
|
|
}
|
|
|
|
|
2023-11-11 16:43:58 +02:00
|
|
|
int main(int argc, const char * argv[])
|
|
|
|
{
|
|
|
|
LobbyServer server;
|
|
|
|
|
|
|
|
server.start(LISTENING_PORT);
|
|
|
|
server.run();
|
|
|
|
}
|