소스 검색

Initial version of global lobby server available in client

Ivan Savenko 1 년 전
부모
커밋
c2c43602ea

+ 11 - 0
CMakeLists.txt

@@ -89,6 +89,10 @@ if(NOT APPLE_IOS AND NOT ANDROID)
 	option(ENABLE_MONOLITHIC_INSTALL "Install everything in single directory on Linux and Mac" OFF)
 endif()
 
+if(NOT APPLE_IOS AND NOT ANDROID)
+	option(ENABLE_LOBBY "Enable compilation of lobby server" ON)
+endif()
+
 option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" OFF)
 if(ENABLE_CCACHE)
 	find_program(CCACHE ccache REQUIRED)
@@ -475,6 +479,10 @@ if(TARGET SDL2_ttf::SDL2_ttf)
 	add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf)
 endif()
 
+if(ENABLE_LOBBY)
+	find_package(SQLite3 REQUIRED)
+endif()
+
 if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
 	# Widgets finds its own dependencies (QtGui and QtCore).
 	find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
@@ -622,6 +630,9 @@ endif()
 if(ENABLE_EDITOR)
 	add_subdirectory(mapeditor)
 endif()
+if(ENABLE_LOBBY)
+	add_subdirectory(lobby)
+endif()
 add_subdirectory(client)
 add_subdirectory(server)
 if(ENABLE_TEST)

+ 4 - 0
client/CMakeLists.txt

@@ -95,6 +95,8 @@ set(client_SRCS
 	renderSDL/ScreenHandler.cpp
 	renderSDL/SDL_Extensions.cpp
 
+	serverLobby/LobbyWindow.cpp
+
 	widgets/Buttons.cpp
 	widgets/CArtifactHolder.cpp
 	widgets/CComponent.cpp
@@ -270,6 +272,8 @@ set(client_HEADERS
 	renderSDL/SDL_Extensions.h
 	renderSDL/SDL_PixelAccess.h
 
+	serverLobby/LobbyWindow.h
+
 	widgets/Buttons.h
 	widgets/CArtifactHolder.h
 	widgets/CComponent.h

+ 3 - 0
client/gui/InterfaceObjectConfigurable.cpp

@@ -129,6 +129,9 @@ void InterfaceObjectConfigurable::build(const JsonNode &config)
 	
 	for(const auto & item : items->Vector())
 		addWidget(item["name"].String(), buildWidget(item));
+
+	pos.w = config["width"].Integer();
+	pos.h = config["height"].Integer();
 }
 
 void InterfaceObjectConfigurable::addConditional(const std::string & name, bool active)

+ 9 - 0
client/mainmenu/CMainMenu.cpp

@@ -24,6 +24,7 @@
 #include "../gui/Shortcut.h"
 #include "../gui/WindowHandler.h"
 #include "../render/Canvas.h"
+#include "../serverLobby/LobbyWindow.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/MiscWidgets.h"
@@ -459,9 +460,17 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType)
 	buttonHotseat = std::make_shared<CButton>(Point(373, 78), AnimationPath::builtin("MUBHOT.DEF"), CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this));
 	buttonHost = std::make_shared<CButton>(Point(373, 78 + 57 * 1), AnimationPath::builtin("MUBHOST.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.hostTCP"), ""), std::bind(&CMultiMode::hostTCP, this));
 	buttonJoin = std::make_shared<CButton>(Point(373, 78 + 57 * 2), AnimationPath::builtin("MUBJOIN.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.joinTCP"), ""), std::bind(&CMultiMode::joinTCP, this));
+	buttonLobby = std::make_shared<CButton>(Point(373, 78 + 57 * 4), AnimationPath::builtin("MUBONL.DEF"), CGI->generaltexth->zelp[265], std::bind(&CMultiMode::openLobby, this));
+
 	buttonCancel = std::make_shared<CButton>(Point(373, 424), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[288], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
 }
 
+void CMultiMode::openLobby()
+{
+	close();
+	GH.windows().createAndPushWindow<LobbyWindow>();
+}
+
 void CMultiMode::hostTCP()
 {
 	auto savedScreenType = screenType;

+ 2 - 0
client/mainmenu/CMainMenu.h

@@ -86,12 +86,14 @@ public:
 	std::shared_ptr<CPicture> picture;
 	std::shared_ptr<CTextInput> playerName;
 	std::shared_ptr<CButton> buttonHotseat;
+	std::shared_ptr<CButton> buttonLobby;
 	std::shared_ptr<CButton> buttonHost;
 	std::shared_ptr<CButton> buttonJoin;
 	std::shared_ptr<CButton> buttonCancel;
 	std::shared_ptr<CGStatusBar> statusBar;
 
 	CMultiMode(ESelectionScreen ScreenType);
+	void openLobby();
 	void hostTCP();
 	void joinTCP();
 	std::string getPlayerName();

+ 40 - 0
client/serverLobby/LobbyWindow.cpp

@@ -0,0 +1,40 @@
+/*
+ * LobbyWindow.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 "LobbyWindow.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../gui/WindowHandler.h"
+
+void LobbyClient::onPacketReceived(const std::vector<uint8_t> & message)
+{
+
+}
+
+LobbyWidget::LobbyWidget()
+{
+	addCallback("closeWindow", [](int) { GH.windows().popWindows(1); });
+
+	const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json"));
+	build(config);
+}
+
+LobbyWindow::LobbyWindow():
+	CWindowObject(BORDERED)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	widget = std::make_shared<LobbyWidget>();
+	pos = widget->pos;
+	center();
+	connection = std::make_shared<LobbyClient>();
+
+	connection->start("127.0.0.1", 30303);
+}

+ 37 - 0
client/serverLobby/LobbyWindow.h

@@ -0,0 +1,37 @@
+/*
+ * LobbyWindow.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
+
+#include "../gui/InterfaceObjectConfigurable.h"
+#include "../windows/CWindowObject.h"
+
+#include "../../lib/network/NetworkClient.h"
+
+class LobbyWidget : public InterfaceObjectConfigurable
+{
+public:
+	LobbyWidget();
+};
+
+class LobbyClient : public NetworkClient
+{
+	void onPacketReceived(const std::vector<uint8_t> & message) override;
+public:
+	LobbyClient() = default;
+};
+
+class LobbyWindow : public CWindowObject
+{
+	std::shared_ptr<LobbyWidget> widget;
+	std::shared_ptr<LobbyClient> connection;
+
+public:
+	LobbyWindow();
+};

+ 9 - 0
cmake_modules/VCMI_lib.cmake

@@ -124,6 +124,10 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/modding/IdentifierStorage.cpp
 		${MAIN_LIB_DIR}/modding/ModUtility.cpp
 
+		${MAIN_LIB_DIR}/network/NetworkClient.cpp
+		${MAIN_LIB_DIR}/network/NetworkConnection.cpp
+		${MAIN_LIB_DIR}/network/NetworkServer.cpp
+
 		${MAIN_LIB_DIR}/networkPacks/NetPacksLib.cpp
 
 		${MAIN_LIB_DIR}/pathfinder/CGPathNode.cpp
@@ -471,6 +475,11 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/modding/ModUtility.h
 		${MAIN_LIB_DIR}/modding/ModVerificationInfo.h
 
+		${MAIN_LIB_DIR}/network/NetworkClient.h
+		${MAIN_LIB_DIR}/network/NetworkConnection.h
+		${MAIN_LIB_DIR}/network/NetworkDefines.h
+		${MAIN_LIB_DIR}/network/NetworkServer.h
+
 		${MAIN_LIB_DIR}/networkPacks/ArtifactLocation.h
 		${MAIN_LIB_DIR}/networkPacks/BattleChanges.h
 		${MAIN_LIB_DIR}/networkPacks/Component.h

+ 186 - 0
config/widgets/lobbyWindow.json

@@ -0,0 +1,186 @@
+{
+	"customTypes" : {
+		"labelTitleMain" : {
+			"type": "label",
+			"font": "big",
+			"alignment": "left",
+			"color": "yellow"
+		},
+		"labelTitle" : {
+			"type": "label",
+			"font": "small",
+			"alignment": "left",
+			"color": "yellow"
+		},
+		"backgroundTexture" : {
+			"type": "texture",
+			"font": "tiny",
+			"color" : "blue",
+			"image": "DIBOXBCK"
+		},
+		"areaFilled":{
+			"type": "transparentFilledRectangle",
+			"color": [0, 0, 0, 75],
+			"colorLine": [64, 80, 128, 255]
+		}
+	},
+	
+	
+	"width": 1024,
+	"height": 600,
+	
+	"items":
+	[
+		{
+			"type": "backgroundTexture",
+			"rect": {"w": 1024, "h": 600}
+		},
+		
+		{
+			"type": "areaFilled",
+			"rect": {"x": 5, "y": 5, "w": 250, "h": 40}
+		},
+		{
+			"type": "labelTitleMain",
+			"position": {"x": 15, "y": 10},
+			"text" : "Player Name"
+		},
+		
+		{
+			"type": "areaFilled",
+			"rect": {"x": 5, "y": 50, "w": 250, "h": 150}
+		},
+		{
+			"type": "labelTitle",
+			"position": {"x": 15, "y": 53},
+			"text" : "Match Filter"
+		},
+
+		{
+			"type": "areaFilled",
+			"rect": {"x": 5, "y": 210, "w": 250, "h": 310}
+		},
+		{
+			"type": "labelTitle",
+			"position": {"x": 15, "y": 213},
+			"text" : "Match List"
+		},
+		
+		{
+			"type": "areaFilled",
+			"rect": {"x": 270, "y": 50, "w": 150, "h": 540}
+		},
+		{
+			"type": "labelTitle",
+			"position": {"x": 280, "y": 53},
+			"text" : "Channel List"
+		},
+		
+		{
+			"type": "areaFilled",
+			"rect": {"x": 430, "y": 50, "w": 430, "h": 515}
+		},
+		{
+			"type": "labelTitle",
+			"position": {"x": 440, "y": 53},
+			"text" : "Game Chat"
+		},
+		
+		{
+			"type": "areaFilled",
+			"rect": {"x": 430, "y": 565, "w": 395, "h": 25}
+		},
+		{
+			"type": "labelTitle",
+			"position": {"x": 440, "y": 568},
+			"text" : "Enter Message"
+		},
+		
+		{
+			"type": "areaFilled",
+			"rect": {"x": 870, "y": 50, "w": 150, "h": 540}
+		},
+		{
+			"type": "labelTitle",
+			"position": {"x": 880, "y": 53},
+			"text" : "Player List"
+		},
+		
+		{
+			"type": "button",
+			"position": {"x": 940, "y": 10},
+			"image": "settingsWindow/button80",
+			"help": "core.help.288",
+			"callback": "closeWindow",
+			"hotkey": "globalReturn",
+			"items":
+			[
+				{
+					"type": "label",
+					"font": "medium",
+					"alignment": "center",
+					"color": "yellow",
+					"text": "Exit"
+				}
+			]
+		},
+		
+		{
+			"type": "button",
+			"position": {"x": 828, "y": 565},
+			"image": "settingsWindow/button32",
+			"help": "core.help.288",
+			"callback": "closeWindow",
+			"hotkey": "globalReturn",
+			"items":
+			[
+				{
+					"type": "label",
+					"font": "medium",
+					"alignment": "center",
+					"color": "yellow",
+					"text": ">"
+				}
+			]
+		},
+		
+		{
+			"type": "button",
+			"position": {"x": 10, "y": 520},
+			"image": "settingsWindow/button190",
+			"help": "core.help.288",
+			"callback": "closeWindow",
+			"hotkey": "globalReturn",
+			"items":
+			[
+				{
+					"type": "label",
+					"font": "medium",
+					"alignment": "center",
+					"color": "yellow",
+					"text": "Start Public Game"
+				}
+			]
+		},
+		
+		{
+			"type": "button",
+			"position": {"x": 10, "y": 555},
+			"image": "settingsWindow/button190",
+			"help": "core.help.288",
+			"callback": "closeWindow",
+			"hotkey": "globalReturn",
+			"items":
+			[
+				{
+					"type": "label",
+					"font": "medium",
+					"alignment": "center",
+					"color": "yellow",
+					"text": "Start Private Game"
+				}
+			]
+		},
+
+	]
+}

+ 47 - 0
lib/network/NetworkClient.cpp

@@ -0,0 +1,47 @@
+/*
+ * NetworkClient.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 "NetworkClient.h"
+#include "NetworkConnection.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+NetworkClient::NetworkClient()
+	: io(new NetworkService)
+	, socket(new NetworkSocket(*io))
+//	, timer(new NetworkTimer(*io))
+{
+}
+
+void NetworkClient::start(const std::string & host, uint16_t port)
+{
+	boost::asio::ip::tcp::resolver resolver(*io);
+	auto endpoints = resolver.resolve(host, std::to_string(port));
+
+	boost::asio::async_connect(*socket, endpoints, std::bind(&NetworkClient::onConnected, this, _1));
+}
+
+void NetworkClient::onConnected(const boost::system::error_code & ec)
+{
+	connection = std::make_shared<NetworkConnection>(socket);
+	connection->start();
+}
+
+void NetworkClient::run()
+{
+	io->run();
+}
+
+void NetworkClient::sendPacket(const std::vector<uint8_t> & message)
+{
+	connection->sendPacket(message);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 37 - 0
lib/network/NetworkClient.h

@@ -0,0 +1,37 @@
+/*
+ * NetworkClient.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
+
+#include "NetworkDefines.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class NetworkConnection;
+
+class DLL_LINKAGE NetworkClient : boost::noncopyable
+{
+	std::shared_ptr<NetworkService> io;
+	std::shared_ptr<NetworkSocket> socket;
+	std::shared_ptr<NetworkConnection> connection;
+	std::shared_ptr<NetworkTimer> timer;
+
+	void onConnected(const boost::system::error_code & ec);
+protected:
+	virtual void onPacketReceived(const std::vector<uint8_t> & message) = 0;
+public:
+	NetworkClient();
+	virtual ~NetworkClient() = default;
+
+	void start(const std::string & host, uint16_t port);
+	void sendPacket(const std::vector<uint8_t> & message);
+	void run();
+};
+
+VCMI_LIB_NAMESPACE_END

+ 97 - 0
lib/network/NetworkConnection.cpp

@@ -0,0 +1,97 @@
+/*
+ * NetworkConnection.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 "NetworkConnection.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+NetworkConnection::NetworkConnection(const std::shared_ptr<NetworkSocket> & socket)
+	: socket(socket)
+{
+
+}
+
+void NetworkConnection::start()
+{
+	boost::asio::async_read(*socket,
+							readBuffer,
+							boost::asio::transfer_exactly(messageHeaderSize),
+							std::bind(&NetworkConnection::onHeaderReceived,this, _1));
+}
+
+void NetworkConnection::onHeaderReceived(const boost::system::error_code & ec)
+{
+	uint32_t messageSize = readPacketSize(ec);
+
+	boost::asio::async_read(*socket,
+							readBuffer,
+							boost::asio::transfer_exactly(messageSize),
+							std::bind(&NetworkConnection::onPacketReceived,this, _1, messageSize));
+}
+
+uint32_t NetworkConnection::readPacketSize(const boost::system::error_code & ec)
+{
+	if (ec)
+	{
+		throw std::runtime_error("Connection aborted!");
+	}
+
+	if (readBuffer.size() < messageHeaderSize)
+	{
+		throw std::runtime_error("Failed to read header!");
+	}
+
+	std::istream istream(&readBuffer);
+
+	uint32_t messageSize;
+	istream.read(reinterpret_cast<char *>(&messageSize), messageHeaderSize);
+
+	if (messageSize > messageMaxSize)
+	{
+		throw std::runtime_error("Invalid packet size!");
+	}
+
+	return messageSize;
+}
+
+void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize)
+{
+	if (ec)
+	{
+		throw std::runtime_error("Connection aborted!");
+	}
+
+	if (readBuffer.size() < expectedPacketSize)
+	{
+		throw std::runtime_error("Failed to read header!");
+	}
+
+	std::vector<uint8_t> message;
+
+	message.resize(expectedPacketSize);
+	std::istream istream(&readBuffer);
+	istream.read(reinterpret_cast<char *>(message.data()), messageHeaderSize);
+
+	start();
+}
+
+void NetworkConnection::sendPacket(const std::vector<uint8_t> & message)
+{
+	NetworkBuffer writeBuffer;
+
+	std::ostream ostream(&writeBuffer);
+	uint32_t messageSize = message.size();
+	ostream.write(reinterpret_cast<const char *>(&messageSize), messageHeaderSize);
+	ostream.write(reinterpret_cast<const char *>(message.data()), message.size());
+
+	boost::asio::write(*socket, writeBuffer );
+}
+
+VCMI_LIB_NAMESPACE_END

+ 36 - 0
lib/network/NetworkConnection.h

@@ -0,0 +1,36 @@
+/*
+ * NetworkConnection.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
+
+#include "NetworkDefines.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE NetworkConnection : boost::noncopyable
+{
+	static const int messageHeaderSize = sizeof(uint32_t);
+	static const int messageMaxSize = 1024;
+
+	std::shared_ptr<NetworkSocket> socket;
+
+	NetworkBuffer readBuffer;
+
+	void onHeaderReceived(const boost::system::error_code & ec);
+	void onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize);
+	uint32_t readPacketSize(const boost::system::error_code & ec);
+
+public:
+	NetworkConnection(const std::shared_ptr<NetworkSocket> & socket);
+
+	void start();
+	void sendPacket(const std::vector<uint8_t> & message);
+};
+
+VCMI_LIB_NAMESPACE_END

+ 22 - 0
lib/network/NetworkDefines.h

@@ -0,0 +1,22 @@
+/*
+ * NetworkDefines.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
+
+#include <boost/asio.hpp>
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+using NetworkService = boost::asio::io_service;
+using NetworkSocket = boost::asio::basic_stream_socket<boost::asio::ip::tcp>;
+using NetworkAcceptor = boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>;
+using NetworkBuffer = boost::asio::streambuf;
+using NetworkTimer = boost::asio::steady_timer;
+
+VCMI_LIB_NAMESPACE_END

+ 63 - 0
lib/network/NetworkServer.cpp

@@ -0,0 +1,63 @@
+/*
+ * NetworkServer.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 "NetworkServer.h"
+#include "NetworkConnection.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void NetworkServer::start(uint16_t port)
+{
+	io = std::make_shared<boost::asio::io_service>();
+	acceptor = std::make_shared<NetworkAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
+
+	startAsyncAccept();
+}
+
+void NetworkServer::startAsyncAccept()
+{
+	std::shared_ptr<NetworkSocket> upcomingConnection = std::make_shared<NetworkSocket>(*io);
+	acceptor->async_accept(*upcomingConnection, std::bind(&NetworkServer::connectionAccepted, this, upcomingConnection, _1));
+}
+
+void NetworkServer::run()
+{
+	io->run();
+}
+
+void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingConnection, const boost::system::error_code & ec)
+{
+	if(ec)
+	{
+		logNetwork->info("Something wrong during accepting: %s", ec.message());
+		return;
+	}
+
+	try
+	{
+		logNetwork->info("We got a new connection! :)");
+		auto connection = std::make_shared<NetworkConnection>(upcomingConnection);
+		connections.insert(connection);
+		connection->start();
+	}
+	catch(std::exception & e)
+	{
+		logNetwork->error("Failure processing new connection! %s", e.what());
+	}
+
+	startAsyncAccept();
+}
+
+void NetworkServer::sendPacket(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
+{
+	connection->sendPacket(message);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 39 - 0
lib/network/NetworkServer.h

@@ -0,0 +1,39 @@
+/*
+ * NetworkServer.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
+
+#include "NetworkDefines.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class NetworkConnection;
+
+class DLL_LINKAGE NetworkServer : boost::noncopyable
+{
+	std::shared_ptr<NetworkService> io;
+	std::shared_ptr<NetworkAcceptor> acceptor;
+	std::set<std::shared_ptr<NetworkConnection>> connections;
+
+	void connectionAccepted(std::shared_ptr<NetworkSocket>, const boost::system::error_code & ec);
+	void startAsyncAccept();
+protected:
+	virtual void onNewConnection(std::shared_ptr<NetworkConnection>) = 0;
+	virtual void onPacketReceived(std::shared_ptr<NetworkConnection>, const std::vector<uint8_t> & message) = 0;
+
+	void sendPacket(const std::shared_ptr<NetworkConnection> &, const std::vector<uint8_t> & message);
+
+public:
+	virtual ~NetworkServer() = default;
+
+	void start(uint16_t port);
+	void run();
+};
+
+VCMI_LIB_NAMESPACE_END

+ 39 - 0
lobby/CMakeLists.txt

@@ -0,0 +1,39 @@
+set(lobby_SRCS
+		StdInc.cpp
+		LobbyServer.cpp
+		SQLiteConnection.cpp
+)
+
+set(lobby_HEADERS
+		StdInc.h
+		LobbyServer.h
+		SQLiteConnection.h
+)
+
+assign_source_group(${lobby_SRCS} ${lobby_HEADERS})
+
+add_executable(vcmilobby ${lobby_SRCS} ${lobby_HEADERS})
+set(lobby_LIBS vcmi)
+
+if(CMAKE_SYSTEM_NAME MATCHES FreeBSD OR HAIKU)
+	set(lobby_LIBS execinfo ${lobby_LIBS})
+endif()
+
+target_link_libraries(vcmilobby PRIVATE ${lobby_LIBS} ${SQLite3_LIBRARIES})
+
+target_include_directories(vcmilobby PRIVATE ${SQLite3_INCLUDE_DIRS})
+target_include_directories(vcmilobby PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+
+if(WIN32)
+	set_target_properties(vcmilobby
+		PROPERTIES
+			OUTPUT_NAME "VCMI_lobby"
+			PROJECT_LABEL "VCMI_lobby"
+	)
+endif()
+
+vcmi_set_output_dir(vcmilobby "")
+enable_pch(vcmilobby)
+
+install(TARGETS vcmilobby DESTINATION ${BIN_DIR})
+

+ 45 - 0
lobby/LobbyServer.cpp

@@ -0,0 +1,45 @@
+/*
+ * 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"
+
+#include <boost/uuid/uuid_generators.hpp>
+#include <boost/uuid/uuid_io.hpp>
+
+static const std::string DATABASE_PATH = "~/vcmi.db";
+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()());
+
+void LobbyServer::onNewConnection(std::shared_ptr<NetworkConnection>)
+{
+
+}
+
+void LobbyServer::onPacketReceived(std::shared_ptr<NetworkConnection>, const std::vector<uint8_t> & message)
+{
+
+}
+
+LobbyServer::LobbyServer()
+{
+	database = SQLiteInstance::open(DATABASE_PATH, true);
+
+}
+
+int main(int argc, const char * argv[])
+{
+	LobbyServer server;
+
+	server.start(LISTENING_PORT);
+	server.run();
+}

+ 24 - 0
lobby/LobbyServer.h

@@ -0,0 +1,24 @@
+/*
+ * LobbyServer.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
+
+#include "../lib/network/NetworkServer.h"
+
+class SQLiteInstance;
+
+class LobbyServer : public NetworkServer
+{
+	std::unique_ptr<SQLiteInstance> database;
+
+	void onNewConnection(std::shared_ptr<NetworkConnection>) override;
+	void onPacketReceived(std::shared_ptr<NetworkConnection>, const std::vector<uint8_t> & message) override;
+public:
+	LobbyServer();
+};

+ 197 - 0
lobby/SQLiteConnection.cpp

@@ -0,0 +1,197 @@
+/*
+ * SQLiteConnection.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 "SQLiteConnection.h"
+
+#include <sqlite3.h>
+
+static void on_sqlite_error( sqlite3 * connection, [[maybe_unused]] int result )
+{
+	if ( result != SQLITE_OK )
+	{
+		printf( "sqlite error: %s\n", sqlite3_errmsg( connection ) );
+	}
+
+	assert( result == SQLITE_OK );
+}
+
+SQLiteStatement::SQLiteStatement( SQLiteInstance & instance, sqlite3_stmt * statement ):
+	m_instance( instance ),
+	m_statement( statement )
+{ }
+
+SQLiteStatement::~SQLiteStatement( )
+{ 
+	int result = sqlite3_finalize( m_statement );
+	on_sqlite_error( m_instance.m_connection, result );
+}
+
+bool SQLiteStatement::execute( )
+{ 
+	int result = sqlite3_step( m_statement );
+
+	switch ( result )
+	{
+		case SQLITE_DONE:
+			return false;
+		case SQLITE_ROW:
+			return true;
+		default:
+			on_sqlite_error( m_instance.m_connection, result );
+			return false;
+	}
+}
+
+void SQLiteStatement::reset( )
+{
+	int result = sqlite3_reset( m_statement );
+	on_sqlite_error( m_instance.m_connection, result );
+}
+
+void SQLiteStatement::clear()
+{
+	int result = sqlite3_clear_bindings(m_statement);
+	on_sqlite_error(m_instance.m_connection, result);
+}
+
+void SQLiteStatement::setBindSingle( size_t index, double const & value )
+{
+	int result = sqlite3_bind_double( m_statement, static_cast<int>(index), value );
+	on_sqlite_error( m_instance.m_connection, result );
+}
+
+void SQLiteStatement::setBindSingle(size_t index, uint8_t const & value)
+{
+	int result = sqlite3_bind_int(m_statement, static_cast<int>(index), value);
+	on_sqlite_error(m_instance.m_connection, result);
+}
+
+void SQLiteStatement::setBindSingle(size_t index, uint16_t const & value)
+{
+	int result = sqlite3_bind_int(m_statement, static_cast<int>(index), value);
+	on_sqlite_error(m_instance.m_connection, result);
+}
+void SQLiteStatement::setBindSingle(size_t index, uint32_t const & value)
+{
+	int result = sqlite3_bind_int(m_statement, static_cast<int>(index), value);
+	on_sqlite_error(m_instance.m_connection, result);
+}
+
+void SQLiteStatement::setBindSingle( size_t index, int32_t const & value )
+{
+	int result = sqlite3_bind_int( m_statement, static_cast<int>( index ), value );
+	on_sqlite_error( m_instance.m_connection, result );
+}
+
+void SQLiteStatement::setBindSingle( size_t index, int64_t const & value )
+{
+	int result = sqlite3_bind_int64( m_statement, static_cast<int>( index ), value );
+	on_sqlite_error( m_instance.m_connection, result );
+}
+
+void SQLiteStatement::setBindSingle( size_t index, std::string const & value )
+{
+	int result = sqlite3_bind_text( m_statement, static_cast<int>( index ), value.data(), static_cast<int>( value.size() ), SQLITE_STATIC );
+	on_sqlite_error( m_instance.m_connection, result );
+}
+
+void SQLiteStatement::setBindSingle( size_t index, char const * value )
+{
+	int result = sqlite3_bind_text( m_statement, static_cast<int>( index ), value, -1, SQLITE_STATIC );
+	on_sqlite_error( m_instance.m_connection, result );
+}
+
+void SQLiteStatement::getColumnSingle( size_t index, double & value )
+{
+	value = sqlite3_column_double( m_statement, static_cast<int>( index ) );
+}
+
+void SQLiteStatement::getColumnSingle( size_t index, uint8_t & value )
+{
+	value = static_cast<uint8_t>(sqlite3_column_int( m_statement, static_cast<int>( index ) ));
+}
+
+void SQLiteStatement::getColumnSingle(size_t index, uint16_t & value)
+{
+	value = static_cast<uint16_t>(sqlite3_column_int(m_statement, static_cast<int>(index)));
+}
+
+void SQLiteStatement::getColumnSingle( size_t index, int32_t & value )
+{
+	value = sqlite3_column_int( m_statement, static_cast<int>( index ) );
+}
+
+void SQLiteStatement::getColumnSingle(size_t index, uint32_t & value)
+{
+	value = sqlite3_column_int(m_statement, static_cast<int>(index));
+}
+
+void SQLiteStatement::getColumnSingle( size_t index, int64_t & value )
+{
+	value = sqlite3_column_int64( m_statement, static_cast<int>( index ) );
+}
+
+void SQLiteStatement::getColumnSingle( size_t index, std::string & value )
+{
+	auto value_raw = sqlite3_column_text(m_statement, static_cast<int>(index));
+	value = reinterpret_cast<char const*>( value_raw );
+}
+
+void SQLiteStatement::getColumnBlob(size_t index, std::byte * value, [[maybe_unused]] size_t capacity)
+{
+	auto * blob_data = sqlite3_column_blob(m_statement, static_cast<int>(index));
+	size_t blob_size = sqlite3_column_bytes(m_statement, static_cast<int>(index));
+
+	assert(blob_size < capacity);
+
+	std::copy_n(static_cast<std::byte const*>(blob_data), blob_size, value);
+	value[blob_size] = std::byte(0);
+}
+
+SQLiteInstancePtr SQLiteInstance::open( std::string const & db_path, bool allow_write)
+{
+	int flags = allow_write ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) : SQLITE_OPEN_READONLY;
+
+	sqlite3 * connection;
+	int result = sqlite3_open_v2( db_path.c_str( ), &connection, flags, nullptr );
+
+	on_sqlite_error( connection, result );
+
+	assert(result == SQLITE_OK);
+
+	if ( result == SQLITE_OK )
+		return SQLiteInstancePtr( new SQLiteInstance( connection ) );
+
+	sqlite3_close( connection );
+	return SQLiteInstancePtr( );
+}
+
+SQLiteInstance::SQLiteInstance( sqlite3 * connection ):
+	m_connection( connection )
+{ }
+
+SQLiteInstance::~SQLiteInstance( )
+{
+	int result = sqlite3_close( m_connection );
+	on_sqlite_error( m_connection, result );
+}
+
+SQLiteStatementPtr SQLiteInstance::prepare( std::string const & sql_text )
+{
+	sqlite3_stmt * statement;
+	int result = sqlite3_prepare_v2( m_connection, sql_text.data(), static_cast<int>( sql_text.size()), &statement, nullptr );
+	
+	on_sqlite_error( m_connection, result );
+
+	if ( result == SQLITE_OK )
+		return SQLiteStatementPtr( new SQLiteStatement( *this, statement ) );
+	sqlite3_finalize( statement );
+	return SQLiteStatementPtr( );
+}

+ 99 - 0
lobby/SQLiteConnection.h

@@ -0,0 +1,99 @@
+/*
+ * SQLiteConnection.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
+
+typedef struct sqlite3 sqlite3;
+typedef struct sqlite3_stmt sqlite3_stmt;
+
+class SQLiteInstance;
+class SQLiteStatement;
+
+using SQLiteInstancePtr = std::unique_ptr< SQLiteInstance >;
+using SQLiteStatementPtr = std::unique_ptr< SQLiteStatement >;
+
+class SQLiteStatement : boost::noncopyable
+{
+public:
+	friend class SQLiteInstance;
+
+	bool execute( );
+	void reset( );
+	void clear( );
+
+	~SQLiteStatement();
+
+	template<typename ... Args >
+	void setBinds( Args const & ... args )
+	{
+		setBindSingle( 1, args... ); // The leftmost SQL parameter has an index of 1
+	}
+
+	template<typename ... Args >
+	void getColumns( Args & ... args )
+	{
+		getColumnSingle( 0, args... ); // The leftmost column of the result set has the index 0
+	}
+
+private:
+	void setBindSingle( size_t index, double const & value );
+	void setBindSingle( size_t index, uint8_t const & value );
+	void setBindSingle( size_t index, uint16_t const & value );
+	void setBindSingle( size_t index, uint32_t const & value );
+	void setBindSingle( size_t index, int32_t const & value );
+	void setBindSingle( size_t index, int64_t const & value );
+	void setBindSingle( size_t index, std::string const & value );
+	void setBindSingle( size_t index, char const * value );
+
+	void getColumnSingle( size_t index, double & 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 );
+	void getColumnSingle( size_t index, int32_t & value );
+	void getColumnSingle( size_t index, int64_t & value );
+	void getColumnSingle( size_t index, std::string & value );
+
+	SQLiteStatement( SQLiteInstance & instance, sqlite3_stmt * statement );
+
+	template<typename T, typename ... Args >
+	void setBindSingle( size_t index, T const & arg, Args const & ... args )
+	{
+		setBindSingle( index, arg );
+		setBindSingle( index + 1, args... );
+	}
+
+	template<typename T, typename ... Args >
+	void getColumnSingle( size_t index, T & arg, Args & ... args )
+	{
+		getColumnSingle( index, arg );
+		getColumnSingle( index + 1, args... );
+	}
+
+	void getColumnBlob(size_t index, std::byte * value, size_t capacity);
+
+	SQLiteInstance & m_instance;
+	sqlite3_stmt * m_statement;
+};
+
+class SQLiteInstance : boost::noncopyable
+{
+public:
+	friend class SQLiteStatement;
+
+	static SQLiteInstancePtr open(std::string const & db_path, bool allow_write );
+
+	~SQLiteInstance( );
+
+	SQLiteStatementPtr prepare( std::string const & statement );
+
+private:
+	SQLiteInstance( sqlite3 * connection );
+
+	sqlite3 * m_connection;
+};

+ 2 - 0
lobby/StdInc.cpp

@@ -0,0 +1,2 @@
+// Creates the precompiled header
+#include "StdInc.h"

+ 14 - 0
lobby/StdInc.h

@@ -0,0 +1,14 @@
+/*
+ * StdInc.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
+
+#include "../Global.h"
+
+VCMI_LIB_USING_NAMESPACE