Sfoglia il codice sorgente

Added list of active accounts and rooms to UI. Added room creation logic

Ivan Savenko 1 anno fa
parent
commit
388ca6e776
37 ha cambiato i file con 698 aggiunte e 304 eliminazioni
  1. 1 0
      client/CMakeLists.txt
  2. 13 16
      client/CServerHandler.cpp
  3. 7 4
      client/CServerHandler.h
  4. 15 1
      client/NetPacksLobbyClient.cpp
  5. 9 1
      client/eventsSDL/InputSourceKeyboard.cpp
  6. 93 5
      client/globalLobby/GlobalLobbyClient.cpp
  7. 18 3
      client/globalLobby/GlobalLobbyClient.h
  8. 28 0
      client/globalLobby/GlobalLobbyDefines.h
  9. 1 1
      client/globalLobby/GlobalLobbyLoginWindow.cpp
  10. 6 8
      client/globalLobby/GlobalLobbyServerSetup.cpp
  11. 107 0
      client/globalLobby/GlobalLobbyWidget.cpp
  12. 35 0
      client/globalLobby/GlobalLobbyWidget.h
  13. 21 0
      client/globalLobby/GlobalLobbyWindow.cpp
  14. 7 0
      client/globalLobby/GlobalLobbyWindow.h
  15. 1 1
      client/lobby/SelectionTab.cpp
  16. 12 16
      client/mainmenu/CMainMenu.cpp
  17. 3 3
      client/mainmenu/CMainMenu.h
  18. 1 1
      client/windows/CMapOverview.h
  19. 19 13
      config/widgets/lobbyWindow.json
  20. 1 1
      lib/StartInfo.cpp
  21. 10 4
      lib/StartInfo.h
  22. 3 3
      lib/gameState/CGameState.cpp
  23. 1 1
      lib/gameState/CGameStateCampaign.cpp
  24. 4 1
      lib/network/NetworkConnection.cpp
  25. 1 1
      lib/networkPacks/PacksForLobby.h
  26. 5 0
      lib/serializer/Connection.cpp
  27. 1 0
      lib/serializer/Connection.h
  28. 35 4
      lobby/LobbyDatabase.cpp
  29. 4 1
      lobby/LobbyDatabase.h
  30. 9 7
      lobby/LobbyDefines.h
  31. 18 6
      lobby/LobbyServer.cpp
  32. 1 0
      server/CMakeLists.txt
  33. 32 196
      server/CVCMIServer.cpp
  34. 2 4
      server/CVCMIServer.h
  35. 170 0
      server/EntryPoint.cpp
  36. 3 1
      server/GlobalLobbyProcessor.cpp
  37. 1 1
      server/NetPacksLobbyServer.cpp

+ 1 - 0
client/CMakeLists.txt

@@ -277,6 +277,7 @@ set(client_HEADERS
 	renderSDL/SDL_PixelAccess.h
 
 	globalLobby/GlobalLobbyClient.h
+	globalLobby/GlobalLobbyDefines.h
 	globalLobby/GlobalLobbyLoginWindow.h
 	globalLobby/GlobalLobbyServerSetup.h
 	globalLobby/GlobalLobbyWidget.h

+ 13 - 16
client/CServerHandler.cpp

@@ -126,9 +126,6 @@ public:
 	}
 };
 
-static const std::string NAME_AFFIX = "client";
-static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
-
 CServerHandler::~CServerHandler()
 {
 	networkHandler->stop();
@@ -141,7 +138,8 @@ CServerHandler::CServerHandler()
 	, applier(std::make_unique<CApplier<CBaseForLobbyApply>>())
 	, lobbyClient(std::make_unique<GlobalLobbyClient>())
 	, client(nullptr)
-	, loadMode(0)
+	, loadMode(ELoadMode::NONE)
+	, screenType(ESelectionScreen::unknown)
 	, campaignStateToSend(nullptr)
 	, campaignServerRestartLock(false)
 {
@@ -158,7 +156,7 @@ void CServerHandler::threadRunNetwork()
 	logGlobal->info("Ending network thread");
 }
 
-void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names)
+void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, const std::vector<std::string> & names)
 {
 	hostClientId = -1;
 	state = EClientState::NONE;
@@ -169,9 +167,10 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::
 	playerNames.clear();
 	si->difficulty = 1;
 	si->mode = mode;
+	screenType = screen;
 	myNames.clear();
-	if(names && !names->empty()) //if have custom set of player names - use it
-		myNames = *names;
+	if(!names.empty()) //if have custom set of player names - use it
+		myNames = names;
 	else
 		myNames.push_back(settings["general"]["playerName"].String());
 }
@@ -617,7 +616,7 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
 	if(client)
 	{
 		lsg.initializedStartInfo = std::make_shared<StartInfo>(* const_cast<StartInfo *>(client->getStartInfo(true)));
-		lsg.initializedStartInfo->mode = StartInfo::NEW_GAME;
+		lsg.initializedStartInfo->mode = EStartMode::NEW_GAME;
 		lsg.initializedStartInfo->seedToBeUsed = lsg.initializedStartInfo->seedPostInit = 0;
 		* si = * lsg.initializedStartInfo;
 	}
@@ -641,13 +640,13 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
 
 	switch(si->mode)
 	{
-	case StartInfo::NEW_GAME:
+	case EStartMode::NEW_GAME:
 		client->newGame(gameState);
 		break;
-	case StartInfo::CAMPAIGN:
+	case EStartMode::CAMPAIGN:
 		client->newGame(gameState);
 		break;
-	case StartInfo::LOAD_GAME:
+	case EStartMode::LOAD_GAME:
 		client->loadGame(gameState);
 		break;
 	default:
@@ -765,7 +764,7 @@ int CServerHandler::howManyPlayerInterfaces()
 	return playerInts;
 }
 
-ui8 CServerHandler::getLoadMode()
+ELoadMode CServerHandler::getLoadMode()
 {
 	if(loadMode != ELoadMode::TUTORIAL && state == EClientState::GAMEPLAY)
 	{
@@ -790,15 +789,13 @@ void CServerHandler::debugStartTest(std::string filename, bool save)
 	auto mapInfo = std::make_shared<CMapInfo>();
 	if(save)
 	{
-		resetStateForLobby(StartInfo::LOAD_GAME);
+		resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, {});
 		mapInfo->saveInit(ResourcePath(filename, EResType::SAVEGAME));
-		screenType = ESelectionScreen::loadGame;
 	}
 	else
 	{
-		resetStateForLobby(StartInfo::NEW_GAME);
+		resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, {});
 		mapInfo->mapInit(filename);
-		screenType = ESelectionScreen::newGame;
 	}
 	if(settings["session"]["donotstartserver"].Bool())
 		connectToServer(getLocalHostname(), getLocalPort());

+ 7 - 4
client/CServerHandler.h

@@ -40,6 +40,9 @@ class GlobalLobbyClient;
 class HighScoreCalculation;
 class HighScoreParameter;
 
+enum class ESelectionScreen : ui8;
+enum class ELoadMode : ui8;
+
 // TODO: Add mutex so we can't set CONNECTION_CANCELLED if client already connected, but thread not setup yet
 enum class EClientState : ui8
 {
@@ -128,8 +131,8 @@ public:
 	// For starting non-custom campaign and continue to next mission
 	std::shared_ptr<CampaignState> campaignStateToSend;
 
-	ui8 screenType; // To create lobby UI only after server is setup
-	ui8 loadMode; // For saves filtering in SelectionTab
+	ESelectionScreen screenType; // To create lobby UI only after server is setup
+	ELoadMode loadMode; // For saves filtering in SelectionTab
 	////////////////////
 
 	std::unique_ptr<CStopWatch> th;
@@ -143,7 +146,7 @@ public:
 	CServerHandler();
 	~CServerHandler();
 	
-	void resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names = nullptr);
+	void resetStateForLobby(EStartMode mode, ESelectionScreen screen, const std::vector<std::string> & names);
 	void startLocalServerAndConnect(bool connectToLobby);
 	void connectToServer(const std::string & addr, const ui16 port);
 
@@ -196,7 +199,7 @@ public:
 
 	// TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle
 	int howManyPlayerInterfaces();
-	ui8 getLoadMode();
+	ELoadMode getLoadMode();
 
 	void visitForLobby(CPackForLobby & lobbyPack);
 	void visitForClient(CPackForClient & clientPack);

+ 15 - 1
client/NetPacksLobbyClient.cpp

@@ -20,6 +20,8 @@
 #include "lobby/SelectionTab.h"
 #include "lobby/CBonusSelection.h"
 #include "globalLobby/GlobalLobbyWindow.h"
+#include "globalLobby/GlobalLobbyServerSetup.h"
+#include "globalLobby/GlobalLobbyClient.h"
 
 #include "CServerHandler.h"
 #include "CGameInfo.h"
@@ -49,6 +51,18 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon
 			if (GH.windows().topWindow<CSimpleJoinScreen>())
 				GH.windows().popWindows(1);
 
+			if (!GH.windows().findWindows<GlobalLobbyServerSetup>().empty())
+			{
+				// announce opened game room
+				// TODO: find better approach?
+				int roomType = settings["lobby"]["roomType"].Integer();
+
+				if (roomType != 0)
+					handler.getGlobalLobby().sendOpenPrivateRoom();
+				else
+					handler.getGlobalLobby().sendOpenPublicRoom();
+			}
+
 			while (!GH.windows().findWindows<GlobalLobbyWindow>().empty())
 			{
 				// if global lobby is open, pop all dialogs on top of it as well as lobby itself
@@ -141,7 +155,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pac
 	}
 	
 	handler.state = EClientState::STARTING;
-	if(handler.si->mode != StartInfo::LOAD_GAME || pack.clientId == handler.c->connectionID)
+	if(handler.si->mode != EStartMode::LOAD_GAME || pack.clientId == handler.c->connectionID)
 	{
 		auto modeBackup = handler.si->mode;
 		handler.si = pack.initializedStartInfo;

+ 9 - 1
client/eventsSDL/InputSourceKeyboard.cpp

@@ -16,6 +16,8 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/EventDispatcher.h"
 #include "../gui/ShortcutHandler.h"
+#include "../CServerHandler.h"
+#include "../globalLobby/GlobalLobbyClient.h"
 
 #include <SDL_clipboard.h>
 #include <SDL_events.h>
@@ -31,6 +33,8 @@ InputSourceKeyboard::InputSourceKeyboard()
 
 void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
 {
+	assert(key.state == SDL_PRESSED);
+
 	if (SDL_IsTextInputActive() == SDL_TRUE)
 	{
 		if(key.keysym.sym == SDLK_v && isKeyboardCtrlDown()) 
@@ -51,7 +55,11 @@ void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
 			return; // ignore periodic event resends
 	}
 
-	assert(key.state == SDL_PRESSED);
+
+	if(key.keysym.sym == SDLK_TAB && isKeyboardCtrlDown())
+	{
+		CSH->getGlobalLobby().activateInterface();
+	}
 
 	if(key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
 	{

+ 93 - 5
client/globalLobby/GlobalLobbyClient.cpp

@@ -59,6 +59,12 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptr<INetworkConnectio
 	if(json["type"].String() == "activeAccounts")
 		return receiveActiveAccounts(json);
 
+	if(json["type"].String() == "activeGameRooms")
+		return receiveActiveGameRooms(json);
+
+	if(json["type"].String() == "joinRoomSuccess")
+		return receiveJoinRoomSuccess(json);
+
 	throw std::runtime_error("Received unexpected message from lobby server: " + json["type"].String());
 }
 
@@ -140,11 +146,51 @@ void GlobalLobbyClient::receiveChatMessage(const JsonNode & json)
 
 void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json)
 {
-	//for (auto const & jsonEntry : json["messages"].Vector())
-	//{
-	//	std::string accountID = jsonEntry["accountID"].String();
-	//	std::string displayName = jsonEntry["displayName"].String();
-	//}
+	activeAccounts.clear();
+
+	for (auto const & jsonEntry : json["accounts"].Vector())
+	{
+		GlobalLobbyAccount account;
+
+		account.accountID = jsonEntry["accountID"].String();
+		account.displayName = jsonEntry["displayName"].String();
+		account.status = jsonEntry["status"].String();
+
+		activeAccounts.push_back(account);
+	}
+
+	auto lobbyWindowPtr = lobbyWindow.lock();
+	if(lobbyWindowPtr)
+		lobbyWindowPtr->onActiveAccounts(activeAccounts);
+}
+
+void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json)
+{
+	activeRooms.clear();
+
+	for (auto const & jsonEntry : json["gameRooms"].Vector())
+	{
+		GlobalLobbyRoom room;
+
+		room.gameRoomID = jsonEntry["gameRoomID"].String();
+		room.hostAccountID = jsonEntry["hostAccountID"].String();
+		room.hostAccountDisplayName = jsonEntry["hostAccountDisplayName"].String();
+		room.description = jsonEntry["description"].String();
+//		room.status = jsonEntry["status"].String();
+		room.playersCount = jsonEntry["playersCount"].Integer();
+		room.playersLimit = jsonEntry["playersLimit"].Integer();
+
+		activeRooms.push_back(room);
+	}
+
+	auto lobbyWindowPtr = lobbyWindow.lock();
+	if(lobbyWindowPtr)
+		lobbyWindowPtr->onActiveRooms(activeRooms);
+}
+
+void GlobalLobbyClient::receiveJoinRoomSuccess(const JsonNode & json)
+{
+	// TODO: store "gameRoomID" field and use it for future queries
 }
 
 void GlobalLobbyClient::onConnectionEstablished(const std::shared_ptr<INetworkConnection> & connection)
@@ -211,6 +257,24 @@ void GlobalLobbyClient::sendMessage(const JsonNode & data)
 	networkConnection->sendPacket(payloadBuffer);
 }
 
+void GlobalLobbyClient::sendOpenPublicRoom()
+{
+	JsonNode toSend;
+	toSend["type"].String() = "openGameRoom";
+	toSend["hostAccountID"] = settings["lobby"]["accountID"];
+	toSend["roomType"].String() = "public";
+	sendMessage(toSend);
+}
+
+void GlobalLobbyClient::sendOpenPrivateRoom()
+{
+	JsonNode toSend;
+	toSend["type"].String() = "openGameRoom";
+	toSend["hostAccountID"] = settings["lobby"]["accountID"];
+	toSend["roomType"].String() = "private";
+	sendMessage(toSend);
+}
+
 void GlobalLobbyClient::connect()
 {
 	std::string hostname = settings["lobby"]["hostname"].String();
@@ -246,3 +310,27 @@ std::shared_ptr<GlobalLobbyWindow> GlobalLobbyClient::createLobbyWindow()
 	lobbyWindowLock = lobbyWindowPtr;
 	return lobbyWindowPtr;
 }
+
+const std::vector<GlobalLobbyAccount> & GlobalLobbyClient::getActiveAccounts() const
+{
+	return activeAccounts;
+}
+
+const std::vector<GlobalLobbyRoom> & GlobalLobbyClient::getActiveRooms() const
+{
+	return activeRooms;
+}
+
+void GlobalLobbyClient::activateInterface()
+{
+	if (!GH.windows().findWindows<GlobalLobbyWindow>().empty())
+		return;
+
+	if (!GH.windows().findWindows<GlobalLobbyLoginWindow>().empty())
+		return;
+
+	if (isConnected())
+		GH.windows().pushWindow(createLobbyWindow());
+	else
+		GH.windows().pushWindow(createLoginWindow());
+}

+ 18 - 3
client/globalLobby/GlobalLobbyClient.h

@@ -9,6 +9,7 @@
  */
 #pragma once
 
+#include "GlobalLobbyDefines.h"
 #include "../../lib/network/NetworkInterface.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -18,8 +19,11 @@ VCMI_LIB_NAMESPACE_END
 class GlobalLobbyLoginWindow;
 class GlobalLobbyWindow;
 
-class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable
+class GlobalLobbyClient final : public INetworkClientListener, boost::noncopyable
 {
+	std::vector<GlobalLobbyAccount> activeAccounts;
+	std::vector<GlobalLobbyRoom> activeRooms;
+
 	std::shared_ptr<INetworkConnection> networkConnection;
 
 	std::weak_ptr<GlobalLobbyLoginWindow> loginWindow;
@@ -40,14 +44,25 @@ class GlobalLobbyClient : public INetworkClientListener, boost::noncopyable
 	void receiveChatHistory(const JsonNode & json);
 	void receiveChatMessage(const JsonNode & json);
 	void receiveActiveAccounts(const JsonNode & json);
+	void receiveActiveGameRooms(const JsonNode & json);
+	void receiveJoinRoomSuccess(const JsonNode & json);
+
+	std::shared_ptr<GlobalLobbyLoginWindow> createLoginWindow();
+	std::shared_ptr<GlobalLobbyWindow> createLobbyWindow();
 
 public:
 	explicit GlobalLobbyClient();
 	~GlobalLobbyClient();
 
+	const std::vector<GlobalLobbyAccount> & getActiveAccounts() const;
+	const std::vector<GlobalLobbyRoom> & getActiveRooms() const;
+
+	/// Activate interface and pushes lobby UI as top window
+	void activateInterface();
 	void sendMessage(const JsonNode & data);
+	void sendOpenPublicRoom();
+	void sendOpenPrivateRoom();
+
 	void connect();
 	bool isConnected();
-	std::shared_ptr<GlobalLobbyLoginWindow> createLoginWindow();
-	std::shared_ptr<GlobalLobbyWindow> createLobbyWindow();
 };

+ 28 - 0
client/globalLobby/GlobalLobbyDefines.h

@@ -0,0 +1,28 @@
+/*
+ * GlobalLobbyClient.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 GlobalLobbyAccount
+{
+	std::string accountID;
+	std::string displayName;
+	std::string status;
+};
+
+struct GlobalLobbyRoom
+{
+	std::string gameRoomID;
+	std::string hostAccountID;
+	std::string hostAccountDisplayName;
+	std::string description;
+//	std::string status;
+	int playersCount;
+	int playersLimit;
+};

+ 1 - 1
client/globalLobby/GlobalLobbyLoginWindow.cpp

@@ -74,7 +74,7 @@ void GlobalLobbyLoginWindow::onLogin()
 void GlobalLobbyLoginWindow::onConnectionSuccess()
 {
 	close();
-	GH.windows().pushWindow(CSH->getGlobalLobby().createLobbyWindow());
+	CSH->getGlobalLobby().activateInterface();
 }
 
 void GlobalLobbyLoginWindow::onConnectionFailed(const std::string & reason)

+ 6 - 8
client/globalLobby/GlobalLobbyServerSetup.cpp

@@ -125,17 +125,15 @@ void GlobalLobbyServerSetup::onGameModeChanged(int value)
 void GlobalLobbyServerSetup::onCreate()
 {
 	if(toggleGameMode->getSelected() == 0)
-	{
-		CSH->resetStateForLobby(StartInfo::NEW_GAME, nullptr);
-		CSH->screenType = ESelectionScreen::newGame;
-	}
+		CSH->resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, {});
 	else
-	{
-		CSH->resetStateForLobby(StartInfo::LOAD_GAME, nullptr);
-		CSH->screenType = ESelectionScreen::loadGame;
-	}
+		CSH->resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, {});
+
 	CSH->loadMode = ELoadMode::MULTI;
 	CSH->startLocalServerAndConnect(true);
+
+	buttonCreate->block(true);
+	buttonClose->block(true);
 }
 
 void GlobalLobbyServerSetup::onClose()

+ 107 - 0
client/globalLobby/GlobalLobbyWidget.cpp

@@ -10,12 +10,19 @@
 
 #include "StdInc.h"
 #include "GlobalLobbyWidget.h"
+
+#include "GlobalLobbyClient.h"
 #include "GlobalLobbyWindow.h"
 
+#include "../CServerHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/WindowHandler.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
 #include "../widgets/TextControls.h"
 
+#include "../../lib/MetaString.h"
 GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window)
 	: window(window)
 {
@@ -23,10 +30,59 @@ GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window)
 	addCallback("sendMessage", [this](int) { this->window->doSendChatMessage(); });
 	addCallback("createGameRoom", [this](int) { this->window->doCreateGameRoom(); });
 
+	REGISTER_BUILDER("accountList", &GlobalLobbyWidget::buildAccountList);
+	REGISTER_BUILDER("roomList", &GlobalLobbyWidget::buildRoomList);
+
 	const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json"));
 	build(config);
 }
 
+std::shared_ptr<CIntObject> GlobalLobbyWidget::buildAccountList(const JsonNode & config) const
+{
+	const auto & createCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
+	{
+		const auto & accounts = CSH->getGlobalLobby().getActiveAccounts();
+
+		if(index < accounts.size())
+			return std::make_shared<GlobalLobbyAccountCard>(this->window, accounts[index]);
+		return std::make_shared<CIntObject>();
+	};
+
+	auto position = readPosition(config["position"]);
+	auto itemOffset = readPosition(config["itemOffset"]);
+	auto sliderPosition = readPosition(config["sliderPosition"]);
+	auto sliderSize = readPosition(config["sliderSize"]);
+	size_t visibleSize = 4; // FIXME: how many items can fit into UI?
+	size_t totalSize = 4; //FIXME: how many items are there in total
+	int sliderMode = 1 | 4; //  present, vertical, blue
+	int initialPos = 0;
+
+	return std::make_shared<CListBox>(createCallback, position, itemOffset, visibleSize, totalSize, initialPos, sliderMode, Rect(sliderPosition, sliderSize) );
+}
+
+std::shared_ptr<CIntObject> GlobalLobbyWidget::buildRoomList(const JsonNode & config) const
+{
+	const auto & createCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
+	{
+		const auto & rooms = CSH->getGlobalLobby().getActiveRooms();
+
+		if(index < rooms.size())
+			return std::make_shared<GlobalLobbyRoomCard>(this->window, rooms[index]);
+		return std::make_shared<CIntObject>();
+	};
+
+	auto position = readPosition(config["position"]);
+	auto itemOffset = readPosition(config["itemOffset"]);
+	auto sliderPosition = readPosition(config["sliderPosition"]);
+	auto sliderSize = readPosition(config["sliderSize"]);
+	size_t visibleSize = 4; // FIXME: how many items can fit into UI?
+	size_t totalSize = 4; //FIXME: how many items are there in total
+	int sliderMode = 1 | 4; //  present, vertical, blue
+	int initialPos = 0;
+
+	return std::make_shared<CListBox>(createCallback, position, itemOffset, visibleSize, totalSize, initialPos, sliderMode, Rect(sliderPosition, sliderSize) );
+}
+
 std::shared_ptr<CLabel> GlobalLobbyWidget::getAccountNameLabel()
 {
 	return widget<CLabel>("accountNameLabel");
@@ -41,3 +97,54 @@ std::shared_ptr<CTextBox> GlobalLobbyWidget::getGameChat()
 {
 	return widget<CTextBox>("gameChat");
 }
+
+std::shared_ptr<CListBox> GlobalLobbyWidget::getAccountList()
+{
+	return widget<CListBox>("accountList");
+}
+
+std::shared_ptr<CListBox> GlobalLobbyWidget::getRoomList()
+{
+	return widget<CListBox>("roomList");
+}
+
+GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	const auto & onInviteClicked = [window, accountID=accountDescription.accountID]()
+	{
+		window->doInviteAccount(accountID);
+	};
+
+	pos.w = 130;
+	pos.h = 40;
+
+	backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
+	labelName = std::make_shared<CLabel>( 5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, accountDescription.displayName);
+	labelStatus = std::make_shared<CLabel>( 5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, accountDescription.status);
+	buttonInvite = std::make_shared<CButton>(Point(95, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onInviteClicked);
+}
+
+GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	const auto & onJoinClicked = [window, roomID=roomDescription.gameRoomID]()
+	{
+		window->doJoinRoom(roomID);
+	};
+
+	auto roomSizeText = MetaString::createFromRawString("%d/%d");
+	roomSizeText.replaceNumber(roomDescription.playersCount);
+	roomSizeText.replaceNumber(roomDescription.playersLimit);
+
+	pos.w = 230;
+	pos.h = 40;
+
+	backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0,0,0,128), ColorRGBA(64,64,64,64));
+	labelName = std::make_shared<CLabel>( 5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName);
+	labelStatus = std::make_shared<CLabel>( 5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, roomDescription.description);
+	labelRoomSize = std::make_shared<CLabel>( 160, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, roomSizeText.toString());
+	buttonJoin = std::make_shared<CButton>(Point(195, 8), AnimationPath::builtin("settingsWindow/button32"), CButton::tooltip(), onJoinClicked);
+}

+ 35 - 0
client/globalLobby/GlobalLobbyWidget.h

@@ -12,15 +12,50 @@
 #include "../gui/InterfaceObjectConfigurable.h"
 
 class GlobalLobbyWindow;
+struct GlobalLobbyAccount;
+struct GlobalLobbyRoom;
+class CListBox;
 
 class GlobalLobbyWidget : public InterfaceObjectConfigurable
 {
 	GlobalLobbyWindow * window;
 
+	std::shared_ptr<CIntObject> buildAccountList(const JsonNode &) const;
+	std::shared_ptr<CIntObject> buildRoomList(const JsonNode &) const;
+
 public:
 	GlobalLobbyWidget(GlobalLobbyWindow * window);
 
 	std::shared_ptr<CLabel> getAccountNameLabel();
 	std::shared_ptr<CTextInput> getMessageInput();
 	std::shared_ptr<CTextBox> getGameChat();
+	std::shared_ptr<CListBox> getAccountList();
+	std::shared_ptr<CListBox> getRoomList();
+};
+
+class GlobalLobbyAccountCard : public CIntObject
+{
+public:
+	GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription);
+
+	GlobalLobbyWindow * window;
+
+	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
+	std::shared_ptr<CLabel> labelName;
+	std::shared_ptr<CLabel> labelStatus;
+	std::shared_ptr<CButton> buttonInvite;
+};
+
+class GlobalLobbyRoomCard : public CIntObject
+{
+public:
+	GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription);
+
+	GlobalLobbyWindow * window;
+
+	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
+	std::shared_ptr<CLabel> labelName;
+	std::shared_ptr<CLabel> labelRoomSize;
+	std::shared_ptr<CLabel> labelStatus;
+	std::shared_ptr<CButton> buttonJoin;
 };

+ 21 - 0
client/globalLobby/GlobalLobbyWindow.cpp

@@ -19,6 +19,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/WindowHandler.h"
 #include "../widgets/TextControls.h"
+#include "../widgets/ObjectLists.h"
 
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/MetaString.h"
@@ -59,6 +60,16 @@ void GlobalLobbyWindow::doCreateGameRoom()
 	// client requests to change room status to private or public
 }
 
+void GlobalLobbyWindow::doInviteAccount(const std::string & accountID)
+{
+
+}
+
+void GlobalLobbyWindow::doJoinRoom(const std::string & roomID)
+{
+
+}
+
 void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when)
 {
 	MetaString chatMessageFormatted;
@@ -71,3 +82,13 @@ void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std:
 
 	widget->getGameChat()->setText(chatHistory);
 }
+
+void GlobalLobbyWindow::onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts)
+{
+	widget->getAccountList()->reset();
+}
+
+void GlobalLobbyWindow::onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms)
+{
+	widget->getRoomList()->reset();
+}

+ 7 - 0
client/globalLobby/GlobalLobbyWindow.h

@@ -12,6 +12,8 @@
 #include "../windows/CWindowObject.h"
 
 class GlobalLobbyWidget;
+struct GlobalLobbyAccount;
+struct GlobalLobbyRoom;
 
 class GlobalLobbyWindow : public CWindowObject
 {
@@ -25,5 +27,10 @@ public:
 	void doSendChatMessage();
 	void doCreateGameRoom();
 
+	void doInviteAccount(const std::string & accountID);
+	void doJoinRoom(const std::string & roomID);
+
 	void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when);
+	void onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts);
+	void onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms);
 };

+ 1 - 1
client/lobby/SelectionTab.cpp

@@ -768,7 +768,7 @@ void SelectionTab::parseSaves(const std::unordered_set<ResourcePath> & files)
 			mapInfo->saveInit(file);
 
 			// Filter out other game modes
-			bool isCampaign = mapInfo->scenarioOptionsOfSave->mode == StartInfo::CAMPAIGN;
+			bool isCampaign = mapInfo->scenarioOptionsOfSave->mode == EStartMode::CAMPAIGN;
 			bool isMultiplayer = mapInfo->amountOfHumanPlayersInSave > 1;
 			bool isTutorial = boost::to_upper_copy(mapInfo->scenarioOptionsOfSave->mapname) == "MAPS/TUTORIAL";
 			switch(CSH->getLoadMode())

+ 12 - 16
client/mainmenu/CMainMenu.cpp

@@ -187,11 +187,11 @@ static std::function<void()> genCommand(CMenuScreen * menu, std::vector<std::str
 				switch(std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
 				{
 				case 0:
-					return std::bind(CMainMenu::openLobby, ESelectionScreen::newGame, true, nullptr, ELoadMode::NONE);
+					return []() { CMainMenu::openLobby(ESelectionScreen::newGame, true, {}, ELoadMode::NONE);};
 				case 1:
 					return []() { GH.windows().createAndPushWindow<CMultiMode>(ESelectionScreen::newGame); };
 				case 2:
-					return std::bind(CMainMenu::openLobby, ESelectionScreen::campaignList, true, nullptr, ELoadMode::NONE);
+					return []() { CMainMenu::openLobby(ESelectionScreen::campaignList, true, {}, ELoadMode::NONE);};
 				case 3:
 					return std::bind(CMainMenu::startTutorial);
 				}
@@ -202,13 +202,14 @@ static std::function<void()> genCommand(CMenuScreen * menu, std::vector<std::str
 				switch(std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
 				{
 				case 0:
-					return std::bind(CMainMenu::openLobby, ESelectionScreen::loadGame, true, nullptr, ELoadMode::SINGLE);
+					return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::SINGLE);};
 				case 1:
 					return []() { GH.windows().createAndPushWindow<CMultiMode>(ESelectionScreen::loadGame); };
 				case 2:
-					return std::bind(CMainMenu::openLobby, ESelectionScreen::loadGame, true, nullptr, ELoadMode::CAMPAIGN);
+					return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::CAMPAIGN);};
 				case 3:
-					return std::bind(CMainMenu::openLobby, ESelectionScreen::loadGame, true, nullptr, ELoadMode::TUTORIAL);
+					return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::TUTORIAL);};
+
 				}
 			}
 			break;
@@ -358,10 +359,9 @@ void CMainMenu::update()
 	GH.windows().simpleRedraw();
 }
 
-void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> * names, ELoadMode loadMode)
+void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> & names, ELoadMode loadMode)
 {
-	CSH->resetStateForLobby(screenType == ESelectionScreen::newGame ? StartInfo::NEW_GAME : StartInfo::LOAD_GAME, names);
-	CSH->screenType = screenType;
+	CSH->resetStateForLobby(screenType == ESelectionScreen::newGame ? EStartMode::NEW_GAME : EStartMode::LOAD_GAME, screenType, names);
 	CSH->loadMode = loadMode;
 
 	GH.windows().createAndPushWindow<CSimpleJoinScreen>(host);
@@ -376,8 +376,7 @@ void CMainMenu::openCampaignLobby(const std::string & campaignFileName, std::str
 
 void CMainMenu::openCampaignLobby(std::shared_ptr<CampaignState> campaign)
 {
-	CSH->resetStateForLobby(StartInfo::CAMPAIGN);
-	CSH->screenType = ESelectionScreen::campaignList;
+	CSH->resetStateForLobby(EStartMode::CAMPAIGN, ESelectionScreen::campaignList, {});
 	CSH->campaignStateToSend = campaign;
 	GH.windows().createAndPushWindow<CSimpleJoinScreen>();
 }
@@ -420,7 +419,7 @@ void CMainMenu::startTutorial()
 		
 	auto mapInfo = std::make_shared<CMapInfo>();
 	mapInfo->mapInit(tutorialMap.getName());
-	CMainMenu::openLobby(ESelectionScreen::newGame, true, nullptr, ELoadMode::NONE);
+	CMainMenu::openLobby(ESelectionScreen::newGame, true, {}, ELoadMode::NONE);
 	CSH->startMapAfterConnection(mapInfo);
 }
 
@@ -470,10 +469,7 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType)
 void CMultiMode::openLobby()
 {
 	close();
-	if (CSH->getGlobalLobby().isConnected())
-		GH.windows().pushWindow(CSH->getGlobalLobby().createLobbyWindow());
-	else
-		GH.windows().pushWindow(CSH->getGlobalLobby().createLoginWindow());
+	CSH->getGlobalLobby().activateInterface();
 }
 
 void CMultiMode::hostTCP()
@@ -547,7 +543,7 @@ void CMultiPlayers::enterSelectionScreen()
 	Settings name = settings.write["general"]["playerName"];
 	name->String() = names[0];
 
-	CMainMenu::openLobby(screenType, host, &names, loadMode);
+	CMainMenu::openLobby(screenType, host, names, loadMode);
 }
 
 CSimpleJoinScreen::CSimpleJoinScreen(bool host)

+ 3 - 3
client/mainmenu/CMainMenu.h

@@ -31,11 +31,11 @@ class CLabel;
 
 
 // TODO: Find new location for these enums
-enum ESelectionScreen : ui8 {
+enum class ESelectionScreen : ui8 {
 	unknown = 0, newGame, loadGame, saveGame, scenarioInfo, campaignList
 };
 
-enum ELoadMode : ui8
+enum class ELoadMode : ui8
 {
 	NONE = 0, SINGLE, MULTI, CAMPAIGN, TUTORIAL
 };
@@ -150,7 +150,7 @@ public:
 	void activate() override;
 	void onScreenResize() override;
 	void update() override;
-	static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> * names, ELoadMode loadMode);
+	static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> & names, ELoadMode loadMode);
 	static void openCampaignLobby(const std::string & campaignFileName, std::string campaignSet = "");
 	static void openCampaignLobby(std::shared_ptr<CampaignState> campaign);
 	static void startTutorial();

+ 1 - 1
client/windows/CMapOverview.h

@@ -24,7 +24,7 @@ class CTextBox;
 class IImage;
 class Canvas;
 class TransparentFilledRectangle;
-enum ESelectionScreen : ui8;
+enum class ESelectionScreen : ui8;
 
 class CMapOverview;
 

+ 19 - 13
config/widgets/lobbyWindow.json

@@ -45,25 +45,23 @@
 			"type": "labelTitleMain",
 			"position": {"x": 15, "y": 10}
 		},
-		
+
 		{
 			"type": "areaFilled",
-			"rect": {"x": 5, "y": 50, "w": 250, "h": 150}
+			"rect": {"x": 5, "y": 50, "w": 250, "h": 500}
 		},
 		{
 			"type": "labelTitle",
 			"position": {"x": 15, "y": 53},
-			"text" : "Match Filter"
+			"text" : "Room List"
 		},
-
 		{
-			"type": "areaFilled",
-			"rect": {"x": 5, "y": 210, "w": 250, "h": 340}
-		},
-		{
-			"type": "labelTitle",
-			"position": {"x": 15, "y": 213},
-			"text" : "Match List"
+			"type" : "roomList",
+			"name" : "roomList",
+			"position" : { "x" : 7, "y" : 69 },
+			"itemOffset" : { "x" : 0, "y" : 40 },
+			"sliderPosition" : { "x" : 230, "y" : 0 },
+			"sliderSize" : { "x" : 450, "y" : 450 }
 		},
 		
 		{
@@ -112,9 +110,17 @@
 		{
 			"type": "labelTitle",
 			"position": {"x": 880, "y": 53},
-			"text" : "Player List"
+			"text" : "Account List"
 		},
-		
+		{
+			"type" : "accountList",
+			"name" : "accountList",
+			"position" : { "x" : 872, "y" : 69 },
+			"itemOffset" : { "x" : 0, "y" : 40 },
+			"sliderPosition" : { "x" : 130, "y" : 0 },
+			"sliderSize" : { "x" : 520, "y" : 520 }
+		},
+
 		{
 			"type": "button",
 			"position": {"x": 840, "y": 10},

+ 1 - 1
lib/StartInfo.cpp

@@ -111,7 +111,7 @@ void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const
 	if(i == si->playerInfos.cend() && !ignoreNoHuman)
 		throw std::domain_error(VLC->generaltexth->translate("core.genrltxt.530"));
 
-	if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
+	if(si->mapGenOptions && si->mode == EStartMode::NEW_GAME)
 	{
 		if(!si->mapGenOptions->checkOptions())
 			throw std::domain_error(VLC->generaltexth->translate("core.genrltxt.751"));

+ 10 - 4
lib/StartInfo.h

@@ -98,12 +98,18 @@ struct DLL_LINKAGE PlayerSettings
 	HeroTypeID getHeroValidated() const;
 };
 
+enum class EStartMode : int32_t
+{
+	NEW_GAME,
+	LOAD_GAME,
+	CAMPAIGN,
+	INVALID = 255
+};
+
 /// Struct which describes the difficulty, the turn time,.. of a heroes match.
 struct DLL_LINKAGE StartInfo
 {
-	enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, INVALID = 255};
-
-	EMode mode;
+	EStartMode mode;
 	ui8 difficulty; //0=easy; 4=impossible
 
 	using TPlayerInfos = std::map<PlayerColor, PlayerSettings>;
@@ -152,7 +158,7 @@ struct DLL_LINKAGE StartInfo
 		h & campState;
 	}
 
-	StartInfo() : mode(INVALID), difficulty(1), seedToBeUsed(0), seedPostInit(0),
+	StartInfo() : mode(EStartMode::INVALID), difficulty(1), seedToBeUsed(0), seedPostInit(0),
 		mapfileChecksum(0), startTimeIso8601(vstd::getDateTimeISO8601Basic(std::time(nullptr))), fileURI("")
 	{
 

+ 3 - 3
lib/gameState/CGameState.cpp

@@ -189,10 +189,10 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog
 
 	switch(scenarioOps->mode)
 	{
-	case StartInfo::NEW_GAME:
+	case EStartMode::NEW_GAME:
 		initNewGame(mapService, allowSavingRandomMap, progressTracking);
 		break;
-	case StartInfo::CAMPAIGN:
+	case EStartMode::CAMPAIGN:
 		initCampaign();
 		break;
 	default:
@@ -711,7 +711,7 @@ void CGameState::initFogOfWar()
 
 void CGameState::initStartingBonus()
 {
-	if (scenarioOps->mode == StartInfo::CAMPAIGN)
+	if (scenarioOps->mode == EStartMode::CAMPAIGN)
 		return;
 	// These are the single scenario bonuses; predefined
 	// campaign bonuses are spread out over other init* functions.

+ 1 - 1
lib/gameState/CGameStateCampaign.cpp

@@ -39,7 +39,7 @@ CampaignHeroReplacement::CampaignHeroReplacement(CGHeroInstance * hero, const Ob
 CGameStateCampaign::CGameStateCampaign(CGameState * owner):
 	gameState(owner)
 {
-	assert(gameState->scenarioOps->mode == StartInfo::CAMPAIGN);
+	assert(gameState->scenarioOps->mode == EStartMode::CAMPAIGN);
 	assert(gameState->scenarioOps->campState != nullptr);
 }
 

+ 4 - 1
lib/network/NetworkConnection.cpp

@@ -95,7 +95,10 @@ void NetworkConnection::sendPacket(const std::vector<uint8_t> & message)
 	ostream.write(reinterpret_cast<const char *>(&messageSize), messageHeaderSize);
 	ostream.write(reinterpret_cast<const char *>(message.data()), message.size());
 
-	boost::asio::write(*socket, writeBuffer );
+	boost::system::error_code ec;
+	boost::asio::write(*socket, writeBuffer, ec );
+
+	// FIXME: handle error?
 }
 
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/networkPacks/PacksForLobby.h

@@ -37,7 +37,7 @@ struct DLL_LINKAGE LobbyClientConnected : public CLobbyPackToPropagate
 	// Set by client before sending pack to server
 	std::string uuid;
 	std::vector<std::string> names;
-	StartInfo::EMode mode = StartInfo::INVALID;
+	EStartMode mode = EStartMode::INVALID;
 	// Changed by server before announcing pack
 	int clientId = -1;
 	int hostClientId = -1;

+ 5 - 0
lib/serializer/Connection.cpp

@@ -106,6 +106,11 @@ bool CConnection::isMyConnection(const std::shared_ptr<INetworkConnection> & oth
 	return otherConnection != nullptr && networkConnection.lock() == otherConnection;
 }
 
+std::shared_ptr<INetworkConnection> CConnection::getConnection()
+{
+	return networkConnection.lock();
+}
+
 void CConnection::disableStackSendingByID()
 {
 	packReader->sendStackInstanceByIds = false;

+ 1 - 0
lib/serializer/Connection.h

@@ -41,6 +41,7 @@ class DLL_LINKAGE CConnection : boost::noncopyable
 
 public:
 	bool isMyConnection(const std::shared_ptr<INetworkConnection> & otherConnection) const;
+	std::shared_ptr<INetworkConnection> getConnection();
 
 	std::string uuid;
 	int connectionID;

+ 35 - 4
lobby/LobbyDatabase.cpp

@@ -96,7 +96,7 @@ void LobbyDatabase::prepareStatements()
 	)";
 
 	static const std::string insertGameRoomText = R"(
-		INSERT INTO gameRooms(roomID, hostAccountID, status, playerLimit) VALUES(?, ?, 'empty', 8);
+		INSERT INTO gameRooms(roomID, hostAccountID, status, playerLimit) VALUES(?, ?, 0, 8);
 	)";
 
 	static const std::string insertGameRoomPlayersText = R"(
@@ -119,6 +119,12 @@ void LobbyDatabase::prepareStatements()
 
 	// UPDATE
 
+	static const std::string setAccountOnlineText = R"(
+		UPDATE accounts
+		SET online = ?
+		WHERE accountID = ?
+	)";
+
 	static const std::string setGameRoomStatusText = R"(
 		UPDATE gameRooms
 		SET status = ?
@@ -145,7 +151,7 @@ void LobbyDatabase::prepareStatements()
 	static const std::string getIdleGameRoomText = R"(
 		SELECT roomID
 		FROM gameRooms
-		WHERE hostAccountID = ? AND status = 'idle'
+		WHERE hostAccountID = ? AND status = 0
 		LIMIT 1
 	)";
 
@@ -153,7 +159,7 @@ void LobbyDatabase::prepareStatements()
 		SELECT grp.roomID
 		FROM gameRoomPlayers grp
 		LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID
-		WHERE accountID = ? AND status IN ('public', 'private', 'busy')
+		WHERE accountID = ? AND status IN (1, 2)
 		LIMIT 1
 	)";
 
@@ -163,6 +169,13 @@ void LobbyDatabase::prepareStatements()
 		WHERE online = 1
 	)";
 
+	static const std::string getActiveGameRoomsText = R"(
+		SELECT roomID, hostAccountID, displayName, status, 0, playerLimit
+		FROM gameRooms
+		LEFT JOIN accounts ON hostAccountID = accountID
+		WHERE status = 1
+	)";
+
 	static const std::string getAccountDisplayNameText = R"(
 		SELECT displayName
 		FROM accounts
@@ -216,6 +229,7 @@ void LobbyDatabase::prepareStatements()
 	deleteGameRoomPlayersStatement = database->prepare(deleteGameRoomPlayersText);
 	deleteGameRoomInvitesStatement = database->prepare(deleteGameRoomInvitesText);
 
+	setAccountOnlineStatement = database->prepare(setAccountOnlineText);
 	setGameRoomStatusStatement = database->prepare(setGameRoomStatusText);
 	setGameRoomPlayerLimitStatement = database->prepare(setGameRoomPlayerLimitText);
 
@@ -223,6 +237,7 @@ void LobbyDatabase::prepareStatements()
 	getIdleGameRoomStatement = database->prepare(getIdleGameRoomText);
 	getAccountGameRoomStatement = database->prepare(getAccountGameRoomText);
 	getActiveAccountsStatement = database->prepare(getActiveAccountsText);
+	getActiveGameRoomsStatement = database->prepare(getActiveGameRoomsText);
 	getAccountDisplayNameStatement = database->prepare(getAccountDisplayNameText);
 
 	isAccountCookieValidStatement = database->prepare(isAccountCookieValidText);
@@ -285,6 +300,11 @@ std::vector<LobbyChatMessage> LobbyDatabase::getRecentMessageHistory()
 	return result;
 }
 
+void LobbyDatabase::setAccountOnline(const std::string & accountID, bool isOnline)
+{
+	setAccountOnlineStatement->executeOnce(isOnline ? 1 : 0, accountID);
+}
+
 void LobbyDatabase::setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus)
 {
 	setGameRoomStatusStatement->executeOnce(vstd::to_underlying(roomStatus), roomID);
@@ -404,7 +424,16 @@ bool LobbyDatabase::isAccountIDExists(const std::string & accountID)
 
 std::vector<LobbyGameRoom> LobbyDatabase::getActiveGameRooms()
 {
-	return {};
+	std::vector<LobbyGameRoom> result;
+
+	while(getActiveGameRoomsStatement->execute())
+	{
+		LobbyGameRoom entry;
+		getActiveGameRoomsStatement->getColumns(entry.roomID, entry.hostAccountID, entry.hostAccountDisplayName, entry.roomStatus, entry.playersCount, entry.playersLimit);
+		result.push_back(entry);
+	}
+	getActiveGameRoomsStatement->reset();
+	return result;
 }
 
 std::vector<LobbyAccount> LobbyDatabase::getActiveAccounts()
@@ -425,6 +454,7 @@ std::string LobbyDatabase::getIdleGameRoom(const std::string & hostAccountID)
 {
 	std::string result;
 
+	getIdleGameRoomStatement->setBinds(hostAccountID);
 	if(getIdleGameRoomStatement->execute())
 		getIdleGameRoomStatement->getColumns(result);
 
@@ -436,6 +466,7 @@ std::string LobbyDatabase::getAccountGameRoom(const std::string & accountID)
 {
 	std::string result;
 
+	getAccountGameRoomStatement->setBinds(accountID);
 	if(getAccountGameRoomStatement->execute())
 		getAccountGameRoomStatement->getColumns(result);
 

+ 4 - 1
lobby/LobbyDatabase.h

@@ -31,13 +31,15 @@ class LobbyDatabase
 	SQLiteStatementPtr deleteGameRoomPlayersStatement;
 	SQLiteStatementPtr deleteGameRoomInvitesStatement;
 
+	SQLiteStatementPtr setAccountOnlineStatement;
 	SQLiteStatementPtr setGameRoomStatusStatement;
 	SQLiteStatementPtr setGameRoomPlayerLimitStatement;
 
 	SQLiteStatementPtr getRecentMessageHistoryStatement;
 	SQLiteStatementPtr getIdleGameRoomStatement;
-	SQLiteStatementPtr getAccountGameRoomStatement;
+	SQLiteStatementPtr getActiveGameRoomsStatement;
 	SQLiteStatementPtr getActiveAccountsStatement;
+	SQLiteStatementPtr getAccountGameRoomStatement;
 	SQLiteStatementPtr getAccountDisplayNameStatement;
 
 	SQLiteStatementPtr isAccountCookieValidStatement;
@@ -54,6 +56,7 @@ public:
 	explicit LobbyDatabase(const boost::filesystem::path & databasePath);
 	~LobbyDatabase();
 
+	void setAccountOnline(const std::string & accountID, bool isOnline);
 	void setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus);
 	void setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit);
 

+ 9 - 7
lobby/LobbyDefines.h

@@ -18,7 +18,9 @@ struct LobbyAccount
 
 struct LobbyGameRoom
 {
-	std::string roomUUID;
+	std::string roomID;
+	std::string hostAccountID;
+	std::string hostAccountDisplayName;
 	std::string roomStatus;
 	uint32_t playersCount;
 	uint32_t playersLimit;
@@ -48,10 +50,10 @@ enum class LobbyInviteStatus : int32_t
 
 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
+	IDLE = 0, // server is ready but no players are in the room
+	PUBLIC = 1, // host has joined and allows anybody to join
+	PRIVATE = 2, // host has joined but only allows those he invited to join
+	//BUSY = 3, // match is ongoing
+	//CANCELLED = 4, // game room was cancelled without starting the game
+	CLOSED = 5, // game room was closed after playing for some time
 };

+ 18 - 6
lobby/LobbyServer.cpp

@@ -140,7 +140,7 @@ void LobbyServer::broadcastActiveAccounts()
 		JsonNode jsonEntry;
 		jsonEntry["accountID"].String() = account.accountID;
 		jsonEntry["displayName"].String() = account.displayName;
-		//		jsonEntry["status"].String() = account.status;
+		jsonEntry["status"].String() = "In Lobby"; // TODO: in room status, in match status, offline status(?)
 		reply["accounts"].Vector().push_back(jsonEntry);
 	}
 
@@ -157,10 +157,13 @@ void LobbyServer::broadcastActiveGameRooms()
 	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;
+		jsonEntry["gameRoomID"].String() = gameRoom.roomID;
+		jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID;
+		jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName;
+		jsonEntry["description"].String() = "TODO: ROOM DESCRIPTION";
+//		jsonEntry["status"].String() = gameRoom.roomStatus;
+		jsonEntry["playersCount"].Integer() = gameRoom.playersCount;
+		jsonEntry["playersLimit"].Integer() = gameRoom.playersLimit;
 		reply["gameRooms"].Vector().push_back(jsonEntry);
 	}
 
@@ -204,6 +207,12 @@ void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection)
 
 void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection)
 {
+	if (activeAccounts.count(connection))
+		database->setAccountOnline(activeAccounts.at(connection).accountID, false);
+
+	if (activeGameRooms.count(connection))
+		database->setGameRoomStatus(activeGameRooms.at(connection).roomID, LobbyRoomState::CLOSED);
+
 	// 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);
@@ -334,6 +343,7 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co
 	// prolong existing cookie
 	database->updateAccessCookie(accountID, accountCookie);
 	database->updateAccountLoginTime(accountID);
+	database->setAccountOnline(accountID, true);
 
 	std::string displayName = database->getAccountDisplayName(accountID);
 
@@ -369,6 +379,8 @@ void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, co
 		activeGameRooms[connection].roomID = gameRoomID;
 		sendLoginSuccess(connection, accountCookie, {});
 	}
+
+	broadcastActiveGameRooms();
 }
 
 void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
@@ -451,7 +463,7 @@ void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, c
 		database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE);
 
 	// TODO: additional flags / initial settings, e.g. allowCheats
-	// TODO: connection mode: direct or proxy. For now direct is assumed
+	// TODO: connection mode: direct or proxy. For now direct is assumed. Proxy might be needed later, for hosted servers
 
 	broadcastActiveGameRooms();
 	sendJoinRoomSuccess(connection, gameRoomID);

+ 1 - 0
server/CMakeLists.txt

@@ -15,6 +15,7 @@ set(server_SRCS
 		processors/PlayerMessageProcessor.cpp
 		processors/TurnOrderProcessor.cpp
 
+		EntryPoint.cpp
 		CGameHandler.cpp
 		GlobalLobbyProcessor.cpp
 		ServerSpellCastEnvironment.cpp

+ 32 - 196
server/CVCMIServer.cpp

@@ -10,44 +10,15 @@
 #include "StdInc.h"
 #include "CVCMIServer.h"
 
-#include "../lib/filesystem/Filesystem.h"
-#include "../lib/campaign/CampaignState.h"
-#include "../lib/CThreadHelper.h"
-#include "../lib/CArtHandler.h"
-#include "../lib/CGeneralTextHandler.h"
-#include "../lib/CHeroHandler.h"
-#include "../lib/CTownHandler.h"
-#include "../lib/CBuildingHandler.h"
-#include "../lib/spells/CSpellHandler.h"
-#include "../lib/CCreatureHandler.h"
-#include "zlib.h"
-#include "../lib/StartInfo.h"
-#include "../lib/mapping/CMapHeader.h"
-#include "../lib/rmg/CMapGenOptions.h"
-#include "LobbyNetPackVisitors.h"
-#ifdef VCMI_ANDROID
-#include <jni.h>
-#include <android/log.h>
-#include "lib/CAndroidVMHelper.h"
-#endif
-#include "../lib/VCMI_Lib.h"
-#include "../lib/VCMIDirs.h"
 #include "CGameHandler.h"
 #include "GlobalLobbyProcessor.h"
+#include "LobbyNetPackVisitors.h"
 #include "processors/PlayerMessageProcessor.h"
-#include "../lib/mapping/CMapInfo.h"
-#include "../lib/GameConstants.h"
-#include "../lib/logging/CBasicLogConfigurator.h"
-#include "../lib/CConfigHandler.h"
-#include "../lib/ScopeGuard.h"
-#include "../lib/serializer/CMemorySerializer.h"
-#include "../lib/serializer/Cast.h"
-#include "../lib/serializer/Connection.h"
 
-#include "../lib/UnlockGuard.h"
-
-// for applier
+#include "../lib/CHeroHandler.h"
 #include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
+#include "../lib/serializer/CMemorySerializer.h"
+#include "../lib/serializer/Connection.h"
 
 // UUID generation
 #include <boost/uuid/uuid.hpp>
@@ -55,8 +26,6 @@
 #include <boost/uuid/uuid_generators.hpp>
 #include <boost/program_options.hpp>
 
-#include "../lib/gameState/CGameState.h"
-
 template<typename T> class CApplyOnServer;
 
 class CBaseForServerApply
@@ -147,9 +116,6 @@ public:
 	}
 };
 
-std::string SERVER_NAME_AFFIX = "server";
-std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')';
-
 CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts)
 	: state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false)
 {
@@ -158,20 +124,29 @@ CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts)
 	applier = std::make_shared<CApplier<CBaseForServerApply>>();
 	registerTypesLobbyPacks(*applier);
 
+	networkHandler = INetworkHandler::createHandler();
+
+	if(cmdLineOptions.count("lobby"))
+		lobbyProcessor = std::make_unique<GlobalLobbyProcessor>(*this);
+	else
+		startAcceptingIncomingConnections();
+}
+
+CVCMIServer::~CVCMIServer() = default;
+
+void CVCMIServer::startAcceptingIncomingConnections()
+{
 	uint16_t port = 3030;
+
 	if(cmdLineOptions.count("port"))
 		port = cmdLineOptions["port"].as<uint16_t>();
 	logNetwork->info("Port %d will be used", port);
 
-	networkHandler = INetworkHandler::createHandler();
 	networkServer = networkHandler->createServerTCP(*this);
 	networkServer->start(port);
-	establishOutgoingConnection();
 	logNetwork->info("Listening for connections at port %d", port);
 }
 
-CVCMIServer::~CVCMIServer() = default;
-
 void CVCMIServer::onNewConnection(const std::shared_ptr<INetworkConnection> & connection)
 {
 	if(state == EServerState::LOBBY)
@@ -206,7 +181,7 @@ EServerState CVCMIServer::getState() const
 
 std::shared_ptr<CConnection> CVCMIServer::findConnection(const std::shared_ptr<INetworkConnection> & netConnection)
 {
-	for (auto const & gameConnection : activeConnections)
+	for(const auto & gameConnection : activeConnections)
 	{
 		if (gameConnection->isMyConnection(netConnection))
 			return gameConnection;
@@ -249,12 +224,6 @@ void CVCMIServer::onTimer()
 	networkHandler->createTimer(*this, serverUpdateInterval);
 }
 
-void CVCMIServer::establishOutgoingConnection()
-{
-	if(cmdLineOptions.count("lobby"))
-		lobbyProcessor = std::make_unique<GlobalLobbyProcessor>(*this);
-}
-
 void CVCMIServer::prepareToRestart()
 {
 	if(state == EServerState::GAMEPLAY)
@@ -304,7 +273,7 @@ bool CVCMIServer::prepareToStartGame()
 	gh = std::make_shared<CGameHandler>(this);
 	switch(si->mode)
 	{
-	case StartInfo::CAMPAIGN:
+	case EStartMode::CAMPAIGN:
 		logNetwork->info("Preparing to start new campaign");
 		si->startTimeIso8601 = vstd::getDateTimeISO8601Basic(std::time(nullptr));
 		si->fileURI = mi->fileURI;
@@ -313,14 +282,14 @@ bool CVCMIServer::prepareToStartGame()
 		gh->init(si.get(), progressTracking);
 		break;
 
-	case StartInfo::NEW_GAME:
+	case EStartMode::NEW_GAME:
 		logNetwork->info("Preparing to start new game");
 		si->startTimeIso8601 = vstd::getDateTimeISO8601Basic(std::time(nullptr));
 		si->fileURI = mi->fileURI;
 		gh->init(si.get(), progressTracking);
 		break;
 
-	case StartInfo::LOAD_GAME:
+	case EStartMode::LOAD_GAME:
 		logNetwork->info("Preparing to start loaded game");
 		if(!gh->load(si->mapname))
 		{
@@ -346,7 +315,7 @@ void CVCMIServer::startGameImmediately()
 	for(auto c : activeConnections)
 		c->enterGameplayConnectionMode(gh->gs);
 
-	gh->start(si->mode == StartInfo::LOAD_GAME);
+	gh->start(si->mode == EStartMode::LOAD_GAME);
 	state = EServerState::GAMEPLAY;
 	lastTimerUpdateTime = gameplayStartTime = std::chrono::steady_clock::now();
 	onTimer();
@@ -360,7 +329,11 @@ void CVCMIServer::onDisconnected(const std::shared_ptr<INetworkConnection> & con
 	vstd::erase(activeConnections, c);
 
 	if(activeConnections.empty() || hostClientId == c->connectionID)
+	{
+		networkHandler->stop();
 		state = EServerState::SHUTDOWN;
+		return;
+	}
 
 	if(gh && state == EServerState::GAMEPLAY)
 	{
@@ -428,7 +401,7 @@ bool CVCMIServer::passHost(int toConnectionId)
 	return false;
 }
 
-void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, StartInfo::EMode mode)
+void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, EStartMode mode)
 {
 	if(state != EServerState::LOBBY)
 		throw std::runtime_error("CVCMIServer::clientConnected called while game is not accepting clients!");
@@ -468,16 +441,16 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
 
 void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
 {
+	networkServer->closeConnection(c->getConnection());
 	vstd::erase(activeConnections, c);
 
 	if(activeConnections.empty() || hostClientId == c->connectionID)
 	{
+		networkHandler->stop();
 		state = EServerState::SHUTDOWN;
 		return;
 	}
 
-	// TODO: close network connection
-
 //	PlayerReinitInterface startAiPack;
 //	startAiPack.playerConnectionId = PlayerSettings::PLAYER_AI;
 //
@@ -565,7 +538,7 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo,
 	if(mi->scenarioOptionsOfSave)
 	{
 		si = CMemorySerializer::deepCopy(*mi->scenarioOptionsOfSave);
-		si->mode = StartInfo::LOAD_GAME;
+		si->mode = EStartMode::LOAD_GAME;
 		if(si->campState)
 			campaignMap = si->campState->currentScenario().value();
 
@@ -581,7 +554,7 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo,
 			}
 		}
 	}
-	else if(si->mode == StartInfo::NEW_GAME || si->mode == StartInfo::CAMPAIGN)
+	else if(si->mode == EStartMode::NEW_GAME || si->mode == EStartMode::CAMPAIGN)
 	{
 		if(mi->campaign)
 			return;
@@ -634,7 +607,7 @@ void CVCMIServer::updateAndPropagateLobbyState()
 {
 	// Update player settings for RMG
 	// TODO: find appropriate location for this code
-	if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
+	if(si->mapGenOptions && si->mode == EStartMode::NEW_GAME)
 	{
 		for(const auto & psetPair : si->playerInfos)
 		{
@@ -971,140 +944,3 @@ ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const
 
 	return 0;
 }
-
-static void handleCommandOptions(int argc, const char * argv[], boost::program_options::variables_map & options)
-{
-	namespace po = boost::program_options;
-	po::options_description opts("Allowed options");
-	opts.add_options()
-	("help,h", "display help and exit")
-	("version,v", "display version information and exit")
-	("run-by-client", "indicate that server launched by client on same machine")
-	("port", po::value<ui16>(), "port at which server will listen to connections from client")
-	("lobby", "start server in lobby mode in which server connects to a global lobby");
-
-	if(argc > 1)
-	{
-		try
-		{
-			po::store(po::parse_command_line(argc, argv, opts), options);
-		}
-		catch(boost::program_options::error & e)
-		{
-			std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl;
-		}
-	}
-	
-#ifdef SINGLE_PROCESS_APP
-	options.emplace("run-by-client", po::variable_value{true, true});
-#endif
-
-	po::notify(options);
-
-#ifndef SINGLE_PROCESS_APP
-	if(options.count("help"))
-	{
-		auto time = std::time(nullptr);
-		printf("%s - A Heroes of Might and Magic 3 clone\n", GameConstants::VCMI_VERSION.c_str());
-		printf("Copyright (C) 2007-%d VCMI dev team - see AUTHORS file\n", std::localtime(&time)->tm_year + 1900);
-		printf("This is free software; see the source for copying conditions. There is NO\n");
-		printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
-		printf("\n");
-		std::cout << opts;
-		exit(0);
-	}
-
-	if(options.count("version"))
-	{
-		printf("%s\n", GameConstants::VCMI_VERSION.c_str());
-		std::cout << VCMIDirs::get().genHelpString();
-		exit(0);
-	}
-#endif
-}
-
-#ifdef SINGLE_PROCESS_APP
-#define main server_main
-#endif
-
-#if VCMI_ANDROID_DUAL_PROCESS
-void CVCMIServer::create()
-{
-	const int argc = 1;
-	const char * argv[argc] = { "android-server" };
-#else
-int main(int argc, const char * argv[])
-{
-#endif
-
-#if !defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
-	// Correct working dir executable folder (not bundle folder) so we can use executable relative paths
-	boost::filesystem::current_path(boost::filesystem::system_complete(argv[0]).parent_path());
-#endif
-
-#ifndef VCMI_IOS
-	console = new CConsoleHandler();
-#endif
-	CBasicLogConfigurator logConfig(VCMIDirs::get().userLogsPath() / "VCMI_Server_log.txt", console);
-	logConfig.configureDefault();
-	logGlobal->info(SERVER_NAME);
-
-	boost::program_options::variables_map opts;
-	handleCommandOptions(argc, argv, opts);
-	preinitDLL(console, false);
-	logConfig.configure();
-
-	loadDLLClasses();
-	srand((ui32)time(nullptr));
-
-	CVCMIServer server(opts);
-
-#ifdef SINGLE_PROCESS_APP
-	boost::condition_variable * cond = reinterpret_cast<boost::condition_variable *>(const_cast<char *>(argv[0]));
-	cond->notify_one();
-#endif
-
-	server.run();
-
-#if VCMI_ANDROID_DUAL_PROCESS
-	CAndroidVMHelper envHelper;
-	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
-#endif
-	logConfig.deconfigure();
-	vstd::clear_pointer(VLC);
-
-#if !VCMI_ANDROID_DUAL_PROCESS
-	return 0;
-#endif
-}
-
-#if VCMI_ANDROID_DUAL_PROCESS
-extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_createServer(JNIEnv * env, jclass cls)
-{
-	__android_log_write(ANDROID_LOG_INFO, "VCMI", "Got jni call to init server");
-	CAndroidVMHelper::cacheVM(env);
-
-	CVCMIServer::create();
-}
-
-extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_initClassloader(JNIEnv * baseEnv, jclass cls)
-{
-	CAndroidVMHelper::initClassloader(baseEnv);
-}
-#elif defined(SINGLE_PROCESS_APP)
-void CVCMIServer::create(boost::condition_variable * cond, const std::vector<std::string> & args)
-{
-	std::vector<const void *> argv = {cond};
-	for(auto & a : args)
-		argv.push_back(a.c_str());
-	main(argv.size(), reinterpret_cast<const char **>(&*argv.begin()));
-}
-
-#ifdef VCMI_ANDROID
-void CVCMIServer::reuseClientJNIEnv(void * jniEnv)
-{
-	CAndroidVMHelper::initClassloader(jniEnv);
-	CAndroidVMHelper::alwaysUseLoadedClass = true;
-}
-#endif // VCMI_ANDROID
-#endif // VCMI_ANDROID_DUAL_PROCESS

+ 2 - 4
server/CVCMIServer.h

@@ -70,8 +70,6 @@ private:
 	std::shared_ptr<CApplier<CBaseForServerApply>> applier;
 	EServerState state;
 
-	void establishOutgoingConnection();
-
 	std::shared_ptr<CConnection> findConnection(const std::shared_ptr<INetworkConnection> &);
 
 	int currentClientId;
@@ -84,7 +82,6 @@ public:
 	void onNewConnection(const std::shared_ptr<INetworkConnection> &) override;
 	void onTimer() override;
 
-
 	std::shared_ptr<CGameHandler> gh;
 	boost::program_options::variables_map cmdLineOptions;
 
@@ -96,6 +93,7 @@ public:
 	bool prepareToStartGame();
 	void prepareToRestart();
 	void startGameImmediately();
+	void startAcceptingIncomingConnections();
 
 	void threadHandleClient(std::shared_ptr<CConnection> c);
 
@@ -107,7 +105,7 @@ public:
 	void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const;
 	void updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpt = {});
 
-	void clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, StartInfo::EMode mode);
+	void clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, EStartMode mode);
 	void clientDisconnected(std::shared_ptr<CConnection> c);
 	void reconnectPlayer(int connId);
 

+ 170 - 0
server/EntryPoint.cpp

@@ -0,0 +1,170 @@
+/*
+ * EntryPoint.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 "CVCMIServer.h"
+
+#include "../lib/CConsoleHandler.h"
+#include "../lib/logging/CBasicLogConfigurator.h"
+#include "../lib/VCMIDirs.h"
+#include "../lib/VCMI_Lib.h"
+
+#ifdef VCMI_ANDROID
+#include <jni.h>
+#include <android/log.h>
+#include "lib/CAndroidVMHelper.h"
+#endif
+
+#include <boost/program_options.hpp>
+
+std::string SERVER_NAME_AFFIX = "server";
+std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')';
+
+static void handleCommandOptions(int argc, const char * argv[], boost::program_options::variables_map & options)
+{
+	namespace po = boost::program_options;
+	po::options_description opts("Allowed options");
+	opts.add_options()
+	("help,h", "display help and exit")
+	("version,v", "display version information and exit")
+	("run-by-client", "indicate that server launched by client on same machine")
+	("port", po::value<ui16>(), "port at which server will listen to connections from client")
+	("lobby", "start server in lobby mode in which server connects to a global lobby");
+
+	if(argc > 1)
+	{
+		try
+		{
+			po::store(po::parse_command_line(argc, argv, opts), options);
+		}
+		catch(boost::program_options::error & e)
+		{
+			std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl;
+		}
+	}
+
+#ifdef SINGLE_PROCESS_APP
+	options.emplace("run-by-client", po::variable_value{true, true});
+#endif
+
+	po::notify(options);
+
+#ifndef SINGLE_PROCESS_APP
+	if(options.count("help"))
+	{
+		auto time = std::time(nullptr);
+		printf("%s - A Heroes of Might and Magic 3 clone\n", GameConstants::VCMI_VERSION.c_str());
+		printf("Copyright (C) 2007-%d VCMI dev team - see AUTHORS file\n", std::localtime(&time)->tm_year + 1900);
+		printf("This is free software; see the source for copying conditions. There is NO\n");
+		printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
+		printf("\n");
+		std::cout << opts;
+		exit(0);
+	}
+
+	if(options.count("version"))
+	{
+		printf("%s\n", GameConstants::VCMI_VERSION.c_str());
+		std::cout << VCMIDirs::get().genHelpString();
+		exit(0);
+	}
+#endif
+}
+
+#ifdef SINGLE_PROCESS_APP
+#define main server_main
+#endif
+
+#if VCMI_ANDROID_DUAL_PROCESS
+void CVCMIServer::create()
+{
+	const int argc = 1;
+	const char * argv[argc] = { "android-server" };
+#else
+int main(int argc, const char * argv[])
+{
+#endif
+
+#if !defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
+	// Correct working dir executable folder (not bundle folder) so we can use executable relative paths
+	boost::filesystem::current_path(boost::filesystem::system_complete(argv[0]).parent_path());
+#endif
+
+#ifndef VCMI_IOS
+	console = new CConsoleHandler();
+#endif
+	CBasicLogConfigurator logConfig(VCMIDirs::get().userLogsPath() / "VCMI_Server_log.txt", console);
+	logConfig.configureDefault();
+	logGlobal->info(SERVER_NAME);
+
+	boost::program_options::variables_map opts;
+	handleCommandOptions(argc, argv, opts);
+	preinitDLL(console, false);
+	logConfig.configure();
+
+	loadDLLClasses();
+	srand((ui32)time(nullptr));
+
+	{
+		CVCMIServer server(opts);
+
+#ifdef SINGLE_PROCESS_APP
+		boost::condition_variable * cond = reinterpret_cast<boost::condition_variable *>(const_cast<char *>(argv[0]));
+		cond->notify_one();
+#endif
+
+		server.run();
+
+		// CVCMIServer destructor must be called here - before VLC cleanup
+	}
+
+
+#if VCMI_ANDROID_DUAL_PROCESS
+	CAndroidVMHelper envHelper;
+	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
+#endif
+	logConfig.deconfigure();
+	vstd::clear_pointer(VLC);
+
+#if !VCMI_ANDROID_DUAL_PROCESS
+	return 0;
+#endif
+}
+
+#if VCMI_ANDROID_DUAL_PROCESS
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_createServer(JNIEnv * env, jclass cls)
+{
+	__android_log_write(ANDROID_LOG_INFO, "VCMI", "Got jni call to init server");
+	CAndroidVMHelper::cacheVM(env);
+
+	CVCMIServer::create();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_initClassloader(JNIEnv * baseEnv, jclass cls)
+{
+	CAndroidVMHelper::initClassloader(baseEnv);
+}
+#elif defined(SINGLE_PROCESS_APP)
+void CVCMIServer::create(boost::condition_variable * cond, const std::vector<std::string> & args)
+{
+	std::vector<const void *> argv = {cond};
+	for(auto & a : args)
+		argv.push_back(a.c_str());
+	main(argv.size(), reinterpret_cast<const char **>(&*argv.begin()));
+}
+
+#ifdef VCMI_ANDROID
+void CVCMIServer::reuseClientJNIEnv(void * jniEnv)
+{
+	CAndroidVMHelper::initClassloader(jniEnv);
+	CAndroidVMHelper::alwaysUseLoadedClass = true;
+}
+#endif // VCMI_ANDROID
+#endif // VCMI_ANDROID_DUAL_PROCESS

+ 3 - 1
server/GlobalLobbyProcessor.cpp

@@ -57,6 +57,7 @@ void GlobalLobbyProcessor::receiveLoginSuccess(const JsonNode & json)
 {
 	// no-op, wait just for any new commands from lobby
 	logGlobal->info("Succesfully connected to lobby server");
+	owner.startAcceptingIncomingConnections();
 }
 
 void GlobalLobbyProcessor::receiveAccountJoinsRoom(const JsonNode & json)
@@ -83,6 +84,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr<INetwor
 
 		JsonNode toSend;
 		toSend["type"].String() = "serverLogin";
+		toSend["gameRoomID"].String() = owner.uuid;
 		toSend["accountID"] = settings["lobby"]["accountID"];
 		toSend["accountCookie"] = settings["lobby"]["accountCookie"];
 		sendMessage(toSend);
@@ -97,7 +99,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr<INetwor
 
 		JsonNode toSend;
 		toSend["type"].String() = "serverProxyLogin";
-		toSend["gameRoomID"].String() = "";
+		toSend["gameRoomID"].String() = owner.uuid;
 		toSend["accountID"].String() = accountID;
 		toSend["accountCookie"] = settings["lobby"]["accountCookie"];
 		sendMessage(toSend);

+ 1 - 1
server/NetPacksLobbyServer.cpp

@@ -158,7 +158,7 @@ void ApplyOnServerNetPackVisitor::visitLobbySetMap(LobbySetMap & pack)
 void ApplyOnServerNetPackVisitor::visitLobbySetCampaign(LobbySetCampaign & pack)
 {
 	srv.si->mapname = pack.ourCampaign->getFilename();
-	srv.si->mode = StartInfo::CAMPAIGN;
+	srv.si->mode = EStartMode::CAMPAIGN;
 	srv.si->campState = pack.ourCampaign;
 	srv.si->turnTimerInfo = TurnTimerInfo{};