Bläddra i källkod

Full rework of pre-game interface and networking

New features for players:
* Loading for multiplayer. Any save could be used for multiplayer.
* Restart for multiplayer. All clients will restart together.
* Loading from single save.
* Hotseat mixed with network game. Multiple players per client.
* Now connection to server could be cancelled.
* Return to menu on disconnections instead of crashes.
* Restoring of last selected map, save or campaign on next run.

TLDR on important changes in engine code:
* UI: work with server separated from UI
* UI: all explitic blitting replaced with IntObject's
* UI: all new code use smart pointers instead of DISPOSE
* Gameplay always start through lobby controlled by server.
* Threads receiving netpacks now shared for lobby and gameplay.
* Campaigns: heroes for crossover now serialized as JsonNode.
Arseniy Shestakov 7 år sedan
förälder
incheckning
ac66fc7f42
85 ändrade filer med 8695 tillägg och 7599 borttagningar
  1. 4 4
      CCallback.cpp
  2. 3 2
      CCallback.h
  3. 5 0
      ChangeLog
  4. 3 1
      client/CGameInfo.cpp
  5. 1 1
      client/CGameInfo.h
  6. 95 207
      client/CMT.cpp
  7. 0 3
      client/CMT.h
  8. 31 2
      client/CMakeLists.txt
  9. 36 71
      client/CPlayerInterface.cpp
  10. 1 15
      client/CPlayerInterface.h
  11. 0 4374
      client/CPreGame.cpp
  12. 0 650
      client/CPreGame.h
  13. 674 0
      client/CServerHandler.cpp
  14. 145 0
      client/CServerHandler.h
  15. 229 544
      client/Client.cpp
  16. 71 124
      client/Client.h
  17. 6 14
      client/NetPacksClient.cpp
  18. 142 0
      client/NetPacksLobbyClient.cpp
  19. 3 3
      client/gui/CGuiHandler.cpp
  20. 15 5
      client/gui/CGuiHandler.h
  21. 2 0
      client/gui/SDL_Extensions.cpp
  22. 6 0
      client/gui/SDL_Extensions.h
  23. 534 0
      client/lobby/CBonusSelection.cpp
  24. 93 0
      client/lobby/CBonusSelection.h
  25. 201 0
      client/lobby/CLobbyScreen.cpp
  26. 35 0
      client/lobby/CLobbyScreen.h
  27. 97 0
      client/lobby/CSavingScreen.cpp
  28. 32 0
      client/lobby/CSavingScreen.h
  29. 59 0
      client/lobby/CScenarioInfoScreen.cpp
  30. 30 0
      client/lobby/CScenarioInfoScreen.h
  31. 407 0
      client/lobby/CSelectionBase.cpp
  32. 146 0
      client/lobby/CSelectionBase.h
  33. 537 0
      client/lobby/OptionsTab.cpp
  34. 127 0
      client/lobby/OptionsTab.h
  35. 300 0
      client/lobby/RandomMapTab.cpp
  36. 58 0
      client/lobby/RandomMapTab.h
  37. 664 0
      client/lobby/SelectionTab.cpp
  38. 98 0
      client/lobby/SelectionTab.h
  39. 156 0
      client/mainmenu/CCampaignScreen.cpp
  40. 55 0
      client/mainmenu/CCampaignScreen.h
  41. 566 0
      client/mainmenu/CMainMenu.cpp
  42. 183 0
      client/mainmenu/CMainMenu.h
  43. 64 0
      client/mainmenu/CPrologEpilogVideo.cpp
  44. 31 0
      client/mainmenu/CPrologEpilogVideo.h
  45. 59 0
      client/mainmenu/CreditsScreen.cpp
  46. 27 0
      client/mainmenu/CreditsScreen.h
  47. 1 1
      client/widgets/AdventureMapClasses.cpp
  48. 12 8
      client/windows/CAdvmapInterface.cpp
  49. 14 5
      client/windows/GUIClasses.cpp
  50. 44 34
      lib/CGameState.cpp
  51. 6 1
      lib/CGameState.h
  52. 3 1
      lib/CMakeLists.txt
  53. 2 0
      lib/GameConstants.h
  54. 3 0
      lib/IGameCallback.cpp
  55. 64 204
      lib/NetPacks.h
  56. 12 6
      lib/NetPacksBase.h
  57. 37 36
      lib/NetPacksLib.cpp
  58. 311 0
      lib/NetPacksLobby.h
  59. 195 0
      lib/StartInfo.cpp
  60. 91 28
      lib/StartInfo.h
  61. 2 0
      lib/mapObjects/CGHeroInstance.h
  62. 1 1
      lib/mapObjects/CGPandoraBox.cpp
  63. 6 0
      lib/mapObjects/CGTownInstance.cpp
  64. 1 0
      lib/mapObjects/CGTownInstance.h
  65. 107 13
      lib/mapping/CCampaignHandler.cpp
  66. 30 7
      lib/mapping/CCampaignHandler.h
  67. 10 5
      lib/mapping/CMap.cpp
  68. 7 4
      lib/mapping/CMap.h
  69. 136 47
      lib/mapping/CMapInfo.cpp
  70. 17 10
      lib/mapping/CMapInfo.h
  71. 1 1
      lib/registerTypes/RegisterTypes.cpp
  72. 31 23
      lib/registerTypes/RegisterTypes.h
  73. 37 37
      lib/registerTypes/TypesLobbyPacks.cpp
  74. 2 1
      lib/rmg/CMapGenOptions.cpp
  75. 1 1
      lib/serializer/CSerializer.h
  76. 43 53
      lib/serializer/Connection.cpp
  77. 15 15
      lib/serializer/Connection.h
  78. 81 211
      server/CGameHandler.cpp
  79. 13 12
      server/CGameHandler.h
  80. 1 0
      server/CMakeLists.txt
  81. 961 680
      server/CVCMIServer.cpp
  82. 105 112
      server/CVCMIServer.h
  83. 250 0
      server/NetPacksLobbyServer.cpp
  84. 10 21
      server/NetPacksServer.cpp
  85. 1 1
      test/game/CGameStateTest.cpp

+ 4 - 4
CCallback.cpp

@@ -95,7 +95,7 @@ void CCallback::endTurn()
 {
 	logGlobal->trace("Player %d ended his turn.", player.get().getNum());
 	EndTurn pack;
-	sendRequest(&pack); //report that we ended turn
+	sendRequest(&pack);
 }
 int CCallback::swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 {
@@ -174,7 +174,7 @@ int CBattleCallback::battleMakeAction(BattleAction* action)
 	return 0;
 }
 
-int CBattleCallback::sendRequest(const CPack *request)
+int CBattleCallback::sendRequest(const CPackForServer * request)
 {
 	int requestID = cl->sendRequest(request, *player);
 	if(waitTillRealize)
@@ -262,8 +262,8 @@ void CCallback::save( const std::string &fname )
 void CCallback::sendMessage(const std::string &mess, const CGObjectInstance * currentObject)
 {
 	ASSERT_IF_CALLED_WITH_PLAYER
-	PlayerMessage pm(*player, mess, currentObject? currentObject->id : ObjectInstanceID(-1));
-	sendRequest(&(CPackForClient&)pm);
+	PlayerMessage pm(mess, currentObject? currentObject->id : ObjectInstanceID(-1));
+	sendRequest(&pm);
 }
 
 void CCallback::buildBoat( const IShipyard *obj )

+ 3 - 2
CCallback.h

@@ -78,12 +78,12 @@ public:
 	virtual void buildBoat(const IShipyard *obj) = 0;
 };
 
-struct CPack;
+struct CPackForServer;
 
 class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback
 {
 protected:
-	int sendRequest(const CPack *request); //returns requestID (that'll be matched to requestID in PackageApplied)
+	int sendRequest(const CPackForServer * request); //returns requestID (that'll be matched to requestID in PackageApplied)
 	CClient *cl;
 
 public:
@@ -95,6 +95,7 @@ public:
 	friend class CClient;
 };
 
+class CPlayerInterface;
 class CCallback : public CPlayerSpecificInfoCallback, public IGameActionCallback, public CBattleCallback
 {
 public:

+ 5 - 0
ChangeLog

@@ -20,6 +20,11 @@ GENERAL:
 - BLOCK_MAGIC_BELOW - allows blocking spells below particular spell level. HotA cape artifact can be implemented with this
 - DESTRUCTION - creature ability for killing extra units after hit, configurable
 
+MULTIPLAYER:
+* Loading support. Save from single client could be used to load all clients.
+* Restart support. All clients will restart together on same server.
+* Hotseat mixed with network game. Multiple colors can be controlled by each client.
+
 SPELLS:
 * Implemented cumulative effects for spells
 

+ 3 - 1
client/CGameInfo.cpp

@@ -14,8 +14,10 @@
 
 #include "../lib/VCMI_Lib.h"
 
-const CGameInfo * CGI; //game info for general use
+const CGameInfo * CGI;
 CClientState * CCS = nullptr;
+CServerHandler * CSH;
+
 
 CGameInfo::CGameInfo()
 {

+ 1 - 1
client/CGameInfo.h

@@ -9,7 +9,6 @@
  */
 #pragma once
 
-
 #include "../lib/ConstTransitivePtr.h"
 
 class CModHandler;
@@ -30,6 +29,7 @@ class CConsoleHandler;
 class CCursorHandler;
 class CGameState;
 class IMainVideoPlayer;
+class CServerHandler;
 
 class CMap;
 

+ 95 - 207
client/CMT.cpp

@@ -20,7 +20,8 @@
 
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/FileStream.h"
-#include "CPreGame.h"
+#include "mainmenu/CMainMenu.h"
+#include "lobby/CSelectionBase.h"
 #include "windows/CCastleInterface.h"
 #include "../lib/CConsoleHandler.h"
 #include "gui/CCursorHandler.h"
@@ -54,6 +55,12 @@
 #include "../lib/StringConstants.h"
 #include "../lib/CPlayerState.h"
 #include "gui/CAnimation.h"
+#include "../lib/serializer/Connection.h"
+#include "CServerHandler.h"
+
+#include <boost/asio.hpp>
+
+#include "mainmenu/CPrologEpilogVideo.h"
 
 #ifdef VCMI_WINDOWS
 #include "SDL_syswm.h"
@@ -75,7 +82,6 @@ namespace bfs = boost::filesystem;
 std::string NAME_AFFIX = "client";
 std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
 CGuiHandler GH;
-static CClient *client = nullptr;
 
 int preferredDriverIndex = -1;
 SDL_Window * mainWindow = nullptr;
@@ -91,7 +97,6 @@ SDL_Surface *screen = nullptr, //main screen surface
 std::queue<SDL_Event> events;
 boost::mutex eventsM;
 
-CondSh<bool> serverAlive(false);
 static po::variables_map vm;
 
 //static bool setResolution = false; //set by event handling thread after resolution is adjusted
@@ -102,9 +107,6 @@ static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayInde
 void dispose();
 void playIntro();
 static void mainLoop();
-//void requestChangingResolution();
-void startGame(StartInfo * options, CConnection *serv = nullptr);
-void endGame();
 
 #ifndef VCMI_WINDOWS
 #ifndef _GNU_SOURCE
@@ -113,54 +115,6 @@ void endGame();
 #include <getopt.h>
 #endif
 
-void startTestMap(const std::string &mapname)
-{
-	StartInfo si;
-	si.mapname = mapname;
-	si.mode = StartInfo::NEW_GAME;
-	for (int i = 0; i < 8; i++)
-	{
-		PlayerSettings &pset = si.playerInfos[PlayerColor(i)];
-		pset.color = PlayerColor(i);
-		pset.name = CGI->generaltexth->allTexts[468];//Computer
-		pset.playerID = PlayerSettings::PLAYER_AI;
-		pset.compOnly = true;
-		pset.castle = 0;
-		pset.hero = -1;
-		pset.heroPortrait = -1;
-		pset.handicap = PlayerSettings::NO_HANDICAP;
-	}
-
-	while(GH.topInt())
-		GH.popIntTotally(GH.topInt());
-	startGame(&si);
-}
-
-void startGameFromFile(const bfs::path &fname)
-{
-	StartInfo si;
-	try //attempt retrieving start info from given file
-	{
-		if(fname.empty() || !bfs::exists(fname))
-			throw std::runtime_error("Startfile \"" + fname.string() + "\" does not exist!");
-
-		CLoadFile out(fname);
-		if (!out.sfile || !*out.sfile)
-			throw std::runtime_error("Cannot read from startfile \"" + fname.string() +"\"!");
-		out >> si;
-	}
-	catch(std::exception &e)
-	{
-		logGlobal->error("Failed to start from the file: %s. Error: %s. Falling back to main menu.", fname, e.what());
-		GH.curInt = CGPreGame::create();
-		return;
-	}
-
-	while(GH.topInt())
-		GH.popIntTotally(GH.topInt());
-	startGame(&si);
-}
-
 void init()
 {
 	CStopWatch tmh, pomtime;
@@ -243,15 +197,15 @@ int main(int argc, char * argv[])
 		("version,v", "display version information and exit")
 		("disable-shm", "force disable shared memory usage")
 		("enable-shm-uuid", "use UUID for shared memory identifier")
-		("start", po::value<bfs::path>(), "starts game from saved StartInfo file")
 		("testmap", po::value<std::string>(), "")
+		("testsave", po::value<std::string>(), "")
 		("spectate,s", "enable spectator interface for AI-only games")
 		("spectate-ignore-hero", "wont follow heroes on adventure map")
 		("spectate-hero-speed", po::value<int>(), "hero movement speed on adventure map")
 		("spectate-battle-speed", po::value<int>(), "battle animation speed for spectator")
 		("spectate-skip-battle", "skip battles in spectator view")
 		("spectate-skip-battle-result", "skip battle result window")
-		("onlyAI", "runs without human player, all players will be default AI")
+		("onlyAI", "allow to run without human player, all players will be default AI")
 		("headless", "runs without GUI, implies --onlyAI")
 		("ai", po::value<std::vector<std::string>>(), "AI to be used for the player, can be specified several times for the consecutive players")
 		("oneGoodAI", "puts one default AI and the rest will be EmptyAI")
@@ -259,12 +213,6 @@ int main(int argc, char * argv[])
 		("disable-video", "disable video player")
 		("nointro,i", "skips intro movies")
 		("donotstartserver,d","do not attempt to start server and just connect to it instead server")
-        ("loadserver","specifies we are the multiplayer server for loaded games")
-        ("loadnumplayers",po::value<int>(),"specifies the number of players connecting to a multiplayer game")
-        ("loadhumanplayerindices",po::value<std::vector<int>>(),"Indexes of human players (0=Red, etc.)")
-        ("loadplayer", po::value<int>(),"specifies which player we are in multiplayer loaded games (0=Red, etc.)")
-        ("loadserverip",po::value<std::string>(),"IP for loaded game server")
-		("loadserverport",po::value<std::string>(),"port for loaded game server")
 		("serverport", po::value<si64>(), "override port specified in config file")
 		("saveprefix", po::value<std::string>(), "prefix for auto save files")
 		("savefrequency", po::value<si64>(), "limit auto save creation to each N days");
@@ -317,6 +265,17 @@ int main(int argc, char * argv[])
 		session["headless"].Bool() = true;
 		session["onlyai"].Bool() = true;
 	}
+	else if(vm.count("spectate"))
+	{
+		session["spectate"].Bool() = true;
+		session["spectate-ignore-hero"].Bool() = vm.count("spectate-ignore-hero");
+		session["spectate-skip-battle"].Bool() = vm.count("spectate-skip-battle");
+		session["spectate-skip-battle-result"].Bool() = vm.count("spectate-skip-battle-result");
+		if(vm.count("spectate-hero-speed"))
+			session["spectate-hero-speed"].Integer() = vm["spectate-hero-speed"].as<int>();
+		if(vm.count("spectate-battle-speed"))
+			session["spectate-battle-speed"].Float() = vm["spectate-battle-speed"].as<int>();
+	}
 	// Server settings
 	session["donotstartserver"].Bool() = vm.count("donotstartserver");
 
@@ -441,6 +400,7 @@ int main(int argc, char * argv[])
 
 	CCS = new CClientState();
 	CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
+	CSH = new CServerHandler();
 	// Initialize video
 #ifdef DISABLE_VIDEO
 	CCS->videoh = new CEmptyVideoPlayer();
@@ -453,15 +413,17 @@ int main(int argc, char * argv[])
 
 	logGlobal->info("\tInitializing video: %d ms", pomtime.getDiff());
 
-	//initializing audio
-	CCS->soundh = new CSoundHandler();
-	CCS->soundh->init();
-	CCS->soundh->setVolume(settings["general"]["sound"].Float());
-	CCS->musich = new CMusicHandler();
-	CCS->musich->init();
-	CCS->musich->setVolume(settings["general"]["music"].Float());
-	logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
-
+	if(!settings["session"]["headless"].Bool())
+	{
+		//initializing audio
+		CCS->soundh = new CSoundHandler();
+		CCS->soundh->init();
+		CCS->soundh->setVolume(settings["general"]["sound"].Float());
+		CCS->musich = new CMusicHandler();
+		CCS->musich->init();
+		CCS->musich->setVolume(settings["general"]["music"].Float());
+		logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
+	}
 #ifdef __APPLE__
 	// Ctrl+click should be treated as a right click on Mac OS X
 	SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
@@ -500,41 +462,21 @@ int main(int argc, char * argv[])
 	session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
 	session["aiSolo"].Bool() = false;
 
-	bfs::path fileToStartFrom; //none by default
-	if(vm.count("start"))
-		fileToStartFrom = vm["start"].as<bfs::path>();
 	if(vm.count("testmap"))
 	{
 		session["testmap"].String() = vm["testmap"].as<std::string>();
+		session["onlyai"].Bool() = true;
+		boost::thread(&CServerHandler::debugStartTest, CSH, session["testmap"].String(), false);
 	}
-
-	session["spectate"].Bool() = vm.count("spectate");
-	if(session["spectate"].Bool())
-	{
-		session["spectate-ignore-hero"].Bool() = vm.count("spectate-ignore-hero");
-		session["spectate-skip-battle"].Bool() = vm.count("spectate-skip-battle");
-		session["spectate-skip-battle-result"].Bool() = vm.count("spectate-skip-battle-result");
-		if(vm.count("spectate-hero-speed"))
-			session["spectate-hero-speed"].Integer() = vm["spectate-hero-speed"].as<int>();
-		if(vm.count("spectate-battle-speed"))
-			session["spectate-battle-speed"].Float() = vm["spectate-battle-speed"].as<int>();
-	}
-	if(!session["testmap"].isNull())
+	else if(vm.count("testsave"))
 	{
-		startTestMap(session["testmap"].String());
+		session["testsave"].String() = vm["testsave"].as<std::string>();
+		session["onlyai"].Bool() = true;
+		boost::thread(&CServerHandler::debugStartTest, CSH, session["testsave"].String(), true);
 	}
 	else
 	{
-		if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom))
-			startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file
-		else
-		{
-			if(!fileToStartFrom.empty())
-			{
-				logGlobal->warn("Warning: cannot find given file to start from (%s). Falling back to main menu.", fileToStartFrom.string());
-			}
-			GH.curInt = CGPreGame::create(); //will set CGP pointer to itself
-		}
+		GH.curInt = CMainMenu::create();
 	}
 
 	if(!settings["session"]["headless"].Bool())
@@ -614,8 +556,8 @@ void processCommand(const std::string &message)
 		}
 		else
 		{
-			if(client && client->erm)
-				client->erm->executeUserCommand(message);
+			if(CSH->client && CSH->client->erm)
+				CSH->client->erm->executeUserCommand(message);
 			std::cout << "erm>";
 		}
 	}
@@ -665,21 +607,21 @@ void processCommand(const std::string &message)
 	}
 	else if(cn=="save")
 	{
-		if(!client)
+		if(!CSH->client)
 		{
 			std::cout << "Game in not active";
 			return;
 		}
 		std::string fname;
 		readed >> fname;
-		client->save(fname);
+		CSH->client->save(fname);
 	}
 //	else if(cn=="load")
 //	{
 //		// TODO: this code should end the running game and manage to call startGame instead
 //		std::string fname;
 //		readed >> fname;
-//		client->loadGame(fname);
+//		CSH->client->loadGame(fname);
 //	}
 	else if(message=="convert txt")
 	{
@@ -861,22 +803,6 @@ void processCommand(const std::string &message)
 			logGlobal->info("Option %s disabled!", what);
 		}
 	}
-	else if(cn == "sinfo")
-	{
-		std::string fname;
-		readed >> fname;
-		if(fname.size() && SEL)
-		{
-			CSaveFile out(fname);
-			out << SEL->sInfo;
-		}
-	}
-	else if(cn == "start")
-	{
-		std::string fname;
-		readed >> fname;
-		startGameFromFile(fname);
-	}
 	else if(cn == "unlock")
 	{
 		std::string mxname;
@@ -935,8 +861,8 @@ void processCommand(const std::string &message)
 	{
 		YourTurn yt;
 		yt.player = player;
-		yt.daysWithoutCastle = client->getPlayer(player)->daysWithoutCastle;
-		yt.applyCl(client);
+		yt.daysWithoutCastle = CSH->client->getPlayer(player)->daysWithoutCastle;
+		yt.applyCl(CSH->client);
 	};
 
 	Settings session = settings.write["session"];
@@ -947,7 +873,7 @@ void processCommand(const std::string &message)
 	else if(cn == "gosolo")
 	{
 		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-		if(!client)
+		if(!CSH->client)
 		{
 			std::cout << "Game in not active";
 			return;
@@ -955,23 +881,23 @@ void processCommand(const std::string &message)
 		PlayerColor color;
 		if(session["aiSolo"].Bool())
 		{
-			for(auto & elem : client->gameState()->players)
+			for(auto & elem : CSH->client->gameState()->players)
 			{
 				if(elem.second.human)
-					client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
+					CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
 			}
 		}
 		else
 		{
 			color = LOCPLINT->playerID;
 			removeGUI();
-			for(auto & elem : client->gameState()->players)
+			for(auto & elem : CSH->client->gameState()->players)
 			{
 				if(elem.second.human)
 				{
-					auto AiToGive = client->aiNameForPlayer(*client->getPlayerSettings(elem.first), false);
+					auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false);
 					logNetwork->info("Player %s will be lead by %s", elem.first, AiToGive);
-					client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
+					CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
 				}
 			}
 			GH.totalRedraw();
@@ -986,7 +912,7 @@ void processCommand(const std::string &message)
 		boost::to_lower(colorName);
 
 		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-		if(!client)
+		if(!CSH->client)
 		{
 			std::cout << "Game in not active";
 			return;
@@ -994,7 +920,7 @@ void processCommand(const std::string &message)
 		PlayerColor color;
 		if(LOCPLINT)
 			color = LOCPLINT->playerID;
-		for(auto & elem : client->gameState()->players)
+		for(auto & elem : CSH->client->gameState()->players)
 		{
 			if(elem.second.human || (colorName.length() &&
 				elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName)))
@@ -1003,7 +929,7 @@ void processCommand(const std::string &message)
 			}
 
 			removeGUI();
-			client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
+			CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
 		}
 		GH.totalRedraw();
 		if(color != PlayerColor::NEUTRAL)
@@ -1290,46 +1216,59 @@ static void handleEvent(SDL_Event & ev)
 	{
 		switch(ev.user.code)
 		{
-		case FORCE_QUIT:
+		case EUserEvent::FORCE_QUIT:
 			{
 				handleQuit(false);
 				return;
 			}
 		    break;
-		case RETURN_TO_MAIN_MENU:
+		case EUserEvent::RETURN_TO_MAIN_MENU:
 			{
-				endGame();
-				GH.curInt = CGPreGame::create();
+				CSH->endGameplay();
+				GH.curInt = CMainMenu::create();
 				GH.defActionsDef = 63;
 			}
 			break;
-		case RESTART_GAME:
+		case EUserEvent::RESTART_GAME:
 			{
-				StartInfo si = *client->getStartInfo(true);
-				si.seedToBeUsed = 0; //server gives new random generator seed if 0
-				endGame();
-				startGame(&si);
+				CSH->sendStartGame();
 			}
 			break;
-		case PREPARE_RESTART_CAMPAIGN:
+		case EUserEvent::CAMPAIGN_START_SCENARIO:
 			{
-				auto si = reinterpret_cast<StartInfo *>(ev.user.data1);
-				endGame();
-				startGame(si);
+				CSH->endGameplay();
+				GH.curInt = CMainMenu::create();
+				auto ourCampaign = std::shared_ptr<CCampaignState>(reinterpret_cast<CCampaignState *>(ev.user.data1));
+				auto & epilogue = ourCampaign->camp->scenarios[ourCampaign->mapsConquered.back()].epilog;
+				auto finisher = [=]()
+				{
+					if(ourCampaign->mapsRemaining.size())
+					{
+						CMM->openCampaignLobby(ourCampaign);
+					}
+				};
+				if(epilogue.hasPrologEpilog)
+				{
+					GH.pushInt(new CPrologEpilogVideo(epilogue, finisher));
+				}
+				else
+				{
+					finisher();
+				}
 			}
 			break;
-		case RETURN_TO_MENU_LOAD:
-			endGame();
-			CGPreGame::create();
+		case EUserEvent::RETURN_TO_MENU_LOAD:
+			CSH->endGameplay();
+			CMainMenu::create();
 			GH.defActionsDef = 63;
-			CGP->update();
-			CGP->menu->switchToTab(vstd::find_pos(CGP->menu->menuNameToEntry, "load"));
-			GH.curInt = CGP;
+			CMM->update();
+			CMM->menu->switchToTab(vstd::find_pos(CMM->menu->menuNameToEntry, "load"));
+			GH.curInt = CMM;
 			break;
-		case FULLSCREEN_TOGGLED:
+		case EUserEvent::FULLSCREEN_TOGGLED:
 			fullScreenChanged();
 			break;
-		case INTERFACE_CHANGED:
+		case EUserEvent::INTERFACE_CHANGED:
 			if(LOCPLINT)
 				LOCPLINT->updateAmbientSounds();
 			break;
@@ -1360,7 +1299,7 @@ static void handleEvent(SDL_Event & ev)
 static void mainLoop()
 {
 	SettingsListener resChanged = settings.listen["video"]["fullscreen"];
-	resChanged([](const JsonNode &newState){  CGuiHandler::pushSDLEvent(SDL_USEREVENT, FULLSCREEN_TOGGLED); });
+	resChanged([](const JsonNode &newState){  CGuiHandler::pushSDLEvent(SDL_USEREVENT, EUserEvent::FULLSCREEN_TOGGLED); });
 
 	inGuiThread.reset(new bool(true));
 	GH.mainFPSmng->init();
@@ -1374,69 +1313,18 @@ static void mainLoop()
 			handleEvent(ev);
 		}
 
+		CSH->applyPacksOnLobbyScreen();
 		GH.renderFrame();
 
 	}
 }
 
-void startGame(StartInfo * options, CConnection *serv)
-{
-	if(!settings["session"]["donotstartserver"].Bool())
-	{
-		serverAlive.waitWhileTrue();
-		serverAlive.setn(true);
-	}
-
-	if(settings["session"]["onlyai"].Bool())
-	{
-		auto ais = vm.count("ai") ? vm["ai"].as<std::vector<std::string>>() : std::vector<std::string>();
-
-		int i = 0;
-
-
-		for(auto & elem : options->playerInfos)
-		{
-			elem.second.playerID = PlayerSettings::PLAYER_AI;
-			if(i < ais.size())
-				elem.second.name = ais[i++];
-		}
-	}
-
-    client = new CClient();
-	CPlayerInterface::howManyPeople = 0;
-	switch(options->mode) //new game
-	{
-	case StartInfo::NEW_GAME:
-	case StartInfo::CAMPAIGN:
-		client->newGame(serv, options);
-		break;
-	case StartInfo::LOAD_GAME:
-		std::string fname = options->mapname;
-		boost::algorithm::erase_last(fname,".vlgm1");
-        if(!vm.count("loadplayer"))
-            client->loadGame(fname);
-        else
-			client->loadGame(fname,vm.count("loadserver"),vm.count("loadhumanplayerindices") ? vm["loadhumanplayerindices"].as<std::vector<int>>() : std::vector<int>(),vm.count("loadnumplayers") ? vm["loadnumplayers"].as<int>() : 1,vm["loadplayer"].as<int>(),vm.count("loadserverip") ? vm["loadserverip"].as<std::string>() : "", vm.count("loadserverport") ? vm["loadserverport"].as<ui16>() : CServerHandler::getDefaultPort());
-		break;
-	}
-	{
-		TLockGuard _(client->connectionHandlerMutex);
-		client->connectionHandler = make_unique<boost::thread>(&CClient::run, client);
-	}
-}
-
-void endGame()
-{
-	client->endGame();
-	vstd::clear_pointer(client);
-}
-
 void handleQuit(bool ask)
 {
 	auto quitApplication = []()
 	{
-		if(client)
-			endGame();
+		if(CSH->client)
+			CSH->endGameplay();
 		dispose();
 		vstd::clear_pointer(console);
 		boost::this_thread::sleep(boost::posix_time::milliseconds(750));
@@ -1450,7 +1338,7 @@ void handleQuit(bool ask)
 		exit(0);
 	};
 
-	if(client && LOCPLINT && ask)
+	if(CSH->client && LOCPLINT && ask)
 	{
 		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
 		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, 0);

+ 0 - 3
client/CMT.h

@@ -9,7 +9,6 @@
  */
 #pragma once
 #include <SDL_render.h>
-#include "../lib/CondSh.h"
 
 extern SDL_Texture * screenTexture;
 
@@ -20,7 +19,5 @@ extern SDL_Surface *screen;      // main screen surface
 extern SDL_Surface *screen2;     // and hlp surface (used to store not-active interfaces layer)
 extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
 
-extern CondSh<bool> serverAlive; //used to prevent game start from executing if server is already running
-
 void removeGUI();
 void handleQuit(bool ask = true);

+ 31 - 2
client/CMakeLists.txt

@@ -43,6 +43,20 @@ set(client_SRCS
 		windows/InfoWindows.cpp
 		windows/QuickRecruitmentWindow.cpp
 
+		mainmenu/CMainMenu.cpp
+		mainmenu/CCampaignScreen.cpp
+		mainmenu/CreditsScreen.cpp
+		mainmenu/CPrologEpilogVideo.cpp
+
+		lobby/CBonusSelection.cpp
+		lobby/CSelectionBase.cpp
+		lobby/CLobbyScreen.cpp
+		lobby/CSavingScreen.cpp
+		lobby/CScenarioInfoScreen.cpp
+		lobby/OptionsTab.cpp
+		lobby/RandomMapTab.cpp
+		lobby/SelectionTab.cpp
+
 		CBitmapHandler.cpp
 		CreatureCostBox.cpp
 		CGameInfo.cpp
@@ -51,11 +65,12 @@ set(client_SRCS
 		CMT.cpp
 		CMusicHandler.cpp
 		CPlayerInterface.cpp
-		CPreGame.cpp
 		CVideoHandler.cpp
+		CServerHandler.cpp
 		Graphics.cpp
 		mapHandler.cpp
 		NetPacksClient.cpp
+		NetPacksLobbyClient.cpp
 		SDLRWwrapper.cpp
 )
 
@@ -100,6 +115,20 @@ set(client_HEADERS
 		windows/InfoWindows.h
 		windows/QuickRecruitmentWindow.h
 
+		mainmenu/CMainMenu.h
+		mainmenu/CCampaignScreen.h
+		mainmenu/CreditsScreen.h
+		mainmenu/CPrologEpilogVideo.h
+
+		lobby/CBonusSelection.h
+		lobby/CSelectionBase.h
+		lobby/CLobbyScreen.h
+		lobby/CSavingScreen.h
+		lobby/CScenarioInfoScreen.h
+		lobby/OptionsTab.h
+		lobby/RandomMapTab.h
+		lobby/SelectionTab.h
+
 		CBitmapHandler.h
 		CreatureCostBox.h
 		CGameInfo.h
@@ -108,8 +137,8 @@ set(client_HEADERS
 		CMT.h
 		CMusicHandler.h
 		CPlayerInterface.h
-		CPreGame.h
 		CVideoHandler.h
+		CServerHandler.h
 		Graphics.h
 		mapHandler.h
 		resource.h

+ 36 - 71
client/CPlayerInterface.cpp

@@ -49,13 +49,16 @@
 #include "mapHandler.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/StartInfo.h"
-#include "../lib/CGameState.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/GameConstants.h"
 #include "gui/CGuiHandler.h"
 #include "windows/InfoWindows.h"
 #include "../lib/UnlockGuard.h"
+#include "../lib/CPathfinder.h"
 #include <SDL.h>
+#include "CServerHandler.h"
+// FIXME: only needed for CGameState::mutex
+#include "../lib/CGameState.h"
 
 
 // The macro below is used to mark functions that are called by client when game state changes.
@@ -90,8 +93,6 @@ CBattleInterface * CPlayerInterface::battleInt;
 enum  EMoveState {STOP_MOVE, WAITING_MOVE, CONTINUE_MOVE, DURING_MOVE};
 CondSh<EMoveState> stillMoveHero(STOP_MOVE); //used during hero movement
 
-int CPlayerInterface::howManyPeople = 0;
-
 static bool objectBlitOrderSorter(const TerrainTileObject  & a, const TerrainTileObject & b)
 {
 	return CMapHandler::compareObjectBlitOrder(a.obj, b.obj);
@@ -114,7 +115,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
 	logGlobal->trace("\tHuman player interface for player %s being constructed", Player.getStr());
 	destinationTeleport = ObjectInstanceID();
 	destinationTeleportPos = int3(-1);
-	howManyPeople++;
 	GH.defActionsDef = 0;
 	LOCPLINT = this;
 	curAction = nullptr;
@@ -139,7 +139,6 @@ CPlayerInterface::~CPlayerInterface()
 {
 	CCS->soundh->ambientStopAllChannels();
 	logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.getStr());
-	//howManyPeople--;
 	delete showingDialog;
 	delete cingconsole;
 	if (LOCPLINT == this)
@@ -148,9 +147,7 @@ CPlayerInterface::~CPlayerInterface()
 void CPlayerInterface::init(std::shared_ptr<CCallback> CB)
 {
 	cb = CB;
-
-	if (!towns.size() && !wanderingHeroes.size())
-		initializeHeroTownList();
+	initializeHeroTownList();
 
 	// always recreate advmap interface to avoid possible memory-corruption bugs
 	if (adventureInt)
@@ -170,7 +167,7 @@ void CPlayerInterface::yourTurn()
 		std::string prefix = settings["session"]["saveprefix"].String();
 		if (firstCall)
 		{
-			if (howManyPeople == 1)
+			if(CSH->howManyPlayerInterfaces() == 1)
 				adventureInt->setPlayer(playerID);
 
 			autosaveCount = getLastIndex(prefix + "Autosave_");
@@ -192,7 +189,7 @@ void CPlayerInterface::yourTurn()
 		if (adventureInt->player != playerID)
 			adventureInt->setPlayer(playerID);
 
-		if (howManyPeople > 1) //hot seat message
+		if (CSH->howManyPlayerInterfaces() > 1) //hot seat message
 		{
 			adventureInt->startHotSeatWait(playerID);
 
@@ -1581,45 +1578,20 @@ void CPlayerInterface::objectPropertyChanged(const SetObjectProperty * sop)
 
 void CPlayerInterface::initializeHeroTownList()
 {
-	std::vector<const CGHeroInstance*> allHeroes = cb->getHeroesInfo();
-	/*
-	std::vector <const CGHeroInstance *> newWanderingHeroes;
-
-	//applying current heroes order to new heroes info
-	int j;
-	for (int i = 0; i < wanderingHeroes.size(); i++)
-		if ((j = vstd::find_pos(allHeroes, wanderingHeroes[i])) >= 0)
-			if (!allHeroes[j]->inTownGarrison)
-			{
-				newWanderingHeroes += allHeroes[j];
-				allHeroes -= allHeroes[j];
-			}
-	//all the rest of new heroes go the end of the list
-	wanderingHeroes.clear();
-	wanderingHeroes = newWanderingHeroes;
-	newWanderingHeroes.clear();*/
-
-	for (auto & allHeroe : allHeroes)
-		if (!allHeroe->inTownGarrison)
-			wanderingHeroes.push_back(allHeroe);
-
-	std::vector<const CGTownInstance*> allTowns = cb->getTownsInfo();
-	/*
-	std::vector<const CGTownInstance*> newTowns;
-	for (int i = 0; i < towns.size(); i++)
-		if ((j = vstd::find_pos(allTowns, towns[i])) >= 0)
+	if(!wanderingHeroes.size())
+	{
+		std::vector<const CGHeroInstance*> heroes = cb->getHeroesInfo();
+		for(auto & hero : heroes)
 		{
-			newTowns += allTowns[j];
-			allTowns -= allTowns[j];
+			if(!hero->inTownGarrison)
+				wanderingHeroes.push_back(hero);
 		}
+	}
 
-	towns.clear();
-	towns = newTowns;
-	newTowns.clear();*/
-	for (auto & allTown : allTowns)
-		towns.push_back(allTown);
+	if(!towns.size())
+		towns = cb->getTownsInfo();
 
-	if (adventureInt)
+	if(adventureInt)
 		adventureInt->updateNextHero(nullptr);
 }
 
@@ -1728,7 +1700,7 @@ void CPlayerInterface::update()
 		return;
 
 	//if there are any waiting dialogs, show them
-	if ((howManyPeople <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get())
+	if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get())
 	{
 		showingDialog->set(true);
 		GH.pushInt(dialogs.front());
@@ -2227,37 +2199,27 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
 			waitForAllDialogs(); //wait till all dialogs are displayed and closed
 		}
 
-		--howManyPeople;
-
-		if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated
+		if(CSH->howManyPlayerInterfaces() == 1 && !settings["session"]["spectate"].Bool()) //all human players eliminated
 		{
-			if (adventureInt)
+			if(adventureInt)
 			{
 				GH.terminate_cond->setn(true);
 				adventureInt->deactivate();
 				if (GH.topInt() == adventureInt)
 					GH.popInt(adventureInt);
-				delete adventureInt;
-				adventureInt = nullptr;
+				vstd::clear_pointer(adventureInt);
 			}
 		}
 
-		if (cb->getStartInfo()->mode == StartInfo::CAMPAIGN)
+		if (victoryLossCheckResult.victory() && LOCPLINT == this)
 		{
-			// if you lose the campaign go back to the main menu
-			// campaign wins are handled in proposeNextMission
-			if (victoryLossCheckResult.loss()) requestReturningToMainMenu();
+			// end game if current human player has won
+			requestReturningToMainMenu(true);
 		}
-		else
+		else if(CSH->howManyPlayerInterfaces() == 1 && !settings["session"]["spectate"].Bool())
 		{
-			if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated
-			{
-				requestReturningToMainMenu();
-			}
-			else if (victoryLossCheckResult.victory() && LOCPLINT == this) // end game if current human player has won
-			{
-				requestReturningToMainMenu();
-			}
+			//all human players eliminated
+			requestReturningToMainMenu(false);
 		}
 
 		if (GH.curInt == this) GH.curInt = nullptr;
@@ -2378,7 +2340,7 @@ void CPlayerInterface::acceptTurn()
 	}
 	waitWhileDialog();
 
-	if (howManyPeople > 1)
+	if(CSH->howManyPlayerInterfaces() > 1)
 		adventureInt->startTurn();
 
 	adventureInt->heroList.update();
@@ -2573,11 +2535,14 @@ void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj)
 		showShipyardDialog(obj);
 }
 
-void CPlayerInterface::requestReturningToMainMenu()
+void CPlayerInterface::requestReturningToMainMenu(bool won)
 {
-	sendCustomEvent(RETURN_TO_MAIN_MENU);
+	CSH->state = EClientState::DISCONNECTING;
 	CCS->soundh->ambientStopAllChannels();
-	cb->unregisterAllInterfaces();
+	if(won && cb->getStartInfo()->campState)
+		CSH->startCampaignScenario(cb->getStartInfo()->campState);
+	else
+		sendCustomEvent(EUserEvent::RETURN_TO_MAIN_MENU);
 }
 
 void CPlayerInterface::sendCustomEvent( int code )
@@ -2672,7 +2637,7 @@ void CPlayerInterface::playerStartsTurn(PlayerColor player)
 			GH.popInts(1);
 	}
 
-	if (howManyPeople == 1)
+	if(CSH->howManyPlayerInterfaces() == 1)
 	{
 		GH.curInt = this;
 		adventureInt->startTurn();
@@ -2696,7 +2661,7 @@ void CPlayerInterface::waitForAllDialogs(bool unlockPim)
 
 void CPlayerInterface::proposeLoadingGame()
 {
-	showYesNoDialog(CGI->generaltexth->allTexts[68], [this](){ sendCustomEvent(RETURN_TO_MENU_LOAD); }, 0, false);
+	showYesNoDialog(CGI->generaltexth->allTexts[68], [this](){ sendCustomEvent(EUserEvent::RETURN_TO_MENU_LOAD); }, 0, false);
 }
 
 CPlayerInterface::SpellbookLastSetting::SpellbookLastSetting()

+ 1 - 15
client/CPlayerInterface.h

@@ -59,19 +59,6 @@ namespace boost
 	class recursive_mutex;
 }
 
-enum
-{
-	/*CHANGE_SCREEN_RESOLUTION = 1,*/
-	RETURN_TO_MAIN_MENU = 2,
-	//STOP_CLIENT = 3,
-	RESTART_GAME = 4,
-	RETURN_TO_MENU_LOAD,
-	FULLSCREEN_TOGGLED,
-	PREPARE_RESTART_CAMPAIGN,
-	FORCE_QUIT, //quit client without question
-	INTERFACE_CHANGED
-};
-
 /// Central class for managing user interface logic
 class CPlayerInterface : public CGameInterface, public IUpdateable
 {
@@ -88,7 +75,6 @@ public:
 	int firstCall; // -1 - just loaded game; 1 - just started game; 0 otherwise
 	int autosaveCount;
 	static const int SAVES_COUNT = 5;
-	static int howManyPeople;
 
 	CCastleInterface * castleInt; //nullptr if castle window isn't opened
 	static CBattleInterface * battleInt; //nullptr if no battle
@@ -247,7 +233,7 @@ public:
 	void acceptTurn(); //used during hot seat after your turn message is close
 	void tryDiggging(const CGHeroInstance *h);
 	void showShipyardDialogOrProblemPopup(const IShipyard *obj); //obj may be town or shipyard;
-	void requestReturningToMainMenu();
+	void requestReturningToMainMenu(bool won);
 	void sendCustomEvent(int code);
 	void proposeLoadingGame();
 

+ 0 - 4374
client/CPreGame.cpp

@@ -1,4374 +0,0 @@
-/*
- * CPreGame.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 "CPreGame.h"
-
-#include "../lib/filesystem/Filesystem.h"
-#include "../lib/filesystem/CCompressedStream.h"
-
-#include "../lib/CStopWatch.h"
-#include "gui/SDL_Extensions.h"
-#include "CGameInfo.h"
-#include "gui/CCursorHandler.h"
-#include "../lib/CGeneralTextHandler.h"
-#include "../lib/CSkillHandler.h"
-#include "../lib/CTownHandler.h"
-#include "../lib/CHeroHandler.h"
-#include "../lib/mapping/CCampaignHandler.h"
-#include "../lib/CCreatureHandler.h"
-#include "../lib/JsonNode.h"
-#include "CMusicHandler.h"
-#include "CVideoHandler.h"
-#include "Graphics.h"
-#include "../lib/serializer/Connection.h"
-#include "../lib/serializer/CTypeList.h"
-#include "../lib/VCMIDirs.h"
-#include "../lib/mapping/CMap.h"
-#include "windows/GUIClasses.h"
-#include "CPlayerInterface.h"
-#include "../CCallback.h"
-#include "CMessage.h"
-#include "../lib/spells/CSpellHandler.h" /*for campaign bonuses*/
-#include "../lib/CArtHandler.h" /*for campaign bonuses*/
-#include "../lib/CBuildingHandler.h" /*for campaign bonuses*/
-#include "CBitmapHandler.h"
-#include "Client.h"
-#include "../lib/NetPacks.h"
-#include "../lib/registerTypes//RegisterTypes.h"
-#include "../lib/CThreadHelper.h"
-#include "../lib/CConfigHandler.h"
-#include "../lib/GameConstants.h"
-#include "gui/CGuiHandler.h"
-#include "gui/CAnimation.h"
-#include "widgets/CComponent.h"
-#include "widgets/Buttons.h"
-#include "widgets/MiscWidgets.h"
-#include "widgets/ObjectLists.h"
-#include "widgets/TextControls.h"
-#include "windows/InfoWindows.h"
-#include "../lib/mapping/CMapService.h"
-#include "../lib/CRandomGenerator.h"
-#include "../lib/CondSh.h"
-
-namespace fs = boost::filesystem;
-
-void startGame(StartInfo * options, CConnection *serv = nullptr);
-void endGame();
-
-CGPreGame * CGP = nullptr;
-ISelectionScreenInfo *SEL;
-
-static PlayerColor playerColor; //if more than one player - applies to the first
-
-/**
- * Stores the current name of the savegame.
- *
- * TODO better solution for auto-selection when saving already saved games.
- * -> CSelectionScreen should be divided into CLoadGameScreen, CSaveGameScreen,...
- * The name of the savegame can then be stored non-statically in CGameState and
- * passed separately to CSaveGameScreen.
- */
-static std::string saveGameName;
-
-struct EvilHlpStruct
-{
-	CConnection *serv;
-	StartInfo *sInfo;
-
-	void reset()
-	{
-//		vstd::clear_pointer(serv);
-		vstd::clear_pointer(sInfo);
-	}
-
-} startingInfo;
-
-static void do_quit()
-{
-	SDL_Event event;
-	event.quit.type = SDL_QUIT;
-	SDL_PushEvent(&event);
-}
-
-static CMapInfo *mapInfoFromGame()
-{
-	auto   ret = new CMapInfo();
-	ret->mapHeader = std::unique_ptr<CMapHeader>(new CMapHeader(*LOCPLINT->cb->getMapHeader()));
-	return ret;
-}
-
-static void setPlayersFromGame()
-{
-	playerColor = LOCPLINT->playerID;
-}
-
-static void swapPlayers(PlayerSettings &a, PlayerSettings &b)
-{
-	std::swap(a.playerID, b.playerID);
-	std::swap(a.name, b.name);
-
-	if(a.playerID == 1)
-		playerColor = a.color;
-	else if(b.playerID == 1)
-		playerColor = b.color;
-}
-
-void setPlayer(PlayerSettings &pset, ui8 player, const std::map<ui8, std::string> &playerNames)
-{
-	if(vstd::contains(playerNames, player))
-		pset.name = playerNames.find(player)->second;
-	else
-		pset.name = CGI->generaltexth->allTexts[468];//Computer
-
-	pset.playerID = player;
-	if(player == playerNames.begin()->first)
-		playerColor = pset.color;
-}
-
-void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader, const std::map<ui8, std::string> &playerNames)
-{
-	sInfo.playerInfos.clear();
-	if(!mapHeader.get())
-	{
-		return;
-	}
-
-	sInfo.mapname = filename;
-	playerColor = PlayerColor::NEUTRAL;
-
-	auto namesIt = playerNames.cbegin();
-
-	for (int i = 0; i < mapHeader->players.size(); i++)
-	{
-		const PlayerInfo &pinfo = mapHeader->players[i];
-
-		//neither computer nor human can play - no player
-		if (!(pinfo.canHumanPlay || pinfo.canComputerPlay))
-			continue;
-
-		PlayerSettings &pset = sInfo.playerInfos[PlayerColor(i)];
-		pset.color = PlayerColor(i);
-		if(pinfo.canHumanPlay && namesIt != playerNames.cend())
-		{
-			setPlayer(pset, namesIt++->first, playerNames);
-		}
-		else
-		{
-			setPlayer(pset, 0, playerNames);
-			if(!pinfo.canHumanPlay)
-			{
-				pset.compOnly = true;
-			}
-		}
-
-		pset.castle = pinfo.defaultCastle();
-		pset.hero = pinfo.defaultHero();
-
-		if(pset.hero != PlayerSettings::RANDOM && pinfo.hasCustomMainHero())
-		{
-			pset.hero = pinfo.mainCustomHeroId;
-			pset.heroName = pinfo.mainCustomHeroName;
-			pset.heroPortrait = pinfo.mainCustomHeroPortrait;
-		}
-
-		pset.handicap = PlayerSettings::NO_HANDICAP;
-	}
-}
-
-template <typename T> class CApplyOnPG;
-
-class CBaseForPGApply
-{
-public:
-	virtual void applyOnPG(CSelectionScreen *selScr, void *pack) const =0;
-	virtual ~CBaseForPGApply(){};
-	template<typename U> static CBaseForPGApply *getApplier(const U * t=nullptr)
-	{
-		return new CApplyOnPG<U>();
-	}
-};
-
-template <typename T> class CApplyOnPG : public CBaseForPGApply
-{
-public:
-	void applyOnPG(CSelectionScreen *selScr, void *pack) const override
-	{
-		T *ptr = static_cast<T*>(pack);
-		ptr->apply(selScr);
-	}
-};
-
-template <> class CApplyOnPG<CPack> : public CBaseForPGApply
-{
-public:
-	void applyOnPG(CSelectionScreen *selScr, void *pack) const override
-	{
-			logGlobal->error("Cannot apply on PG plain CPack!");
-			assert(0);
-	}
-};
-
-static CApplier<CBaseForPGApply> *applier = nullptr;
-
-static CPicture* createPicture(const JsonNode& config)
-{
-	return new CPicture(config["name"].String(), config["x"].Float(), config["y"].Float());
-}
-
-CMenuScreen::CMenuScreen(const JsonNode& configNode):
-	config(configNode)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	background = new CPicture(config["background"].String());
-	if (config["scalable"].Bool())
-	{
-		if (background->bg->format->palette)
-			background->convertToScreenBPP();
-		background->scaleTo(Point(screen->w, screen->h));
-	}
-
-	pos = background->center();
-
-	for(const JsonNode& node : config["items"].Vector())
-		menuNameToEntry.push_back(node["name"].String());
-
-	for(const JsonNode& node : config["images"].Vector())
-		images.push_back(createPicture(node));
-
-	//Hardcoded entry
-	menuNameToEntry.push_back("credits");
-
-    tabs = new CTabbedInt(std::bind(&CMenuScreen::createTab, this, _1), CTabbedInt::DestroyFunc());
-	tabs->type |= REDRAW_PARENT;
-}
-
-CIntObject * CMenuScreen::createTab(size_t index)
-{
-	if (config["items"].Vector().size() == index)
-		return new CreditsScreen();
-
-	return new CMenuEntry(this, config["items"].Vector()[index]);
-}
-
-void CMenuScreen::showAll(SDL_Surface * to)
-{
-	CIntObject::showAll(to);
-
-	if (pos.h != to->h || pos.w != to->w)
-		CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15);
-
-}
-
-void CMenuScreen::show(SDL_Surface * to)
-{
-	if (!config["video"].isNull())
-		CCS->videoh->update(config["video"]["x"].Float() + pos.x, config["video"]["y"].Float() + pos.y, to, true, false);
-	CIntObject::show(to);
-}
-
-void CMenuScreen::activate()
-{
-	CCS->musich->playMusic("Music/MainMenu", true);
-	if (!config["video"].isNull())
-		CCS->videoh->open(config["video"]["name"].String());
-	CIntObject::activate();
-}
-
-void CMenuScreen::deactivate()
-{
-	if (!config["video"].isNull())
-		CCS->videoh->close();
-
-	CIntObject::deactivate();
-}
-
-void CMenuScreen::switchToTab(size_t index)
-{
-	tabs->setActive(index);
-}
-
-//funciton for std::string -> std::function conversion for main menu
-static std::function<void()> genCommand(CMenuScreen* menu, std::vector<std::string> menuType, const std::string &string)
-{
-	static const std::vector<std::string> commandType  =
-		{"to", "campaigns", "start", "load", "exit", "highscores"};
-
-	static const std::vector<std::string> gameType =
-		{"single", "multi", "campaign", "tutorial"};
-
-	std::list<std::string> commands;
-	boost::split(commands, string, boost::is_any_of("\t "));
-
-	if (!commands.empty())
-	{
-		size_t index = std::find(commandType.begin(), commandType.end(), commands.front()) - commandType.begin();
-		commands.pop_front();
-		if (index > 3 || !commands.empty())
-		{
-			switch (index)
-			{
-				break; case 0://to - switch to another tab, if such tab exists
-				{
-					size_t index2 = std::find(menuType.begin(), menuType.end(), commands.front()) - menuType.begin();
-					if ( index2 != menuType.size())
-                        return std::bind(&CMenuScreen::switchToTab, menu, index2);
-				}
-				break; case 1://open campaign selection window
-				{
-                    return std::bind(&CGPreGame::openCampaignScreen, CGP, commands.front());
-				}
-				break; case 2://start
-				{
-					switch (std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
-					{
-                        case 0: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::newGame, CMenuScreen::SINGLE_PLAYER);
-						case 1: return &pushIntT<CMultiMode>;
-                        case 2: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::campaignList, CMenuScreen::SINGLE_PLAYER);
-						//TODO: start tutorial
-                        case 3: return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector<CComponent*>*)nullptr, false, PlayerColor(1));
-					}
-				}
-				break; case 3://load
-				{
-					switch (std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
-					{
-						case 0: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::SINGLE_PLAYER);
-						case 1: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::MULTI_HOT_SEAT);
-						case 2: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::SINGLE_CAMPAIGN);
-						//TODO: load tutorial
-                        case 3: return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector<CComponent*>*)nullptr, false, PlayerColor(1));
-					}
-				}
-				break; case 4://exit
-				{
-                    return std::bind(CInfoWindow::showYesNoDialog, std::ref(CGI->generaltexth->allTexts[69]), (const std::vector<CComponent*>*)nullptr, do_quit, 0, false, PlayerColor(1));
-				}
-				break; case 5://highscores
-				{
-					//TODO: high scores
-                    return std::bind(CInfoWindow::showInfoDialog, "Sorry, high scores menu is not implemented yet\n", (const std::vector<CComponent*>*)nullptr, false, PlayerColor(1));
-				}
-			}
-		}
-	}
-	logGlobal->error("Failed to parse command: %s", string);
-	return std::function<void()>();
-}
-
-CButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNode& button)
-{
-	std::function<void()> command = genCommand(parent, parent->menuNameToEntry, button["command"].String());
-
-	std::pair<std::string, std::string> help;
-	if (!button["help"].isNull() && button["help"].Float() > 0)
-		help = CGI->generaltexth->zelp[button["help"].Float()];
-
-	int posx = button["x"].Float();
-	if (posx < 0)
-		posx = pos.w + posx;
-
-	int posy = button["y"].Float();
-	if (posy < 0)
-		posy = pos.h + posy;
-
-	return new CButton(Point(posx, posy), button["name"].String(), help, command, button["hotkey"].Float());
-}
-
-CMenuEntry::CMenuEntry(CMenuScreen* parent, const JsonNode &config)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	type |= REDRAW_PARENT;
-	pos = parent->pos;
-
-	for(const JsonNode& node : config["images"].Vector())
-		images.push_back(createPicture(node));
-
-	for(const JsonNode& node : config["buttons"].Vector())
-	{
-		buttons.push_back(createButton(parent, node));
-		buttons.back()->hoverable = true;
-		buttons.back()->type |= REDRAW_PARENT;
-	}
-}
-
-CreditsScreen::CreditsScreen():
-    positionCounter(0)
-{
-	addUsedEvents(LCLICK | RCLICK);
-	type |= REDRAW_PARENT;
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	pos.w = CGP->menu->pos.w;
-	pos.h = CGP->menu->pos.h;
-	auto textFile = CResourceHandler::get()->load(ResourceID("DATA/CREDITS.TXT"))->readAll();
-	std::string text((char*)textFile.first.get(), textFile.second);
-	size_t firstQuote = text.find('\"')+1;
-	text = text.substr(firstQuote, text.find('\"', firstQuote) - firstQuote );
-	credits = new CMultiLineLabel(Rect(pos.w - 350, 0, 350, 600), FONT_CREDITS, CENTER, Colors::WHITE, text);
-	credits->scrollTextTo(-600); // move all text below the screen
-}
-
-void CreditsScreen::show(SDL_Surface * to)
-{
-	CIntObject::show(to);
-	positionCounter++;
-	if (positionCounter % 2 == 0)
-		credits->scrollTextBy(1);
-
-	//end of credits, close this screen
-	if (credits->textSize.y + 600 < positionCounter / 2)
-		clickRight(false, false);
-}
-
-void CreditsScreen::clickLeft(tribool down, bool previousState)
-{
-	clickRight(down, previousState);
-}
-
-void CreditsScreen::clickRight(tribool down, bool previousState)
-{
-	CTabbedInt* menu = dynamic_cast<CTabbedInt*>(parent);
-	assert(menu);
-	menu->setActive(0);
-}
-
-CGPreGameConfig & CGPreGameConfig::get()
-{
-	static CGPreGameConfig config;
-	return config;
-}
-
-const JsonNode & CGPreGameConfig::getConfig() const
-{
-	return config;
-}
-
-const JsonNode & CGPreGameConfig::getCampaigns() const
-{
-	return campaignSets;
-}
-
-
-CGPreGameConfig::CGPreGameConfig() :
-	campaignSets(JsonNode(ResourceID("config/campaignSets.json"))),
-	config(JsonNode(ResourceID("config/mainmenu.json")))
-{
-
-}
-
-CGPreGame::CGPreGame()
-{
-	pos.w = screen->w;
-	pos.h = screen->h;
-
-	GH.defActionsDef = 63;
-	CGP = this;
-	menu = new CMenuScreen(CGPreGameConfig::get().getConfig()["window"]);
-	loadGraphics();
-}
-
-CGPreGame::~CGPreGame()
-{
-	boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
-	disposeGraphics();
-	if(CGP == this)
-		CGP = nullptr;
-
-	if(GH.curInt == this)
-		GH.curInt = nullptr;
-}
-
-void CGPreGame::openSel(CMenuScreen::EState screenType, CMenuScreen::EGameMode gameMode)
-{
-	GH.pushInt(new CSelectionScreen(screenType, gameMode));
-}
-
-void CGPreGame::loadGraphics()
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	new CFilledTexture("DIBOXBCK", pos);
-
-	victoryIcons = std::make_shared<CAnimation>("SCNRVICT.DEF");
-	victoryIcons->load();
-	lossIcons = std::make_shared<CAnimation>("SCNRLOSS.DEF");
-	lossIcons->load();
-}
-
-void CGPreGame::disposeGraphics()
-{
-	victoryIcons->unload();
-	lossIcons->unload();
-}
-
-void CGPreGame::update()
-{
-	if(CGP != this) //don't update if you are not a main interface
-		return;
-
-	if (GH.listInt.empty())
-	{
-		GH.pushInt(this);
-		GH.pushInt(menu);
-		menu->switchToTab(0);
-	}
-
-	if(SEL)
-		SEL->update();
-
-	// Handles mouse and key input
-	GH.updateTime();
-	GH.handleEvents();
-
-	// check for null othervice crash on finishing a campaign
-	// /FIXME: find out why GH.listInt is empty to begin with
-	if (GH.topInt() != nullptr)
-		GH.topInt()->show(screen);
-}
-
-void CGPreGame::openCampaignScreen(std::string name)
-{
-	if (vstd::contains(CGPreGameConfig::get().getCampaigns().Struct(), name))
-	{
-		GH.pushInt(new CCampaignScreen(CGPreGameConfig::get().getCampaigns()[name]));
-		return;
-	}
-	logGlobal->error("Unknown campaign set: %s", name);
-}
-
-CGPreGame *CGPreGame::create()
-{
-	if(!CGP)
-		CGP = new CGPreGame();
-
-	GH.terminate_cond->set(false);
-	return CGP;
-}
-
-void CGPreGame::removeFromGui()
-{
-	//remove everything but main menu and background
-	GH.popInts(GH.listInt.size() - 2);
-	GH.popInt(GH.topInt()); //remove main menu
-	GH.popInt(GH.topInt()); //remove background
-}
-
-CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EGameMode GameMode, const std::map<ui8, std::string> * Names, const std::string & Address, const ui16 Port)
-	: ISelectionScreenInfo(Names), serverHandlingThread(nullptr), mx(new boost::recursive_mutex),
-	  serv(nullptr), ongoingClosing(false), myNameID(255)
-{
-	CGPreGame::create(); //we depend on its graphics
-	screenType = Type;
-	gameMode = GameMode;
-
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	bool network = (isGuest() || isHost());
-
-	CServerHandler *sh = nullptr;
-	if(isHost())
-	{
-		sh = new CServerHandler();
-		sh->startServer();
-	}
-
-	IShowActivatable::type = BLOCK_ADV_HOTKEYS;
-	pos.w = 762;
-	pos.h = 584;
-	if(Type == CMenuScreen::saveGame)
-	{
-		bordered = false;
-		center(pos);
-	}
-	else if(Type == CMenuScreen::campaignList)
-	{
-		bordered = false;
-		bg = new CPicture("CamCust.bmp", 0, 0);
-		pos = bg->center();
-	}
-	else
-	{
-		bordered = true;
-		//load random background
-		const JsonVector & bgNames = CGPreGameConfig::get().getConfig()["game-select"].Vector();
-		bg = new CPicture(RandomGeneratorUtil::nextItem(bgNames, CRandomGenerator::getDefault())->String(), 0, 0);
-		pos = bg->center();
-	}
-
-	sInfo.difficulty = 1;
-	current = nullptr;
-
-	sInfo.mode = (Type == CMenuScreen::newGame ? StartInfo::NEW_GAME : StartInfo::LOAD_GAME);
-	sInfo.turnTime = 0;
-	curTab = nullptr;
-
-	card = new InfoCard(network); //right info card
-	if (screenType == CMenuScreen::campaignList)
-	{
-		opt = nullptr;
-		randMapTab = nullptr;
-	}
-	else
-	{
-		opt = new OptionsTab(); //scenario options tab
-		opt->recActions = DISPOSE;
-
-		randMapTab = new CRandomMapTab();
-        randMapTab->getMapInfoChanged() += std::bind(&CSelectionScreen::changeSelection, this, _1);
-		randMapTab->recActions = DISPOSE;
-	}
-	sel = new SelectionTab(screenType, std::bind(&CSelectionScreen::changeSelection, this, _1), gameMode); //scenario selection tab
-	sel->recActions = DISPOSE;
-
-	switch(screenType)
-	{
-	case CMenuScreen::newGame:
-		{
-			SDL_Color orange = {232, 184, 32, 0};
-			SDL_Color overlayColor = isGuest() ? orange : Colors::WHITE;
-
-			card->difficulty->addCallback(std::bind(&CSelectionScreen::difficultyChange, this, _1));
-			card->difficulty->setSelected(1);
-			CButton * select = new CButton(Point(411, 80), "GSPBUTT.DEF", CGI->generaltexth->zelp[45], 0, SDLK_s);
-			select->addCallback([&]()
-			{
-				toggleTab(sel);
-				changeSelection(sel->getSelectedMapInfo());
-			});
-			select->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, overlayColor);
-
-			CButton *opts = new CButton(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], std::bind(&CSelectionScreen::toggleTab, this, opt), SDLK_a);
-			opts->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, overlayColor);
-
-			CButton * randomBtn = new CButton(Point(411, 105), "GSPBUTT.DEF", CGI->generaltexth->zelp[47], 0, SDLK_r);
-			randomBtn->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, overlayColor);
-			randomBtn->addCallback([&]()
-			{
-				toggleTab(randMapTab);
-				changeSelection(randMapTab->getMapInfo());
-			});
-
-			start  = new CButton(Point(411, 535), "SCNRBEG.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_b);
-
-			if(network)
-			{
-				CButton *hideChat = new CButton(Point(619, 83), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&InfoCard::toggleChat, card), SDLK_h);
-				hideChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL);
-
-				if(isGuest())
-				{
-					select->block(true);
-					opts->block(true);
-					randomBtn->block(true);
-					start->block(true);
-				}
-			}
-		}
-		break;
-	case CMenuScreen::loadGame:
-		sel->recActions = 255;
-		start  = new CButton(Point(411, 535), "SCNRLOD.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_l);
-		break;
-	case CMenuScreen::saveGame:
-		sel->recActions = 255;
-		start  = new CButton(Point(411, 535), "SCNRSAV.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_s);
-		break;
-	case CMenuScreen::campaignList:
-		sel->recActions = 255;
-		start  = new CButton(Point(411, 535), "SCNRLOD.DEF", CButton::tooltip(), std::bind(&CSelectionScreen::startCampaign, this), SDLK_b);
-		break;
-	}
-
-	start->assignedKeys.insert(SDLK_RETURN);
-
-	back = new CButton(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
-
-	if(network)
-	{
-		if(isHost())
-		{
-			assert(playerNames.size() == 1  &&  vstd::contains(playerNames, 1)); //TODO hot-seat/network combo
-			if(settings["session"]["donotstartserver"].Bool())
-				serv = CServerHandler::justConnectToServer(Address, Port);
-			else
-				serv = sh->connectToServer();
-			*serv << (ui8) 4;
-			myNameID = 1;
-		}
-		else
-			serv = CServerHandler::justConnectToServer(Address, Port);
-
-		serv->enterPregameConnectionMode();
-		*serv << playerNames.begin()->second;
-
-		if(isGuest())
-		{
-			const CMapInfo *map;
-			*serv >> myNameID >> map;
-			serv->connectionID = myNameID;
-			changeSelection(map);
-		}
-		else if(current)
-		{
-			SelectMap sm(*current);
-			*serv << &sm;
-
-			UpdateStartOptions uso(sInfo);
-			*serv << &uso;
-		}
-
-		applier = new CApplier<CBaseForPGApply>();
-		registerTypesPregamePacks(*applier);
-		serverHandlingThread = new boost::thread(&CSelectionScreen::handleConnection, this);
-	}
-	delete sh;
-}
-
-CSelectionScreen::~CSelectionScreen()
-{
-	ongoingClosing = true;
-	if(serv)
-	{
-		assert(serverHandlingThread);
-		QuitMenuWithoutStarting qmws;
-		*serv << &qmws;
-// 		while(!serverHandlingThread->timed_join(boost::posix_time::milliseconds(50)))
-// 			processPacks();
-		serverHandlingThread->join();
-		delete serverHandlingThread;
-	}
-	playerColor = PlayerColor::CANNOT_DETERMINE;
-	playerNames.clear();
-
-	assert(!serv);
-	vstd::clear_pointer(applier);
-	delete mx;
-}
-
-void CSelectionScreen::toggleTab(CIntObject *tab)
-{
-	if(isHost() && serv)
-	{
-		PregameGuiAction pga;
-		if(tab == curTab)
-			pga.action = PregameGuiAction::NO_TAB;
-		else if(tab == opt)
-			pga.action = PregameGuiAction::OPEN_OPTIONS;
-		else if(tab == sel)
-			pga.action = PregameGuiAction::OPEN_SCENARIO_LIST;
-		else if(tab == randMapTab)
-			pga.action = PregameGuiAction::OPEN_RANDOM_MAP_OPTIONS;
-
-		*serv << &pga;
-	}
-
-	if(curTab && curTab->active)
-	{
-		curTab->deactivate();
-		curTab->recActions = DISPOSE;
-	}
-	if(curTab != tab)
-	{
-		tab->recActions = 255;
-		tab->activate();
-		curTab = tab;
-	}
-	else
-	{
-		curTab = nullptr;
-	}
-	GH.totalRedraw();
-}
-
-void CSelectionScreen::changeSelection(const CMapInfo * to)
-{
-	if(current == to) return;
-	if(isGuest())
-		vstd::clear_pointer(current);
-
-	current = to;
-
-	if(to && (screenType == CMenuScreen::loadGame ||
-			  screenType == CMenuScreen::saveGame))
-	   SEL->sInfo.difficulty = to->scenarioOpts->difficulty;
-	if(screenType != CMenuScreen::campaignList)
-	{
-		std::unique_ptr<CMapHeader> emptyHeader;
-        if(to)
-			updateStartInfo(to->fileURI, sInfo, to->mapHeader);
-		else
-			updateStartInfo("", sInfo, emptyHeader);
-
-		if(screenType == CMenuScreen::newGame)
-		{
-			if(to && to->isRandomMap)
-			{
-				sInfo.mapGenOptions = std::shared_ptr<CMapGenOptions>(new CMapGenOptions(randMapTab->getMapGenOptions()));
-			}
-			else
-			{
-				sInfo.mapGenOptions.reset();
-			}
-		}
-	}
-	card->changeSelection(to);
-	if(screenType != CMenuScreen::campaignList)
-	{
-		opt->recreate();
-	}
-
-	if(isHost() && serv)
-	{
-		SelectMap sm(*to);
-		*serv << &sm;
-
-		UpdateStartOptions uso(sInfo);
-		*serv << &uso;
-	}
-}
-
-void CSelectionScreen::startCampaign()
-{
-	if (SEL->current)
-		GH.pushInt(new CBonusSelection(SEL->current->fileURI));
-}
-
-void CSelectionScreen::startScenario()
-{
-	if(screenType == CMenuScreen::newGame)
-	{
-		//there must be at least one human player before game can be started
-		std::map<PlayerColor, PlayerSettings>::const_iterator i;
-		for(i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++)
-			if(i->second.playerID != PlayerSettings::PLAYER_AI)
-				break;
-
-		if(i == SEL->sInfo.playerInfos.cend())
-		{
-			GH.pushInt(CInfoWindow::create(CGI->generaltexth->allTexts[530])); //You must position yourself prior to starting the game.
-			return;
-		}
-	}
-
-	if(isHost())
-	{
-		start->block(true);
-		StartWithCurrentSettings swcs;
-		*serv << &swcs;
-		ongoingClosing = true;
-		return;
-	}
-
-	if(screenType != CMenuScreen::saveGame)
-	{
-		if(!current)
-			return;
-
-		if(sInfo.mapGenOptions)
-		{
-			//copy settings from interface to actual options. TODO: refactor, it used to have no effect at all -.-
-			sInfo.mapGenOptions = std::shared_ptr<CMapGenOptions>(new CMapGenOptions(randMapTab->getMapGenOptions()));
-
-			// Update player settings for RMG
-			for(const auto & psetPair : sInfo.playerInfos)
-			{
-				const auto & pset = psetPair.second;
-				sInfo.mapGenOptions->setStartingTownForPlayer(pset.color, pset.castle);
-				if(pset.playerID != PlayerSettings::PLAYER_AI)
-				{
-					sInfo.mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::HUMAN);
-				}
-			}
-
-			if(!sInfo.mapGenOptions->checkOptions())
-			{
-				GH.pushInt(CInfoWindow::create(CGI->generaltexth->allTexts[751]));
-				return;
-			}
-		}
-
-		saveGameName.clear();
-		if(screenType == CMenuScreen::loadGame)
-		{
-			saveGameName = sInfo.mapname;
-		}
-
-		auto   si = new StartInfo(sInfo);
-		CGP->removeFromGui();
-        CGP->showLoadingScreen(std::bind(&startGame, si, (CConnection *)nullptr));
-	}
-	else
-	{
-		if(!(sel && sel->txt && sel->txt->text.size()))
-			return;
-
-		saveGameName = "Saves/" + sel->txt->text;
-
-		CFunctionList<void()> overWrite;
-        overWrite += std::bind(&CCallback::save, LOCPLINT->cb.get(), saveGameName);
-        overWrite += std::bind(&CGuiHandler::popIntTotally, &GH, this);
-
-		if(CResourceHandler::get("local")->existsResource(ResourceID(saveGameName, EResType::CLIENT_SAVEGAME)))
-		{
-			std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite?
-			boost::algorithm::replace_first(hlp, "%s", sel->txt->text);
-			LOCPLINT->showYesNoDialog(hlp, overWrite, 0, false);
-		}
-		else
-			overWrite();
-	}
-}
-
-void CSelectionScreen::difficultyChange( int to )
-{
-	assert(screenType == CMenuScreen::newGame);
-	sInfo.difficulty = to;
-	propagateOptions();
-	redraw();
-}
-
-void CSelectionScreen::handleConnection()
-{
-	setThreadName("CSelectionScreen::handleConnection");
-	try
-	{
-		assert(serv);
-		while(serv)
-		{
-			CPackForSelectionScreen *pack = nullptr;
-			*serv >> pack;
-			logNetwork->trace("Received a pack of type %s", typeid(*pack).name());
-			assert(pack);
-			if(QuitMenuWithoutStarting *endingPack = dynamic_cast<QuitMenuWithoutStarting *>(pack))
-			{
-				endingPack->apply(this);
-			}
-			else if(StartWithCurrentSettings *endingPack = dynamic_cast<StartWithCurrentSettings *>(pack))
-			{
-				endingPack->apply(this);
-			}
-			else
-			{
-				boost::unique_lock<boost::recursive_mutex> lll(*mx);
-				upcomingPacks.push_back(pack);
-			}
-		}
-	}
-	catch(int i)
-	{
-		if(i != 666)
-			throw;
-	}
-	catch(...)
-	{
-		handleException();
-		throw;
-	}
-}
-
-void CSelectionScreen::setSInfo(const StartInfo &si)
-{
-	std::map<PlayerColor, PlayerSettings>::const_iterator i;
-	for(i = si.playerInfos.cbegin(); i != si.playerInfos.cend(); i++)
-	{
-		if(i->second.playerID == myNameID)
-		{
-			playerColor = i->first;
-			break;
-		}
-	}
-
-	if(i == si.playerInfos.cend()) //not found
-		playerColor = PlayerColor::CANNOT_DETERMINE;
-
-	sInfo = si;
-	if(current)
-		opt->recreate(); //will force to recreate using current sInfo
-
-	card->difficulty->setSelected(si.difficulty);
-
-	if(curTab == randMapTab)
-		randMapTab->setMapGenOptions(si.mapGenOptions);
-
-	GH.totalRedraw();
-}
-
-void CSelectionScreen::processPacks()
-{
-	boost::unique_lock<boost::recursive_mutex> lll(*mx);
-	while(!upcomingPacks.empty())
-	{
-		CPackForSelectionScreen *pack = upcomingPacks.front();
-		upcomingPacks.pop_front();
-		CBaseForPGApply *apply = applier->getApplier(typeList.getTypeID(pack)); //find the applier
-		apply->applyOnPG(this, pack);
-		delete pack;
-	}
-}
-
-void CSelectionScreen::update()
-{
-	if(serverHandlingThread)
-		processPacks();
-}
-
-void CSelectionScreen::propagateOptions()
-{
-	if(isHost() && serv)
-	{
-		UpdateStartOptions ups(sInfo);
-		*serv << &ups;
-	}
-}
-
-void CSelectionScreen::postRequest(ui8 what, ui8 dir)
-{
-	if(!isGuest() || !serv)
-		return;
-
-	RequestOptionsChange roc(what, dir, myNameID);
-	*serv << &roc;
-}
-
-void CSelectionScreen::postChatMessage(const std::string &txt)
-{
-	assert(serv);
-	ChatMessage cm;
-	cm.message = txt;
-	cm.playerName = sInfo.getPlayersSettings(myNameID)->name;
-	*serv << &cm;
-}
-
-void CSelectionScreen::propagateNames()
-{
-	PlayersNames pn;
-	pn.playerNames = playerNames;
-	*serv << &pn;
-}
-
-void CSelectionScreen::showAll(SDL_Surface *to)
-{
-	CIntObject::showAll(to);
-	if (bordered && (pos.h != to->h || pos.w != to->w))
-		CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15);
-}
-
-// A new size filter (Small, Medium, ...) has been selected. Populate
-// selMaps with the relevant data.
-void SelectionTab::filter( int size, bool selectFirst )
-{
-	curItems.clear();
-
-	if(tabType == CMenuScreen::campaignList)
-	{
-		for (auto & elem : allItems)
-			curItems.push_back(&elem);
-	}
-	else
-	{
-		for (auto & elem : allItems)
-			if( elem.mapHeader && elem.mapHeader->version  &&  (!size || elem.mapHeader->width == size))
-				curItems.push_back(&elem);
-	}
-
-	if(curItems.size())
-	{
-		slider->block(false);
-		slider->setAmount(curItems.size());
-		sort();
-		if(selectFirst)
-		{
-			slider->moveTo(0);
-			onSelect(curItems[0]);
-			selectAbs(0);
-		}
-	}
-	else
-	{
-		slider->block(true);
-		onSelect(nullptr);
-	}
-}
-
-std::unordered_set<ResourceID> SelectionTab::getFiles(std::string dirURI, int resType)
-{
-	boost::to_upper(dirURI);
-	CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount)
-	{
-		return boost::algorithm::starts_with(mount, dirURI);
-	});
-
-	std::unordered_set<ResourceID> ret = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
-	{
-		return ident.getType() == resType
-			&& boost::algorithm::starts_with(ident.getName(), dirURI);
-	});
-
-	return ret;
-}
-
-void SelectionTab::parseMaps(const std::unordered_set<ResourceID> &files)
-{
-	logGlobal->debug("Parsing %d maps", files.size());
-	allItems.clear();
-	for(auto & file : files)
-	{
-		try
-		{
-			CMapInfo mapInfo;
-			mapInfo.mapInit(file.getName());
-
-			// ignore unsupported map versions (e.g. WoG maps without WoG)
-			// but accept VCMI maps
-			if((mapInfo.mapHeader->version >= EMapFormat::VCMI) || (mapInfo.mapHeader->version <= CGI->modh->settings.data["textData"]["mapVersion"].Float()))
-				allItems.push_back(std::move(mapInfo));
-		}
-		catch(std::exception & e)
-		{
-			logGlobal->error("Map %s is invalid. Message: %s", file.getName(), e.what());
-		}
-	}
-}
-
-void SelectionTab::parseGames(const std::unordered_set<ResourceID> &files, CMenuScreen::EGameMode gameMode)
-{
-	for(auto & file : files)
-	{
-		try
-		{
-			CLoadFile lf(*CResourceHandler::get()->getResourceName(file), MINIMAL_SERIALIZATION_VERSION);
-			lf.checkMagicBytes(SAVEGAME_MAGIC);
-// 			ui8 sign[8];
-// 			lf >> sign;
-// 			if(std::memcmp(sign,"VCMISVG",7))
-// 			{
-// 				throw std::runtime_error("not a correct savefile!");
-// 			}
-
-			// Create the map info object
-			CMapInfo mapInfo;
-			mapInfo.mapHeader = make_unique<CMapHeader>();
-			mapInfo.scenarioOpts = nullptr;//to be created by serialiser
-			lf >> *(mapInfo.mapHeader.get()) >> mapInfo.scenarioOpts;
-			mapInfo.fileURI = file.getName();
-			mapInfo.countPlayers();
-			std::time_t time = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(file));
-			mapInfo.date = std::asctime(std::localtime(&time));
-
-			// Filter out other game modes
-			bool isCampaign = mapInfo.scenarioOpts->mode == StartInfo::CAMPAIGN;
-			bool isMultiplayer = mapInfo.actualHumanPlayers > 1;
-			switch(gameMode)
-			{
-			case CMenuScreen::SINGLE_PLAYER:
-				if(isMultiplayer || isCampaign)
-					mapInfo.mapHeader.reset();
-				break;
-			case CMenuScreen::SINGLE_CAMPAIGN:
-				if(!isCampaign)
-					mapInfo.mapHeader.reset();
-				break;
-			default:
-				if(!isMultiplayer)
-					mapInfo.mapHeader.reset();
-				break;
-			}
-
-			allItems.push_back(std::move(mapInfo));
-		}
-		catch(const std::exception & e)
-		{
-			logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what());
-		}
-	}
-}
-
-void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> &files )
-{
-	allItems.reserve(files.size());
-	for (auto & file : files)
-	{
-		CMapInfo info;
-		//allItems[i].date = std::asctime(std::localtime(&files[i].date));
-		info.fileURI = file.getName();
-		info.campaignInit();
-		allItems.push_back(std::move(info));
-	}
-}
-
-SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::function<void(CMapInfo *)> &OnSelect, CMenuScreen::EGameMode GameMode)
-	:bg(nullptr), onSelect(OnSelect)
-{
-	OBJ_CONSTRUCTION;
-	selectionPos = 0;
-	addUsedEvents(LCLICK | WHEEL | KEYBOARD | DOUBLECLICK);
-	slider = nullptr;
-	txt = nullptr;
-	tabType = Type;
-
-	if (Type != CMenuScreen::campaignList)
-	{
-		bg = new CPicture("SCSELBCK.bmp", 0, 6);
-		pos = bg->pos;
-	}
-	else
-	{
-		bg = nullptr; //use background from parent
-		type |= REDRAW_PARENT; // we use parent background so we need to make sure it's will be redrawn too
-		pos.w = parent->pos.w;
-		pos.h = parent->pos.h;
-		pos.x += 3; pos.y += 6;
-	}
-
-	if(GameMode == CMenuScreen::MULTI_NETWORK_GUEST)
-	{
-		positions = 18;
-	}
-	else
-	{
-		switch(tabType)
-		{
-		case CMenuScreen::newGame:
-			parseMaps(getFiles("Maps/", EResType::MAP));
-			positions = 18;
-			break;
-
-		case CMenuScreen::loadGame:
-		case CMenuScreen::saveGame:
-			parseGames(getFiles("Saves/", EResType::CLIENT_SAVEGAME), GameMode);
-			if(tabType == CMenuScreen::loadGame)
-			{
-				positions = 18;
-			}
-			else
-			{
-				positions = 16;
-			}
-			if(tabType == CMenuScreen::saveGame)
-			{
-				txt = new CTextInput(Rect(32, 539, 350, 20), Point(-32, -25), "GSSTRIP.bmp", 0);
-				txt->filters += CTextInput::filenameFilter;
-			}
-			break;
-
-		case CMenuScreen::campaignList:
-			parseCampaigns(getFiles("Maps/", EResType::CAMPAIGN));
-			positions = 18;
-			break;
-
-		default:
-			assert(0);
-			break;
-		}
-	}
-
-	generalSortingBy = (tabType == CMenuScreen::loadGame || tabType == CMenuScreen::saveGame) ? _fileName : _name;
-
-	if (tabType != CMenuScreen::campaignList)
-	{
-		//size filter buttons
-		{
-			int sizes[] = {36, 72, 108, 144, 0};
-			const char * names[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
-			for(int i = 0; i < 5; i++)
-				new CButton(Point(158 + 47*i, 46), names[i], CGI->generaltexth->zelp[54+i], std::bind(&SelectionTab::filter, this, sizes[i], true));
-		}
-
-		//sort buttons buttons
-		{
-			int xpos[] = {23, 55, 88, 121, 306, 339};
-			const char * names[] = {"SCBUTT1.DEF", "SCBUTT2.DEF", "SCBUTCP.DEF", "SCBUTT3.DEF", "SCBUTT4.DEF", "SCBUTT5.DEF"};
-			for(int i = 0; i < 6; i++)
-			{
-				ESortBy criteria = (ESortBy)i;
-
-				if(criteria == _name)
-					criteria = generalSortingBy;
-
-				new CButton(Point(xpos[i], 86), names[i], CGI->generaltexth->zelp[107+i], std::bind(&SelectionTab::sortBy, this, criteria));
-			}
-		}
-	}
-	else
-	{
-		//sort by buttons
-		new CButton(Point(23, 86), "CamCusM.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)); //by num of maps
-		new CButton(Point(55, 86), "CamCusL.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)); //by name
-	}
-
-	slider = new CSlider(Point(372, 86), tabType != CMenuScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positions, curItems.size(), 0, false, CSlider::BLUE);
-	slider->addUsedEvents(WHEEL);
-
-	formatIcons = std::make_shared<CAnimation>("SCSELC.DEF");
-	formatIcons->load();
-
-	sortingBy = _format;
-	ascending = true;
-	filter(0);
-	//select(0);
-	switch(tabType)
-	{
-	case CMenuScreen::newGame:
-		logGlobal->error(settings["session"]["lastMap"].String());
-		if(settings["session"]["lastMap"].isNull())
-			selectFName("Maps/Arrogance");
-		else
-			selectFName(settings["session"]["lastMap"].String());
-
-		break;
-	case CMenuScreen::campaignList:
-		select(0);
-		break;
-	case CMenuScreen::loadGame:
-	case CMenuScreen::saveGame:
-		if(saveGameName.empty())
-		{
-			if(tabType == CMenuScreen::saveGame)
-				txt->setText("NEWGAME");
-			else
-				select(0);
-		}
-		else
-		{
-			selectFName(saveGameName);
-		}
-	}
-}
-
-SelectionTab::~SelectionTab()
-{
-	formatIcons->unload();
-}
-
-void SelectionTab::sortBy( int criteria )
-{
-	if(criteria == sortingBy)
-	{
-		ascending = !ascending;
-	}
-	else
-	{
-		sortingBy = (ESortBy)criteria;
-		ascending = true;
-	}
-	sort();
-
-	selectAbs(0);
-}
-
-void SelectionTab::sort()
-{
-	if(sortingBy != generalSortingBy)
-		std::stable_sort(curItems.begin(), curItems.end(), mapSorter(generalSortingBy));
-	std::stable_sort(curItems.begin(), curItems.end(), mapSorter(sortingBy));
-
-	if(!ascending)
-		std::reverse(curItems.begin(), curItems.end());
-
-	redraw();
-}
-
-void SelectionTab::select( int position )
-{
-	if(!curItems.size()) return;
-
-	// New selection. py is the index in curItems.
-	int py = position + slider->getValue();
-	vstd::amax(py, 0);
-	vstd::amin(py, curItems.size()-1);
-
-	selectionPos = py;
-
-	if(position < 0)
-		slider->moveBy(position);
-	else if(position >= positions)
-		slider->moveBy(position - positions + 1);
-
-	if(tabType == CMenuScreen::newGame)
-	{
-		Settings lastMap = settings.write["session"]["lastMap"];
-		lastMap->String() = getSelectedMapInfo()->fileURI;
-	}
-
-	if(txt)
-	{
-		auto filename = *CResourceHandler::get("local")->getResourceName(
-								   ResourceID(curItems[py]->fileURI, EResType::CLIENT_SAVEGAME));
-		txt->setText(filename.stem().string());
-	}
-
-	onSelect(curItems[py]);
-}
-
-void SelectionTab::selectAbs( int position )
-{
-	select(position - slider->getValue());
-}
-
-int SelectionTab::getPosition( int x, int y )
-{
-	return -1;
-}
-
-void SelectionTab::sliderMove( int slidPos )
-{
-	if(!slider) return; //ignore spurious call when slider is being created
-	redraw();
-}
-
-
-// Display the tab with the scenario names
-//
-// elemIdx is the index of the maps or saved game to display on line 0
-// slider->capacity contains the number of available screen lines
-// slider->positionsAmnt is the number of elements after filtering
-void SelectionTab::printMaps(SDL_Surface *to)
-{
-
-	int elemIdx = slider->getValue();
-
-	// Display all elements if there's enough space
-	//if(slider->amount < slider->capacity)
-	//	elemIdx = 0;
-
-
-	SDL_Color itemColor;
-	for (int line = 0; line < positions  &&  elemIdx < curItems.size(); elemIdx++, line++)
-	{
-		CMapInfo *currentItem = curItems[elemIdx];
-
-		if(elemIdx == selectionPos)
-			itemColor=Colors::YELLOW;
-		else
-			itemColor=Colors::WHITE;
-
-		if(tabType != CMenuScreen::campaignList)
-		{
-			//amount of players
-			std::ostringstream ostr(std::ostringstream::out);
-			ostr << currentItem->playerAmnt << "/" << currentItem->humanPlayers;
-			printAtLoc(ostr.str(), 29, 120 + line * 25, FONT_SMALL, itemColor, to);
-
-			//map size
-			std::string temp2 = "C";
-			switch (currentItem->mapHeader->width)
-			{
-			case 36:
-				temp2="S";
-				break;
-			case 72:
-				temp2="M";
-				break;
-			case 108:
-				temp2="L";
-				break;
-			case 144:
-				temp2="XL";
-				break;
-			}
-			printAtMiddleLoc(temp2, 70, 128 + line * 25, FONT_SMALL, itemColor, to);
-
-			int frame = -1, group = 0;
-			switch (currentItem->mapHeader->version)
-			{
-			case EMapFormat::ROE:
-				frame = 0;
-				break;
-			case EMapFormat::AB:
-				frame = 1;
-				break;
-			case EMapFormat::SOD:
-				frame = 2;
-				break;
-			case EMapFormat::WOG:
-				frame = 3;
-				break;
-			case EMapFormat::VCMI:
-				frame = 0;
-				group = 1;
-				break;
-			default:
-				// Unknown version. Be safe and ignore that map
-				logGlobal->warn("Warning: %s has wrong version!", currentItem->fileURI);
-				continue;
-			}
-			auto icon = formatIcons->getImage(frame,group);
-			if(icon)
-			{
-				icon->draw(to, pos.x + 88, pos.y + 117 + line * 25);
-				icon->decreaseRef();
-			}
-
-			//victory conditions
-			icon = CGP->victoryIcons->getImage(currentItem->mapHeader->victoryIconIndex,0);
-			if(icon)
-			{
-				icon->draw(to, pos.x + 306, pos.y + 117 + line * 25);
-				icon->decreaseRef();
-			}
-			//loss conditions
-			icon = CGP->lossIcons->getImage(currentItem->mapHeader->defeatIconIndex,0);
-			if(icon)
-			{
-				icon->draw(to, pos.x + 339, pos.y + 117 + line * 25);
-				icon->decreaseRef();
-			}
-		}
-		else //if campaign
-		{
-			//number of maps in campaign
-			std::ostringstream ostr(std::ostringstream::out);
-			ostr << CGI->generaltexth->campaignRegionNames[ currentItem->campaignHeader->mapVersion ].size();
-			printAtLoc(ostr.str(), 29, 120 + line * 25, FONT_SMALL, itemColor, to);
-		}
-
-		std::string name;
-		if(tabType == CMenuScreen::newGame)
-		{
-			if (!currentItem->mapHeader->name.length())
-				currentItem->mapHeader->name = "Unnamed";
-			name = currentItem->mapHeader->name;
-		}
-		else if(tabType == CMenuScreen::campaignList)
-		{
-			name = currentItem->campaignHeader->name;
-		}
-		else
-		{
-			name = CResourceHandler::get("local")->getResourceName(
-								 ResourceID(currentItem->fileURI, EResType::CLIENT_SAVEGAME))->stem().string();
-		}
-
-		//print name
-		printAtMiddleLoc(name, 213, 128 + line * 25, FONT_SMALL, itemColor, to);
-	}
-}
-
-void SelectionTab::showAll(SDL_Surface * to)
-{
-	CIntObject::showAll(to);
-	printMaps(to);
-
-	std::string title;
-	switch(tabType) {
-	case CMenuScreen::newGame:
-		title = CGI->generaltexth->arraytxt[229];
-		break;
-	case CMenuScreen::loadGame:
-		title = CGI->generaltexth->arraytxt[230];
-		break;
-	case CMenuScreen::saveGame:
-		title = CGI->generaltexth->arraytxt[231];
-		break;
-	case CMenuScreen::campaignList:
-		title = CGI->generaltexth->allTexts[726];
-		break;
-	}
-
-	printAtMiddleLoc(title, 205, 28, FONT_MEDIUM, Colors::YELLOW, to); //Select a Scenario to Play
-	if(tabType != CMenuScreen::campaignList)
-	{
-		printAtMiddleLoc(CGI->generaltexth->allTexts[510], 87, 62, FONT_SMALL, Colors::YELLOW, to); //Map sizes
-	}
-}
-
-void SelectionTab::clickLeft( tribool down, bool previousState )
-{
-	if(down)
-	{
-		int line = getLine();
-		if(line != -1)
-			select(line);
-	}
-}
-void SelectionTab::keyPressed( const SDL_KeyboardEvent & key )
-{
-	if(key.state != SDL_PRESSED) return;
-
-	int moveBy = 0;
-	switch(key.keysym.sym)
-	{
-	case SDLK_UP:
-		moveBy = -1;
-		break;
-	case SDLK_DOWN:
-		moveBy = +1;
-		break;
-	case SDLK_PAGEUP:
-		moveBy = -positions+1;
-		break;
-	case SDLK_PAGEDOWN:
-		moveBy = +positions-1;
-		break;
-	case SDLK_HOME:
-		select(-slider->getValue());
-		return;
-	case SDLK_END:
-		select(curItems.size() - slider->getValue());
-		return;
-	default:
-		return;
-	}
-	select(selectionPos - slider->getValue() + moveBy);
-}
-
-void SelectionTab::onDoubleClick()
-{
-	if(getLine() != -1) //double clicked scenarios list
-	{
-		//act as if start button was pressed
-		(static_cast<CSelectionScreen*>(parent))->start->clickLeft(false, true);
-	}
-}
-
-int SelectionTab::getLine()
-{
-	int line = -1;
-	Point clickPos(GH.current->button.x, GH.current->button.y);
-	clickPos = clickPos - pos.topLeft();
-
-	// Ignore clicks on save name area
-	int maxPosY;
-	if(tabType == CMenuScreen::saveGame)
-		maxPosY = 516;
-	else
-		maxPosY = 564;
-
-    	if(clickPos.y > 115  &&  clickPos.y < maxPosY  &&  clickPos.x > 22  &&  clickPos.x < 371)
-	{
-		line = (clickPos.y-115) / 25; //which line
-	}
-
-	return line;
-}
-
-void SelectionTab::selectFName( std::string fname )
-{
-	boost::to_upper(fname);
-	for(int i = curItems.size() - 1; i >= 0; i--)
-	{
-		if(curItems[i]->fileURI == fname)
-		{
-			slider->moveTo(i);
-			selectAbs(i);
-			return;
-		}
-	}
-
-	selectAbs(0);
-}
-
-const CMapInfo * SelectionTab::getSelectedMapInfo() const
-{
-	return curItems.empty() ? nullptr : curItems[selectionPos];
-}
-
-CRandomMapTab::CRandomMapTab()
-{
-	OBJ_CONSTRUCTION;
-	bg = new CPicture("RANMAPBK", 0, 6);
-
-	// Map Size
-	mapSizeBtnGroup = new CToggleGroup(0);
-	mapSizeBtnGroup->pos.y += 81;
-	mapSizeBtnGroup->pos.x += 158;
-	const std::vector<std::string> mapSizeBtns = {"RANSIZS", "RANSIZM","RANSIZL","RANSIZX"};
-	addButtonsToGroup(mapSizeBtnGroup, mapSizeBtns, 0, 3, 47, 198);
-	mapSizeBtnGroup->setSelected(1);
-	mapSizeBtnGroup->addCallback([&](int btnId)
-	{
-		auto mapSizeVal = getPossibleMapSizes();
-		mapGenOptions.setWidth(mapSizeVal[btnId]);
-		mapGenOptions.setHeight(mapSizeVal[btnId]);
-		if(!SEL->isGuest())
-			updateMapInfo();
-	});
-
-	// Two levels
-	twoLevelsBtn = new CToggleButton(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]);
-	//twoLevelsBtn->select(true); for now, deactivated
-	twoLevelsBtn->addCallback([&](bool on)
-	{
-		mapGenOptions.setHasTwoLevels(on);
-		if(!SEL->isGuest())
-			updateMapInfo();
-	});
-
-	// Create number defs list
-	std::vector<std::string> numberDefs;
-	for(int i = 0; i <= 8; ++i)
-	{
-		numberDefs.push_back("RANNUM" + boost::lexical_cast<std::string>(i));
-	}
-
-	const int NUMBERS_WIDTH = 32;
-	const int BTNS_GROUP_LEFT_MARGIN = 67;
-	// Amount of players
-	playersCntGroup = new CToggleGroup(0);
-	playersCntGroup->pos.y += 153;
-	playersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	addButtonsWithRandToGroup(playersCntGroup, numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212);
-	playersCntGroup->addCallback([&](int btnId)
-	{
-		mapGenOptions.setPlayerCount(btnId);
-		deactivateButtonsFrom(teamsCntGroup, btnId);
-		deactivateButtonsFrom(compOnlyPlayersCntGroup, btnId);
-		validatePlayersCnt(btnId);
-		if(!SEL->isGuest())
-			updateMapInfo();
-	});
-
-	// Amount of teams
-	teamsCntGroup = new CToggleGroup(0);
-	teamsCntGroup->pos.y += 219;
-	teamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	addButtonsWithRandToGroup(teamsCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222);
-	teamsCntGroup->addCallback([&](int btnId)
-	{
-		mapGenOptions.setTeamCount(btnId);
-		if(!SEL->isGuest())
-			updateMapInfo();
-	});
-
-	// Computer only players
-	compOnlyPlayersCntGroup = new CToggleGroup(0);
-	compOnlyPlayersCntGroup->pos.y += 285;
-	compOnlyPlayersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	addButtonsWithRandToGroup(compOnlyPlayersCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232);
-	compOnlyPlayersCntGroup->addCallback([&](int btnId)
-	{
-		mapGenOptions.setCompOnlyPlayerCount(btnId);
-		deactivateButtonsFrom(compOnlyTeamsCntGroup, (btnId == 0 ? 1 : btnId));
-		validateCompOnlyPlayersCnt(btnId);
-		if(!SEL->isGuest())
-			updateMapInfo();
-	});
-
-	// Computer only teams
-	compOnlyTeamsCntGroup = new CToggleGroup(0);
-	compOnlyTeamsCntGroup->pos.y += 351;
-	compOnlyTeamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	addButtonsWithRandToGroup(compOnlyTeamsCntGroup, numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241);
-	deactivateButtonsFrom(compOnlyTeamsCntGroup, 1);
-	compOnlyTeamsCntGroup->addCallback([&](int btnId)
-	{
-		mapGenOptions.setCompOnlyTeamCount(btnId);
-		if(!SEL->isGuest())
-			updateMapInfo();
-	});
-
-	const int WIDE_BTN_WIDTH = 85;
-	// Water content
-	waterContentGroup = new CToggleGroup(0);
-	waterContentGroup->pos.y += 419;
-	waterContentGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	const std::vector<std::string> waterContentBtns = {"RANNONE","RANNORM","RANISLD"};
-	addButtonsWithRandToGroup(waterContentGroup, waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246);
-	waterContentGroup->addCallback([&](int btnId)
-	{
-		mapGenOptions.setWaterContent(static_cast<EWaterContent::EWaterContent>(btnId));
-	});
-
-	// Monster strength
-	monsterStrengthGroup = new CToggleGroup(0);
-	monsterStrengthGroup->pos.y += 485;
-	monsterStrengthGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	const std::vector<std::string> monsterStrengthBtns = {"RANWEAK","RANNORM","RANSTRG"};
-	addButtonsWithRandToGroup(monsterStrengthGroup, monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251);
-	monsterStrengthGroup->addCallback([&](int btnId)
-	{
-		if (btnId < 0)
-			mapGenOptions.setMonsterStrength(EMonsterStrength::RANDOM);
-		else
-			mapGenOptions.setMonsterStrength(static_cast<EMonsterStrength::EMonsterStrength>(btnId + EMonsterStrength::GLOBAL_WEAK)); //value 2 to 4
-	});
-
-	// Show random maps btn
-	showRandMaps = new CButton(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]);
-
-	// Initialize map info object
-	if(!SEL->isGuest())
-		updateMapInfo();
-}
-
-void CRandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex) const
-{
-	addButtonsToGroup(group, defs, nStart, nEnd, btnWidth, helpStartIndex);
-
-	// Buttons are relative to button group, TODO better solution?
-	SObjectConstruction obj__i(group);
-	const std::string RANDOM_DEF = "RANRAND";
-	group->addToggle(CMapGenOptions::RANDOM_SIZE, new CToggleButton(Point(256, 0), RANDOM_DEF, CGI->generaltexth->zelp[helpRandIndex]));
-	group->setSelected(CMapGenOptions::RANDOM_SIZE);
-}
-
-void CRandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex) const
-{
-	// Buttons are relative to button group, TODO better solution?
-	SObjectConstruction obj__i(group);
-	int cnt = nEnd - nStart + 1;
-	for(int i = 0; i < cnt; ++i)
-	{
-		auto button = new CToggleButton(Point(i * btnWidth, 0), defs[i + nStart], CGI->generaltexth->zelp[helpStartIndex + i]);
-		// For blocked state we should use pressed image actually
-		button->setImageOrder(0, 1, 1, 3);
-
-		group->addToggle(i + nStart, button);
-	}
-}
-
-void CRandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
-{
-	logGlobal->debug("Blocking buttons from %d", startId);
-	for(auto toggle : group->buttons)
-	{
-		if (auto button = dynamic_cast<CToggleButton*>(toggle.second))
-		{
-			if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId)
-			{
-				button->block(false);
-			}
-			else
-			{
-				button->block(true);
-			}
-		}
-	}
-}
-
-void CRandomMapTab::validatePlayersCnt(int playersCnt)
-{
-	if(playersCnt == CMapGenOptions::RANDOM_SIZE)
-	{
-		return;
-	}
-
-	if(mapGenOptions.getTeamCount() >= playersCnt)
-	{
-		mapGenOptions.setTeamCount(playersCnt - 1);
-		teamsCntGroup->setSelected(mapGenOptions.getTeamCount());
-	}
-	if(mapGenOptions.getCompOnlyPlayerCount() >= playersCnt)
-	{
-		mapGenOptions.setCompOnlyPlayerCount(playersCnt - 1);
-		compOnlyPlayersCntGroup->setSelected(mapGenOptions.getCompOnlyPlayerCount());
-	}
-
-	validateCompOnlyPlayersCnt(mapGenOptions.getCompOnlyPlayerCount());
-}
-
-void CRandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
-{
-	if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE)
-	{
-		return;
-	}
-
-	if(mapGenOptions.getCompOnlyTeamCount() >= compOnlyPlayersCnt)
-	{
-		int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
-		mapGenOptions.setCompOnlyTeamCount(compOnlyTeamCount);
-		compOnlyTeamsCntGroup->setSelected(compOnlyTeamCount);
-	}
-}
-
-std::vector<int> CRandomMapTab::getPossibleMapSizes()
-{
-	return {CMapHeader::MAP_SIZE_SMALL,CMapHeader::MAP_SIZE_MIDDLE,CMapHeader::MAP_SIZE_LARGE,CMapHeader::MAP_SIZE_XLARGE};
-}
-
-void CRandomMapTab::showAll(SDL_Surface * to)
-{
-	CIntObject::showAll(to);
-
-	// Headline
-	printAtMiddleLoc(CGI->generaltexth->allTexts[738], 222, 36, FONT_BIG, Colors::YELLOW, to);
-	printAtMiddleLoc(CGI->generaltexth->allTexts[739], 222, 56, FONT_SMALL, Colors::WHITE, to);
-
-	// Map size
-	printAtMiddleLoc(CGI->generaltexth->allTexts[752], 104, 97, FONT_SMALL, Colors::WHITE, to);
-
-	// Players cnt
-	printAtLoc(CGI->generaltexth->allTexts[753], 68, 133, FONT_SMALL, Colors::WHITE, to);
-
-	// Teams cnt
-	printAtLoc(CGI->generaltexth->allTexts[754], 68, 199, FONT_SMALL, Colors::WHITE, to);
-
-	// Computer only players cnt
-	printAtLoc(CGI->generaltexth->allTexts[755], 68, 265, FONT_SMALL, Colors::WHITE, to);
-
-	// Computer only teams cnt
-	printAtLoc(CGI->generaltexth->allTexts[756], 68, 331, FONT_SMALL, Colors::WHITE, to);
-
-	// Water content
-	printAtLoc(CGI->generaltexth->allTexts[757], 68, 398, FONT_SMALL, Colors::WHITE, to);
-
-	// Monster strength
-	printAtLoc(CGI->generaltexth->allTexts[758], 68, 465, FONT_SMALL, Colors::WHITE, to);
-}
-
-void CRandomMapTab::updateMapInfo()
-{
-	// Generate header info
-	mapInfo = make_unique<CMapInfo>();
-	mapInfo->isRandomMap = true;
-	mapInfo->mapHeader = make_unique<CMapHeader>();
-	mapInfo->mapHeader->version = EMapFormat::SOD;
-	mapInfo->mapHeader->name = CGI->generaltexth->allTexts[740];
-	mapInfo->mapHeader->description = CGI->generaltexth->allTexts[741];
-	mapInfo->mapHeader->difficulty = 1; // Normal
-	mapInfo->mapHeader->height = mapGenOptions.getHeight();
-	mapInfo->mapHeader->width = mapGenOptions.getWidth();
-	mapInfo->mapHeader->twoLevel = mapGenOptions.getHasTwoLevels();
-
-	// Generate player information
-	mapInfo->mapHeader->players.clear();
-	int playersToGen = PlayerColor::PLAYER_LIMIT_I;
-	if(mapGenOptions.getPlayerCount() != CMapGenOptions::RANDOM_SIZE)
-		playersToGen = mapGenOptions.getPlayerCount();
-	mapInfo->mapHeader->howManyTeams = playersToGen;
-
-	for(int i = 0; i < playersToGen; ++i)
-	{
-		PlayerInfo player;
-		player.isFactionRandom = true;
-		player.canComputerPlay = true;
-		if(mapGenOptions.getCompOnlyPlayerCount() != CMapGenOptions::RANDOM_SIZE &&
-			i >= mapGenOptions.getHumanOnlyPlayerCount())
-		{
-			player.canHumanPlay = false;
-		}
-		else
-		{
-			player.canHumanPlay = true;
-		}
-		player.team = TeamID(i);
-		player.hasMainTown = true;
-		player.generateHeroAtMainTown = true;
-		mapInfo->mapHeader->players.push_back(player);
-	}
-
-	mapInfoChanged(mapInfo.get());
-}
-
-CFunctionList<void(const CMapInfo *)> & CRandomMapTab::getMapInfoChanged()
-{
-	return mapInfoChanged;
-}
-
-const CMapInfo * CRandomMapTab::getMapInfo() const
-{
-	return mapInfo.get();
-}
-
-const CMapGenOptions & CRandomMapTab::getMapGenOptions() const
-{
-	return mapGenOptions;
-}
-
-void CRandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
-{
-	mapSizeBtnGroup->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
-	twoLevelsBtn->setSelected(opts->getHasTwoLevels());
-	playersCntGroup->setSelected(opts->getPlayerCount());
-	teamsCntGroup->setSelected(opts->getTeamCount());
-	compOnlyPlayersCntGroup->setSelected(opts->getCompOnlyPlayerCount());
-	compOnlyTeamsCntGroup->setSelected(opts->getCompOnlyTeamCount());
-	waterContentGroup->setSelected(opts->getWaterContent());
-	monsterStrengthGroup->setSelected(opts->getMonsterStrength());
-}
-
-CChatBox::CChatBox(const Rect &rect)
-{
-	OBJ_CONSTRUCTION;
-	pos += rect;
-	addUsedEvents(KEYBOARD | TEXTINPUT);
-	captureAllKeys = true;
-	type |= REDRAW_PARENT;
-
-	const int height = graphics->fonts[FONT_SMALL]->getLineHeight();
-	inputBox = new CTextInput(Rect(0, rect.h - height, rect.w, height));
-	inputBox->removeUsedEvents(KEYBOARD);
-	chatHistory = new CTextBox("", Rect(0, 0, rect.w, rect.h - height), 1);
-
-	chatHistory->label->color = Colors::GREEN;
-}
-
-void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
-{
-	if(key.keysym.sym == SDLK_RETURN  &&  key.state == SDL_PRESSED  &&  inputBox->text.size())
-	{
-		SEL->postChatMessage(inputBox->text);
-		inputBox->setText("");
-	}
-	else
-		inputBox->keyPressed(key);
-}
-
-void CChatBox::addNewMessage(const std::string &text)
-{
-	CCS->soundh->playSound("CHAT");
-	chatHistory->setText(chatHistory->label->text + text + "\n");
-	if(chatHistory->slider)
-		chatHistory->slider->moveToMax();
-}
-
-InfoCard::InfoCard( bool Network )
-  : sizes(nullptr), bg(nullptr), network(Network), chatOn(false), chat(nullptr), playerListBg(nullptr),
-	difficulty(nullptr)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	CIntObject::type |= REDRAW_PARENT;
-	pos.x += 393;
-	pos.y += 6;
-	addUsedEvents(RCLICK);
-	mapDescription = nullptr;
-
-	Rect descriptionRect(26, 149, 320, 115);
-	mapDescription = new CTextBox("", descriptionRect, 1);
-
-	if(SEL->screenType == CMenuScreen::campaignList)
-	{
-		CSelectionScreen *ss = static_cast<CSelectionScreen*>(parent);
-		mapDescription->addChild(new CPicture(*ss->bg, descriptionRect + Point(-393, 0)), true); //move subpicture bg to our description control (by default it's our (Infocard) child)
-	}
-	else
-	{
-		bg = new CPicture("GSELPOP1.bmp", 0, 0);
-		parent->addChild(bg);
-		auto it = vstd::find(parent->children, this); //our position among parent children
-		parent->children.insert(it, bg); //put BG before us
-		parent->children.pop_back();
-		pos.w = bg->pos.w;
-		pos.h = bg->pos.h;
-		sizes = new CAnimImage("SCNRMPSZ", 4, 0, 318, 22);//let it be custom size (frame 4) by default
-		sizes->recActions &= ~(SHOWALL | UPDATE);//explicit draw
-
-		sFlags = std::make_shared<CAnimation>("ITGFLAGS.DEF");
-		sFlags->load();
-		difficulty = new CToggleGroup(0);
-		{
-			static const char *difButns[] = {"GSPBUT3.DEF", "GSPBUT4.DEF", "GSPBUT5.DEF", "GSPBUT6.DEF", "GSPBUT7.DEF"};
-
-			for(int i = 0; i < 5; i++)
-			{
-				auto button = new CToggleButton(Point(110 + i*32, 450), difButns[i], CGI->generaltexth->zelp[24+i]);
-
-				difficulty->addToggle(i, button);
-				if(SEL->screenType != CMenuScreen::newGame)
-					button->block(true);
-			}
-		}
-
-		if(network)
-		{
-			playerListBg = new CPicture("CHATPLUG.bmp", 16, 276);
-			chat = new CChatBox(Rect(26, 132, 340, 132));
-
-			chatOn = true;
-			mapDescription->disable();
-		}
-	}
-
-	victory = new CAnimImage("SCNRVICT",0, 0, 24, 302);
-	victory->recActions &= ~(SHOWALL | UPDATE);//explicit draw
-	loss = new CAnimImage("SCNRLOSS", 0, 0, 24, 359);
-	loss->recActions &= ~(SHOWALL | UPDATE);//explicit draw
-}
-
-InfoCard::~InfoCard()
-{
-	if(sFlags)
-		sFlags->unload();
-}
-
-void InfoCard::showAll(SDL_Surface * to)
-{
-	CIntObject::showAll(to);
-
-	//blit texts
-	if(SEL->screenType != CMenuScreen::campaignList)
-	{
-		printAtLoc(CGI->generaltexth->allTexts[390] + ":", 24, 400, FONT_SMALL, Colors::WHITE, to); //Allies
-		printAtLoc(CGI->generaltexth->allTexts[391] + ":", 190, 400, FONT_SMALL, Colors::WHITE, to); //Enemies
-		printAtLoc(CGI->generaltexth->allTexts[494], 33, 430, FONT_SMALL, Colors::YELLOW, to);//"Map Diff:"
-		printAtLoc(CGI->generaltexth->allTexts[492] + ":", 133,430, FONT_SMALL, Colors::YELLOW, to); //player difficulty
-		printAtLoc(CGI->generaltexth->allTexts[218] + ":", 290,430, FONT_SMALL, Colors::YELLOW, to); //"Rating:"
-		printAtLoc(CGI->generaltexth->allTexts[495], 26, 22, FONT_SMALL, Colors::YELLOW, to); //Scenario Name:
-		if(!chatOn)
-		{
-			printAtLoc(CGI->generaltexth->allTexts[496], 26, 132, FONT_SMALL, Colors::YELLOW, to); //Scenario Description:
-			printAtLoc(CGI->generaltexth->allTexts[497], 26, 283, FONT_SMALL, Colors::YELLOW, to); //Victory Condition:
-			printAtLoc(CGI->generaltexth->allTexts[498], 26, 339, FONT_SMALL, Colors::YELLOW, to); //Loss Condition:
-		}
-		else //players list
-		{
-			std::map<ui8, std::string> playerNames = SEL->playerNames;
-			int playerSoFar = 0;
-			for (auto i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++)
-			{
-				if(i->second.playerID != PlayerSettings::PLAYER_AI)
-				{
-					printAtLoc(i->second.name, 24, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->getLineHeight(), FONT_SMALL, Colors::WHITE, to);
-					playerNames.erase(i->second.playerID);
-				}
-			}
-
-			playerSoFar = 0;
-			for (auto i = playerNames.cbegin(); i != playerNames.cend(); i++)
-			{
-				printAtLoc(i->second, 193, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->getLineHeight(), FONT_SMALL, Colors::WHITE, to);
-			}
-
-		}
-	}
-
-	if(SEL->current)
-	{
-		if(SEL->screenType != CMenuScreen::campaignList)
-		{
-			if(!chatOn)
-			{
-				CMapHeader * header = SEL->current->mapHeader.get();
-				//victory conditions
-				printAtLoc(header->victoryMessage, 60, 307, FONT_SMALL, Colors::WHITE, to);
-				victory->setFrame(header->victoryIconIndex);
-				victory->showAll(to);
-				//loss conditoins
-				printAtLoc(header->defeatMessage, 60, 366, FONT_SMALL, Colors::WHITE, to);
-				loss->setFrame(header->defeatIconIndex);
-				loss->showAll(to);
-			}
-
-			//difficulty
-			assert(SEL->current->mapHeader->difficulty <= 4);
-			std::string &diff = CGI->generaltexth->arraytxt[142 + SEL->current->mapHeader->difficulty];
-			printAtMiddleLoc(diff, 62, 472, FONT_SMALL, Colors::WHITE, to);
-
-			//selecting size icon
-			switch (SEL->current->mapHeader->width)
-			{
-			case 36:
-				sizes->setFrame(0);
-				break;
-			case 72:
-				sizes->setFrame(1);
-				break;
-			case 108:
-				sizes->setFrame(2);
-				break;
-			case 144:
-				sizes->setFrame(3);
-				break;
-			default:
-				sizes->setFrame(4);
-				break;
-			}
-			sizes->showAll(to);
-
-			if(SEL->screenType == CMenuScreen::loadGame)
-				printToLoc((static_cast<const CMapInfo*>(SEL->current))->date,308,34, FONT_SMALL, Colors::WHITE, to);
-
-			//print flags
-			int fx = 34  + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[390]);
-			int ex = 200 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[391]);
-
-			TeamID myT;
-
-			if(playerColor < PlayerColor::PLAYER_LIMIT)
-				myT = SEL->current->mapHeader->players[playerColor.getNum()].team;
-			else
-				myT = TeamID::NO_TEAM;
-
-			for (auto i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++)
-			{
-				int *myx = ((i->first == playerColor  ||  SEL->current->mapHeader->players[i->first.getNum()].team == myT) ? &fx : &ex);
-				auto flag = sFlags->getImage(i->first.getNum(),0);
-				flag->draw(to, pos.x + *myx, pos.y + 399);
-				*myx += flag->width();
-				flag->decreaseRef();
-			}
-
-			std::string tob;
-			switch (SEL->sInfo.difficulty)
-			{
-			case 0:
-				tob="80%";
-				break;
-			case 1:
-				tob="100%";
-				break;
-			case 2:
-				tob="130%";
-				break;
-			case 3:
-				tob="160%";
-				break;
-			case 4:
-				tob="200%";
-				break;
-			}
-			printAtMiddleLoc(tob, 311, 472, FONT_SMALL, Colors::WHITE, to);
-		}
-
-		//blit description
-		std::string name;
-
-		if (SEL->screenType == CMenuScreen::campaignList)
-		{
-			name = SEL->current->campaignHeader->name;
-		}
-		else
-		{
-			name = SEL->current->mapHeader->name;
-		}
-
-		//name
-		if (name.length())
-			printAtLoc(name, 26, 39, FONT_BIG, Colors::YELLOW, to);
-		else
-			printAtLoc("Unnamed", 26, 39, FONT_BIG, Colors::YELLOW, to);
-	}
-}
-
-void InfoCard::changeSelection( const CMapInfo *to )
-{
-	if(to && mapDescription)
-	{
-		if (SEL->screenType == CMenuScreen::campaignList)
-			mapDescription->setText(to->campaignHeader->description);
-		else
-			mapDescription->setText(to->mapHeader->description);
-
-		mapDescription->label->scrollTextTo(0);
-		if (mapDescription->slider)
-			mapDescription->slider->moveToMin();
-
-		if(SEL->screenType != CMenuScreen::newGame && SEL->screenType != CMenuScreen::campaignList) {
-			//difficulty->block(true);
-			difficulty->setSelected(SEL->sInfo.difficulty);
-		}
-	}
-	redraw();
-}
-
-void InfoCard::clickRight( tribool down, bool previousState )
-{
-	static const Rect flagArea(19, 397, 335, 23);
-	if(SEL->current && down && isItInLoc(flagArea, GH.current->motion.x, GH.current->motion.y))
-		showTeamsPopup();
-}
-
-void InfoCard::showTeamsPopup()
-{
-	SDL_Surface *bmp = CMessage::drawDialogBox(256, 90 + 50 * SEL->current->mapHeader->howManyTeams);
-
-	graphics->fonts[FONT_MEDIUM]->renderTextCenter(bmp, CGI->generaltexth->allTexts[657], Colors::YELLOW, Point(128, 30));
-
-	for(int i = 0; i < SEL->current->mapHeader->howManyTeams; i++)
-	{
-		std::vector<ui8> flags;
-		std::string hlp = CGI->generaltexth->allTexts[656]; //Team %d
-		hlp.replace(hlp.find("%d"), 2, boost::lexical_cast<std::string>(i+1));
-
-		graphics->fonts[FONT_SMALL]->renderTextCenter(bmp, hlp, Colors::WHITE, Point(128, 65 + 50 * i));
-
-		for(int j = 0; j < PlayerColor::PLAYER_LIMIT_I; j++)
-			if((SEL->current->mapHeader->players[j].canHumanPlay || SEL->current->mapHeader->players[j].canComputerPlay)
-				&& SEL->current->mapHeader->players[j].team == TeamID(i))
-				flags.push_back(j);
-
-		int curx = 128 - 9*flags.size();
-		for(auto & flag : flags)
-		{
-			auto icon = sFlags->getImage(flag,0);
-			icon->draw(bmp, curx, 75 + 50*i);
-			icon->decreaseRef();
-			curx += 18;
-		}
-	}
-
-	GH.pushInt(new CInfoPopup(bmp, true));
-}
-
-void InfoCard::toggleChat()
-{
-	setChat(!chatOn);
-}
-
-void InfoCard::setChat(bool activateChat)
-{
-	if(chatOn == activateChat)
-		return;
-
-	assert(active);
-
-	if(activateChat)
-	{
-		mapDescription->disable();
-		chat->enable();
-		playerListBg->enable();
-	}
-	else
-	{
-		mapDescription->enable();
-		chat->disable();
-		playerListBg->disable();
-	}
-
-	chatOn = activateChat;
-	GH.totalRedraw();
-}
-
-OptionsTab::OptionsTab():
-	turnDuration(nullptr)
-{
-	OBJ_CONSTRUCTION;
-	bg = new CPicture("ADVOPTBK", 0, 6);
-	pos = bg->pos;
-
-	if(SEL->screenType == CMenuScreen::newGame)
-		turnDuration = new CSlider(Point(55, 551), 194, std::bind(&OptionsTab::setTurnLength, this, _1), 1, 11, 11, true, CSlider::BLUE);
-}
-
-OptionsTab::~OptionsTab()
-{
-
-}
-
-void OptionsTab::showAll(SDL_Surface * to)
-{
-	CIntObject::showAll(to);
-	printAtMiddleLoc(CGI->generaltexth->allTexts[515], 222, 30, FONT_BIG, Colors::YELLOW, to);
-	printAtMiddleWBLoc(CGI->generaltexth->allTexts[516], 222, 68, FONT_SMALL, 300, Colors::WHITE, to); //Select starting options, handicap, and name for each player in the game.
-	printAtMiddleWBLoc(CGI->generaltexth->allTexts[517], 107, 110, FONT_SMALL, 100, Colors::YELLOW, to); //Player Name Handicap Type
-	printAtMiddleWBLoc(CGI->generaltexth->allTexts[518], 197, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Town
-	printAtMiddleWBLoc(CGI->generaltexth->allTexts[519], 273, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Hero
-	printAtMiddleWBLoc(CGI->generaltexth->allTexts[520], 349, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Bonus
-	printAtMiddleLoc(CGI->generaltexth->allTexts[521], 222, 538, FONT_SMALL, Colors::YELLOW, to); // Player Turn Duration
-	if (turnDuration)
-		printAtMiddleLoc(CGI->generaltexth->turnDurations[turnDuration->getValue()], 319,559, FONT_SMALL, Colors::WHITE, to);//Turn duration value
-}
-
-void OptionsTab::nextCastle( PlayerColor player, int dir )
-{
-	if(SEL->isGuest())
-	{
-		SEL->postRequest(RequestOptionsChange::TOWN, dir);
-		return;
-	}
-
-	PlayerSettings &s = SEL->sInfo.playerInfos[player];
-	si16 &cur = s.castle;
-	auto & allowed = SEL->current->mapHeader->players[s.color.getNum()].allowedFactions;
-	const bool allowRandomTown = SEL->current->mapHeader->players[s.color.getNum()].isFactionRandom;
-
-	if (cur == PlayerSettings::NONE) //no change
-		return;
-
-	if (cur == PlayerSettings::RANDOM) //first/last available
-	{
-		if (dir > 0)
-			cur = *allowed.begin(); //id of first town
-		else
-			cur = *allowed.rbegin(); //id of last town
-
-	}
-	else // next/previous available
-	{
-		if((cur == *allowed.begin() && dir < 0 ) || (cur == *allowed.rbegin() && dir > 0))
-		{
-			if(allowRandomTown)
-			{
-				cur = PlayerSettings::RANDOM;
-			}
-			else
-			{
-				if (dir > 0)
-					cur = *allowed.begin();
-				else
-					cur = *allowed.rbegin();
-			}
-		}
-		else
-		{
-			assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range
-			auto iter = allowed.find(cur);
-			std::advance(iter, dir);
-			cur = *iter;
-		}
-	}
-
-	if(s.hero >= 0 && !SEL->current->mapHeader->players[s.color.getNum()].hasCustomMainHero()) // remove hero unless it set to fixed one in map editor
-	{
-		usedHeroes.erase(s.hero); // restore previously selected hero back to available pool
-		s.hero =  PlayerSettings::RANDOM;
-	}
-	if(cur < 0  &&  s.bonus == PlayerSettings::RESOURCE)
-		s.bonus = PlayerSettings::RANDOM;
-
-	entries[player]->selectButtons();
-
-	SEL->propagateOptions();
-	entries[player]->update();
-	redraw();
-}
-
-void OptionsTab::nextHero( PlayerColor player, int dir )
-{
-	if(SEL->isGuest())
-	{
-		SEL->postRequest(RequestOptionsChange::HERO, dir);
-		return;
-	}
-
-	PlayerSettings &s = SEL->sInfo.playerInfos[player];
-	int old = s.hero;
-	if (s.castle < 0  ||  s.playerID == PlayerSettings::PLAYER_AI  ||  s.hero == PlayerSettings::NONE)
-		return;
-
-	if (s.hero == PlayerSettings::RANDOM) // first/last available
-	{
-		int max = CGI->heroh->heroes.size(),
-			min = 0;
-		s.hero = nextAllowedHero(player, min,max,0,dir);
-	}
-	else
-	{
-		if(dir > 0)
-			s.hero = nextAllowedHero(player, s.hero, CGI->heroh->heroes.size(), 1, dir);
-		else
-			s.hero = nextAllowedHero(player, -1, s.hero, 1, dir); // min needs to be -1 -- hero at index 0 would be skipped otherwise
-	}
-
-	if(old != s.hero)
-	{
-		usedHeroes.erase(old);
-		usedHeroes.insert(s.hero);
-		entries[player]->update();
-		redraw();
-	}
-	SEL->propagateOptions();
-}
-
-int OptionsTab::nextAllowedHero( PlayerColor player, int min, int max, int incl, int dir )
-{
-	if(dir>0)
-	{
-		for(int i=min+incl; i<=max-incl; i++)
-			if(canUseThisHero(player, i))
-				return i;
-	}
-	else
-	{
-		for(int i=max-incl; i>=min+incl; i--)
-			if(canUseThisHero(player, i))
-				return i;
-	}
-	return -1;
-}
-
-bool OptionsTab::canUseThisHero( PlayerColor player, int ID )
-{
-	return CGI->heroh->heroes.size() > ID
-	        && SEL->sInfo.playerInfos[player].castle == CGI->heroh->heroes[ID]->heroClass->faction
-	        && !vstd::contains(usedHeroes, ID)
-	        && SEL->current->mapHeader->allowedHeroes[ID];
-}
-
-void OptionsTab::nextBonus( PlayerColor player, int dir )
-{
-	if(SEL->isGuest())
-	{
-		SEL->postRequest(RequestOptionsChange::BONUS, dir);
-		return;
-	}
-
-	PlayerSettings &s = SEL->sInfo.playerInfos[player];
-	PlayerSettings::Ebonus &ret = s.bonus = static_cast<PlayerSettings::Ebonus>(static_cast<int>(s.bonus) + dir);
-
-	if (s.hero==PlayerSettings::NONE &&
-		!SEL->current->mapHeader->players[s.color.getNum()].heroesNames.size() &&
-		ret==PlayerSettings::ARTIFACT) //no hero - can't be artifact
-	{
-		if (dir<0)
-			ret=PlayerSettings::RANDOM;
-		else ret=PlayerSettings::GOLD;
-	}
-
-	if(ret > PlayerSettings::RESOURCE)
-		ret = PlayerSettings::RANDOM;
-	if(ret < PlayerSettings::RANDOM)
-		ret = PlayerSettings::RESOURCE;
-
-	if (s.castle==PlayerSettings::RANDOM && ret==PlayerSettings::RESOURCE) //random castle - can't be resource
-	{
-		if (dir<0)
-			ret=PlayerSettings::GOLD;
-		else ret=PlayerSettings::RANDOM;
-	}
-
-	SEL->propagateOptions();
-	entries[player]->update();
-	redraw();
-}
-
-void OptionsTab::recreate()
-{
-	for(auto & elem : entries)
-	{
-		delete elem.second;
-	}
-	entries.clear();
-	usedHeroes.clear();
-
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	for(auto it = SEL->sInfo.playerInfos.begin(); it != SEL->sInfo.playerInfos.end(); ++it)
-	{
-		entries.insert(std::make_pair(it->first, new PlayerOptionsEntry(this, it->second)));
-		const std::vector<SHeroName> &heroes = SEL->current->mapHeader->players[it->first.getNum()].heroesNames;
-		for(auto & heroe : heroes)
-			if(heroe.heroId >= 0)//in VCMI map format heroId = -1 means random hero
-				usedHeroes.insert(heroe.heroId);
-	}
-}
-
-void OptionsTab::setTurnLength( int npos )
-{
-	static const int times[] = {1, 2, 4, 6, 8, 10, 15, 20, 25, 30, 0};
-	vstd::amin(npos, ARRAY_COUNT(times) - 1);
-
-	SEL->sInfo.turnTime = times[npos];
-	redraw();
-}
-
-void OptionsTab::flagPressed( PlayerColor color )
-{
-	PlayerSettings &clicked =  SEL->sInfo.playerInfos[color];
-	PlayerSettings *old = nullptr;
-
-	if(SEL->playerNames.size() == 1) //single player -> just swap
-	{
-		if(color == playerColor) //that color is already selected, no action needed
-			return;
-
-		old = &SEL->sInfo.playerInfos[playerColor];
-		swapPlayers(*old, clicked);
-	}
-	else
-	{
-		//identify clicked player
-		int clickedNameID = clicked.playerID; //human is a number of player, zero means AI
-
-		if(clickedNameID > 0  &&  playerToRestore.id == clickedNameID) //player to restore is about to being replaced -> put him back to the old place
-		{
-			PlayerSettings &restPos = SEL->sInfo.playerInfos[playerToRestore.color];
-			SEL->setPlayer(restPos, playerToRestore.id);
-			playerToRestore.reset();
-		}
-
-		int newPlayer; //which player will take clicked position
-
-		//who will be put here?
-		if(!clickedNameID) //AI player clicked -> if possible replace computer with unallocated player
-		{
-			newPlayer = SEL->getIdOfFirstUnallocatedPlayer();
-			if(!newPlayer) //no "free" player -> get just first one
-				newPlayer = SEL->playerNames.begin()->first;
-		}
-		else //human clicked -> take next
-		{
-			auto i = SEL->playerNames.find(clickedNameID); //clicked one
-			i++; //player AFTER clicked one
-
-			if(i != SEL->playerNames.end())
-				newPlayer = i->first;
-			else
-				newPlayer = 0; //AI if we scrolled through all players
-		}
-
-		SEL->setPlayer(clicked, newPlayer); //put player
-
-		//if that player was somewhere else, we need to replace him with computer
-		if(newPlayer) //not AI
-		{
-			for(auto i = SEL->sInfo.playerInfos.begin(); i != SEL->sInfo.playerInfos.end(); i++)
-			{
-				int curNameID = i->second.playerID;
-				if(i->first != color  &&  curNameID == newPlayer)
-				{
-					assert(i->second.playerID);
-					playerToRestore.color = i->first;
-					playerToRestore.id = newPlayer;
-					SEL->setPlayer(i->second, 0); //set computer
-					old = &i->second;
-					break;
-				}
-			}
-		}
-	}
-
-	entries[clicked.color]->selectButtons();
-	if(old)
-	{
-		entries[old->color]->selectButtons();
-		if(old->hero >= 0)
-			usedHeroes.erase(old->hero);
-
-		old->hero = entries[old->color]->pi.defaultHero();
-		entries[old->color]->update(); // update previous frame images in case entries were auto-updated
-	}
-
-	SEL->propagateOptions();
-	GH.totalRedraw();
-}
-
-OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry( OptionsTab *owner, PlayerSettings &S)
- : pi(SEL->current->mapHeader->players[S.color.getNum()]), s(S)
-{
-	OBJ_CONSTRUCTION;
-	defActions |= SHARE_POS;
-
-	int serial = 0;
-	for(int g=0; g < s.color.getNum(); ++g)
-	{
-		PlayerInfo &itred = SEL->current->mapHeader->players[g];
-		if( itred.canComputerPlay || itred.canHumanPlay)
-			serial++;
-	}
-
-	pos.x += 54;
-	pos.y += 122 + serial*50;
-
-	static const char *flags[] = {"AOFLGBR.DEF", "AOFLGBB.DEF", "AOFLGBY.DEF", "AOFLGBG.DEF",
-		"AOFLGBO.DEF", "AOFLGBP.DEF", "AOFLGBT.DEF", "AOFLGBS.DEF"};
-	static const char *bgs[] = {"ADOPRPNL.bmp", "ADOPBPNL.bmp", "ADOPYPNL.bmp", "ADOPGPNL.bmp",
-		"ADOPOPNL.bmp", "ADOPPPNL.bmp", "ADOPTPNL.bmp", "ADOPSPNL.bmp"};
-
-	bg = new CPicture(BitmapHandler::loadBitmap(bgs[s.color.getNum()]), 0, 0, true);
-	if(SEL->screenType == CMenuScreen::newGame)
-	{
-		btns[0] = new CButton(Point(107, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[132], std::bind(&OptionsTab::nextCastle, owner, s.color, -1));
-		btns[1] = new CButton(Point(168, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[133], std::bind(&OptionsTab::nextCastle, owner, s.color, +1));
-		btns[2] = new CButton(Point(183, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[148], std::bind(&OptionsTab::nextHero, owner, s.color, -1));
-		btns[3] = new CButton(Point(244, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[149], std::bind(&OptionsTab::nextHero, owner, s.color, +1));
-		btns[4] = new CButton(Point(259, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[164], std::bind(&OptionsTab::nextBonus, owner, s.color, -1));
-		btns[5] = new CButton(Point(320, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[165], std::bind(&OptionsTab::nextBonus, owner, s.color, +1));
-	}
-	else
-		for(auto & elem : btns)
-			elem = nullptr;
-
-	selectButtons();
-
-	assert(SEL->current && SEL->current->mapHeader);
-	const PlayerInfo &p = SEL->current->mapHeader->players[s.color.getNum()];
-	assert(p.canComputerPlay || p.canHumanPlay); //someone must be able to control this player
-	if(p.canHumanPlay && p.canComputerPlay)
-		whoCanPlay = HUMAN_OR_CPU;
-	else if(p.canComputerPlay)
-		whoCanPlay = CPU;
-	else
-		whoCanPlay = HUMAN;
-
-	if(SEL->screenType != CMenuScreen::scenarioInfo
-		&&  SEL->current->mapHeader->players[s.color.getNum()].canHumanPlay)
-	{
-		flag = new CButton(Point(-43, 2), flags[s.color.getNum()], CGI->generaltexth->zelp[180], std::bind(&OptionsTab::flagPressed, owner, s.color));
-		flag->hoverable = true;
-		flag->block(SEL->isGuest());
-	}
-	else
-		flag = nullptr;
-
-	town = new SelectedBox(Point(119, 2), s, TOWN);
-	hero = new SelectedBox(Point(195, 2), s, HERO);
-	bonus = new SelectedBox(Point(271, 2), s, BONUS);
-}
-
-void OptionsTab::PlayerOptionsEntry::showAll(SDL_Surface * to)
-{
-	CIntObject::showAll(to);
-	printAtMiddleLoc(s.name, 55, 10, FONT_SMALL, Colors::WHITE, to);
-	printAtMiddleWBLoc(CGI->generaltexth->arraytxt[206+whoCanPlay], 28, 39, FONT_TINY, 50, Colors::WHITE, to);
-}
-
-void OptionsTab::PlayerOptionsEntry::update()
-{
-	town->update();
-	hero->update();
-	bonus->update();
-}
-
-void OptionsTab::PlayerOptionsEntry::selectButtons()
-{
-	if(!btns[0])
-		return;
-
-	const bool foreignPlayer = SEL->isGuest() && s.color != playerColor;
-
-	if( (pi.allowedFactions.size() < 2 && !pi.isFactionRandom) || foreignPlayer)
-	{
-		btns[0]->disable();
-		btns[1]->disable();
-	}
-	else
-	{
-		btns[0]->enable();
-		btns[1]->enable();
-	}
-
-	if( (pi.defaultHero() != -1  ||  !s.playerID  ||  s.castle < 0) //fixed hero
-		|| foreignPlayer)//or not our player
-	{
-		btns[2]->disable();
-		btns[3]->disable();
-	}
-	else
-	{
-		btns[2]->enable();
-		btns[3]->enable();
-	}
-
-	if(foreignPlayer)
-	{
-		btns[4]->disable();
-		btns[5]->disable();
-	}
-	else
-	{
-		btns[4]->enable();
-		btns[5]->enable();
-	}
-}
-
-size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
-{
-	enum EBonusSelection //frames of bonuses file
-	{
-		WOOD_ORE = 0,   CRYSTAL = 1,    GEM  = 2,
-		MERCURY  = 3,   SULFUR  = 5,    GOLD = 8,
-		ARTIFACT = 9,   RANDOM  = 10,
-		WOOD = 0,       ORE     = 0,    MITHRIL = 10, // resources unavailable in bonuses file
-
-		TOWN_RANDOM = 38,  TOWN_NONE = 39, // Special frames in ITPA
-		HERO_RANDOM = 163, HERO_NONE = 164 // Special frames in PortraitsSmall
-	};
-
-	switch(type)
-	{
-	case TOWN:
-		switch (settings.castle)
-		{
-		case PlayerSettings::NONE:   return TOWN_NONE;
-		case PlayerSettings::RANDOM: return TOWN_RANDOM;
-		default: return CGI->townh->factions[settings.castle]->town->clientInfo.icons[true][false] + 2;
-		}
-
-	case HERO:
-		switch (settings.hero)
-		{
-		case PlayerSettings::NONE:   return HERO_NONE;
-		case PlayerSettings::RANDOM: return HERO_RANDOM;
-		default:
-			{
-				if(settings.heroPortrait >= 0)
-					return settings.heroPortrait;
-				return CGI->heroh->heroes[settings.hero]->imageIndex;
-			}
-		}
-
-	case BONUS:
-		{
-			switch(settings.bonus)
-			{
-			case PlayerSettings::RANDOM:   return RANDOM;
-			case PlayerSettings::ARTIFACT: return ARTIFACT;
-			case PlayerSettings::GOLD:     return GOLD;
-			case PlayerSettings::RESOURCE:
-				{
-					switch(CGI->townh->factions[settings.castle]->town->primaryRes)
-					{
-					case Res::WOOD_AND_ORE : return WOOD_ORE;
-					case Res::WOOD    : return WOOD;
-					case Res::MERCURY : return MERCURY;
-					case Res::ORE     : return ORE;
-					case Res::SULFUR  : return SULFUR;
-					case Res::CRYSTAL : return CRYSTAL;
-					case Res::GEMS    : return GEM;
-					case Res::GOLD    : return GOLD;
-					case Res::MITHRIL : return MITHRIL;
-					}
-				}
-			}
-		}
-	}
-	return 0;
-}
-
-std::string OptionsTab::CPlayerSettingsHelper::getImageName()
-{
-	switch(type)
-	{
-	case OptionsTab::TOWN:  return "ITPA";
-	case OptionsTab::HERO:  return "PortraitsSmall";
-	case OptionsTab::BONUS: return "SCNRSTAR";
-	}
-	return "";
-}
-
-std::string OptionsTab::CPlayerSettingsHelper::getTitle()
-{
-	switch(type)
-	{
-	case OptionsTab::TOWN: return (settings.castle < 0) ? CGI->generaltexth->allTexts[103] : CGI->generaltexth->allTexts[80];
-	case OptionsTab::HERO: return (settings.hero   < 0) ? CGI->generaltexth->allTexts[101] : CGI->generaltexth->allTexts[77];
-	case OptionsTab::BONUS:
-		{
-			switch(settings.bonus)
-			{
-			case PlayerSettings::RANDOM:   return CGI->generaltexth->allTexts[86]; //{Random Bonus}
-			case PlayerSettings::ARTIFACT: return CGI->generaltexth->allTexts[83]; //{Artifact Bonus}
-			case PlayerSettings::GOLD:     return CGI->generaltexth->allTexts[84]; //{Gold Bonus}
-			case PlayerSettings::RESOURCE: return CGI->generaltexth->allTexts[85]; //{Resource Bonus}
-			}
-		}
-	}
-	return "";
-}
-
-std::string OptionsTab::CPlayerSettingsHelper::getName()
-{
-	switch(type)
-	{
-	case TOWN:
-		{
-			switch (settings.castle)
-			{
-			case PlayerSettings::NONE   : return CGI->generaltexth->allTexts[523];
-			case PlayerSettings::RANDOM : return CGI->generaltexth->allTexts[522];
-			default : return CGI->townh->factions[settings.castle]->name;
-			}
-		}
-	case HERO:
-		{
-			switch (settings.hero)
-			{
-			case PlayerSettings::NONE   : return CGI->generaltexth->allTexts[523];
-			case PlayerSettings::RANDOM : return CGI->generaltexth->allTexts[522];
-			default :
-				{
-					if (!settings.heroName.empty())
-						return settings.heroName;
-					return CGI->heroh->heroes[settings.hero]->name;
-				}
-			}
-		}
-	case BONUS:
-		{
-			switch (settings.bonus)
-			{
-				case PlayerSettings::RANDOM : return CGI->generaltexth->allTexts[522];
-				default: return CGI->generaltexth->arraytxt[214 + settings.bonus];
-			}
-		}
-	}
-	return "";
-}
-
-std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
-{
-	switch(type)
-	{
-	case TOWN: return getName();
-	case HERO:
-		{
-			if (settings.hero >= 0)
-				return getName() + " - " + CGI->heroh->heroes[settings.hero]->heroClass->name;
-			return getName();
-		}
-
-	case BONUS:
-		{
-			switch(settings.bonus)
-			{
-			case PlayerSettings::GOLD:     return CGI->generaltexth->allTexts[87]; //500-1000
-			case PlayerSettings::RESOURCE:
-				{
-					switch(CGI->townh->factions[settings.castle]->town->primaryRes)
-					{
-					case Res::MERCURY: return CGI->generaltexth->allTexts[694];
-					case Res::SULFUR:  return CGI->generaltexth->allTexts[695];
-					case Res::CRYSTAL: return CGI->generaltexth->allTexts[692];
-					case Res::GEMS:    return CGI->generaltexth->allTexts[693];
-					case Res::WOOD_AND_ORE: return CGI->generaltexth->allTexts[89]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
-					}
-				}
-			}
-		}
-	}
-	return "";
-}
-
-std::string OptionsTab::CPlayerSettingsHelper::getDescription()
-{
-	switch(type)
-	{
-	case TOWN: return CGI->generaltexth->allTexts[104];
-	case HERO: return CGI->generaltexth->allTexts[102];
-	case BONUS:
-		{
-			switch(settings.bonus)
-			{
-			case PlayerSettings::RANDOM:   return CGI->generaltexth->allTexts[94]; //Gold, wood and ore, or an artifact is randomly chosen as your starting bonus
-			case PlayerSettings::ARTIFACT: return CGI->generaltexth->allTexts[90]; //An artifact is randomly chosen and equipped to your starting hero
-			case PlayerSettings::GOLD:     return CGI->generaltexth->allTexts[92]; //At the start of the game, 500-1000 gold is added to your Kingdom's resource pool
-			case PlayerSettings::RESOURCE:
-				{
-					switch(CGI->townh->factions[settings.castle]->town->primaryRes)
-					{
-					case Res::MERCURY: return CGI->generaltexth->allTexts[690];
-					case Res::SULFUR:  return CGI->generaltexth->allTexts[691];
-					case Res::CRYSTAL: return CGI->generaltexth->allTexts[688];
-					case Res::GEMS:    return CGI->generaltexth->allTexts[689];
-					case Res::WOOD_AND_ORE: return CGI->generaltexth->allTexts[93]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
-					}
-				}
-			}
-		}
-	}
-	return "";
-}
-
-OptionsTab::CPregameTooltipBox::CPregameTooltipBox(CPlayerSettingsHelper & helper):
-	CWindowObject(BORDERED | RCLICK_POPUP),
-	CPlayerSettingsHelper(helper)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	int value = PlayerSettings::NONE;
-
-	switch(CPlayerSettingsHelper::type)
-	{
-	break; case TOWN:
-			value = settings.castle;
-	break; case HERO:
-			value = settings.hero;
-	break; case BONUS:
-			value = settings.bonus;
-	}
-
-	if (value == PlayerSettings::RANDOM)
-		genBonusWindow();
-	else if (CPlayerSettingsHelper::type == BONUS)
-		genBonusWindow();
-	else if (CPlayerSettingsHelper::type == HERO)
-		genHeroWindow();
-	else if (CPlayerSettingsHelper::type == TOWN)
-		genTownWindow();
-
-	center();
-}
-
-void OptionsTab::CPregameTooltipBox::genHeader()
-{
-	new CFilledTexture("DIBOXBCK", pos);
-	updateShadow();
-
-	new CLabel(pos.w / 2 + 8, 21, FONT_MEDIUM, CENTER, Colors::YELLOW, getTitle());
-
-	new CLabel(pos.w / 2, 88, FONT_SMALL, CENTER, Colors::WHITE, getSubtitle());
-
-	new CAnimImage(getImageName(), getImageIndex(), 0, pos.w / 2 - 24, 45);
-}
-
-void OptionsTab::CPregameTooltipBox::genTownWindow()
-{
-	pos = Rect(0, 0, 228, 290);
-	genHeader();
-
-	new CLabel(pos.w / 2 + 8, 122, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[79]);
-
-	std::vector<CComponent *> components;
-	const CTown * town = CGI->townh->factions[settings.castle]->town;
-
-	for (auto & elem : town->creatures)
-	{
-		if (!elem.empty())
-			components.push_back(new CComponent(CComponent::creature, elem.front(), 0, CComponent::tiny));
-	}
-
-	new CComponentBox(components, Rect(10, 140, pos.w - 20, 140));
-}
-
-void OptionsTab::CPregameTooltipBox::genHeroWindow()
-{
-	pos = Rect(0, 0, 292, 226);
-	genHeader();
-
-	// specialty
-	new CAnimImage("UN44", CGI->heroh->heroes[settings.hero]->imageIndex, 0, pos.w / 2 - 22, 134);
-
-	new CLabel(pos.w / 2 + 4, 117, FONT_MEDIUM, CENTER,  Colors::YELLOW, CGI->generaltexth->allTexts[78]);
-	new CLabel(pos.w / 2,     188, FONT_SMALL,  CENTER, Colors::WHITE, CGI->heroh->heroes[settings.hero]->specName);
-}
-
-void OptionsTab::CPregameTooltipBox::genBonusWindow()
-{
-	pos = Rect(0, 0, 228, 162);
-	genHeader();
-
-	new CTextBox(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, CENTER, Colors::WHITE );
-}
-
-OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & settings, SelType type)
-	:CIntObject(RCLICK, position),
-	CPlayerSettingsHelper(settings, type)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	image = new CAnimImage(getImageName(), getImageIndex());
-	subtitle = new CLabel(23, 39, FONT_TINY, CENTER, Colors::WHITE, getName());
-
-	pos = image->pos;
-}
-
-void OptionsTab::SelectedBox::update()
-{
-	image->setFrame(getImageIndex());
-	subtitle->setText(getName());
-}
-
-void OptionsTab::SelectedBox::clickRight( tribool down, bool previousState )
-{
-	if (down)
-	{
-		// cases when we do not need to display a message
-		if (settings.castle == -2 && CPlayerSettingsHelper::type == TOWN )
-			return;
-		if (settings.hero == -2 && !SEL->current->mapHeader->players[settings.color.getNum()].hasCustomMainHero() && CPlayerSettingsHelper::type == HERO)
-			return;
-
-		GH.pushInt(new CPregameTooltipBox(*this));
-	}
-}
-
-CScenarioInfo::CScenarioInfo(const CMapHeader *mapHeader, const StartInfo *startInfo)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	for(auto it = startInfo->playerInfos.cbegin(); it != startInfo->playerInfos.cend(); ++it)
-	{
-		if(it->second.playerID)
-		{
-			playerColor = it->first;
-		}
-	}
-
-	pos.w = 762;
-	pos.h = 584;
-	center(pos);
-
-	assert(LOCPLINT);
-	sInfo = *LOCPLINT->cb->getStartInfo();
-	assert(!SEL->current);
-	current = mapInfoFromGame();
-	setPlayersFromGame();
-
-	screenType = CMenuScreen::scenarioInfo;
-
-	card = new InfoCard();
-	opt = new OptionsTab();
-	opt->recreate();
-	card->changeSelection(current);
-
-	card->difficulty->setSelected(startInfo->difficulty);
-	back = new CButton(Point(584, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
-}
-
-CScenarioInfo::~CScenarioInfo()
-{
-	delete current;
-}
-
-bool mapSorter::operator()(const CMapInfo *aaa, const CMapInfo *bbb)
-{
-	const CMapHeader * a = aaa->mapHeader.get(),
-			* b = bbb->mapHeader.get();
-	if(a && b) //if we are sorting scenarios
-	{
-		switch (sortBy)
-		{
-		case _format: //by map format (RoE, WoG, etc)
-			return (a->version<b->version);
-			break;
-		case _loscon: //by loss conditions
-			return (a->defeatMessage < b->defeatMessage);
-			break;
-		case _playerAm: //by player amount
-			int playerAmntB,humenPlayersB,playerAmntA,humenPlayersA;
-			playerAmntB=humenPlayersB=playerAmntA=humenPlayersA=0;
-			for (int i=0;i<8;i++)
-			{
-				if (a->players[i].canHumanPlay) {playerAmntA++;humenPlayersA++;}
-				else if (a->players[i].canComputerPlay) {playerAmntA++;}
-				if (b->players[i].canHumanPlay) {playerAmntB++;humenPlayersB++;}
-				else if (b->players[i].canComputerPlay) {playerAmntB++;}
-			}
-			if (playerAmntB!=playerAmntA)
-				return (playerAmntA<playerAmntB);
-			else
-				return (humenPlayersA<humenPlayersB);
-			break;
-		case _size: //by size of map
-			return (a->width<b->width);
-			break;
-		case _viccon: //by victory conditions
-			return (a->victoryMessage < b->victoryMessage);
-			break;
-		case _name: //by name
-			return boost::ilexicographical_compare(a->name, b->name);
-		case _fileName: //by filename
-			return boost::ilexicographical_compare(aaa->fileURI, bbb->fileURI);
-		default:
-			return boost::ilexicographical_compare(a->name, b->name);
-		}
-	}
-	else //if we are sorting campaigns
-	{
-		switch(sortBy)
-		{
-			case _numOfMaps: //by number of maps in campaign
-				return CGI->generaltexth->campaignRegionNames[ aaa->campaignHeader->mapVersion ].size() <
-					CGI->generaltexth->campaignRegionNames[ bbb->campaignHeader->mapVersion ].size();
-				break;
-			case _name: //by name
-				return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
-			default:
-				return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
-		}
-	}
-}
-
-CMultiMode::CMultiMode()
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	bg = new CPicture("MUPOPUP.bmp");
-	bg->convertToScreenBPP(); //so we could draw without problems
-	blitAt(CPicture("MUMAP.bmp"), 16, 77, *bg); //blit img
-	pos = bg->center(); //center, window has size of bg graphic
-
-	bar = new CGStatusBar(new CPicture(Rect(7, 465, 440, 18), 0));//226, 472
-	txt = new CTextInput(Rect(19, 436, 334, 16), *bg);
-	txt->setText(settings["general"]["playerName"].String()); //Player
-
-	btns[0] = new CButton(Point(373, 78),        "MUBHOT.DEF",  CGI->generaltexth->zelp[266], std::bind(&CMultiMode::openHotseat, this));
-	btns[1] = new CButton(Point(373, 78 + 57*1), "MUBHOST.DEF", CButton::tooltip("Host TCP/IP game", ""), std::bind(&CMultiMode::hostTCP, this));
-	btns[2] = new CButton(Point(373, 78 + 57*2), "MUBJOIN.DEF", CButton::tooltip("Join TCP/IP game", ""), std::bind(&CMultiMode::joinTCP, this));
-	btns[6] = new CButton(Point(373, 424),       "MUBCANC.DEF", CGI->generaltexth->zelp[288], [&](){ GH.popIntTotally(this);}, SDLK_ESCAPE);
-}
-
-void CMultiMode::openHotseat()
-{
-	GH.pushInt(new CHotSeatPlayers(txt->text));
-}
-
-void CMultiMode::hostTCP()
-{
-	Settings name = settings.write["general"]["playerName"];
-	name->String() = txt->text;
-	GH.popIntTotally(this);
-	if(settings["session"]["donotstartserver"].Bool())
-		GH.pushInt(new CSimpleJoinScreen(CMenuScreen::MULTI_NETWORK_HOST));
-	else
-		GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_NETWORK_HOST));
-}
-
-void CMultiMode::joinTCP()
-{
-	Settings name = settings.write["general"]["playerName"];
-	name->String() = txt->text;
-	GH.pushInt(new CSimpleJoinScreen(CMenuScreen::MULTI_NETWORK_GUEST));
-}
-
-CHotSeatPlayers::CHotSeatPlayers(const std::string &firstPlayer)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	bg = new CPicture("MUHOTSEA.bmp");
-	pos = bg->center(); //center, window has size of bg graphic
-
-	std::string text = CGI->generaltexth->allTexts[446];
-	boost::replace_all(text, "\t","\n");
-	Rect boxRect(25, 20, 315, 50);
-	title = new CTextBox(text, boxRect, 0, FONT_BIG, CENTER, Colors::WHITE);//HOTSEAT	Please enter names
-
-	for(int i = 0; i < ARRAY_COUNT(txt); i++)
-	{
-		txt[i] = new CTextInput(Rect(60, 85 + i*30, 280, 16), *bg);
-        txt[i]->cb += std::bind(&CHotSeatPlayers::onChange, this, _1);
-	}
-
-	ok = new CButton(Point(95, 338), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CHotSeatPlayers::enterSelectionScreen, this), SDLK_RETURN);
-	cancel = new CButton(Point(205, 338), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE);
-	bar = new CGStatusBar(new CPicture(Rect(7, 381, 348, 18), 0));//226, 472
-
-	txt[0]->setText(firstPlayer, true);
-	txt[0]->giveFocus();
-}
-
-void CHotSeatPlayers::onChange(std::string newText)
-{
-	size_t namesCount = 0;
-
-	for(auto & elem : txt)
-		if(!elem->text.empty())
-			namesCount++;
-
-	ok->block(namesCount < 2);
-}
-
-void CHotSeatPlayers::enterSelectionScreen()
-{
-	std::map<ui8, std::string> names;
-	for(int i = 0, j = 1; i < ARRAY_COUNT(txt); i++)
-		if(txt[i]->text.length())
-			names[j++] = txt[i]->text;
-
-	Settings name = settings.write["general"]["playerName"];
-	name->String() = names.begin()->second;
-
-	GH.popInts(2); //pop MP mode window and this
-	GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_HOT_SEAT, &names));
-}
-
-void CBonusSelection::init()
-{
-	highlightedRegion = nullptr;
-	ourHeader.reset();
-	diffLb = nullptr;
-	diffRb = nullptr;
-	bonuses = nullptr;
-	selectedMap = 0;
-
-	// Initialize start info
-	startInfo.mapname = ourCampaign->camp->header.filename;
-	startInfo.mode = StartInfo::CAMPAIGN;
-	startInfo.campState = ourCampaign;
-	startInfo.turnTime = 0;
-
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	static const std::string bgNames [] = {"E1_BG.BMP", "G2_BG.BMP", "E2_BG.BMP", "G1_BG.BMP", "G3_BG.BMP", "N1_BG.BMP",
-		"S1_BG.BMP", "BR_BG.BMP", "IS_BG.BMP", "KR_BG.BMP", "NI_BG.BMP", "TA_BG.BMP", "AR_BG.BMP", "HS_BG.BMP",
-		"BB_BG.BMP", "NB_BG.BMP", "EL_BG.BMP", "RN_BG.BMP", "UA_BG.BMP", "SP_BG.BMP"};
-
-	loadPositionsOfGraphics();
-
-	background = BitmapHandler::loadBitmap(bgNames[ourCampaign->camp->header.mapVersion]);
-	pos.h = background->h;
-	pos.w = background->w;
-	center();
-
-	SDL_Surface * panel = BitmapHandler::loadBitmap("CAMPBRF.BMP");
-
-	blitAt(panel, 456, 6, background);
-
-	startB   = new CButton(Point(475, 536), "CBBEGIB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), SDLK_RETURN);
-	restartB = new CButton(Point(475, 536), "CBRESTB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), SDLK_RETURN);
-	backB    = new CButton(Point(624, 536), "CBCANCB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), SDLK_ESCAPE);
-
-	//campaign name
-	if (ourCampaign->camp->header.name.length())
-		graphics->fonts[FONT_BIG]->renderTextLeft(background, ourCampaign->camp->header.name, Colors::YELLOW, Point(481, 28));
-	else
-		graphics->fonts[FONT_BIG]->renderTextLeft(background, CGI->generaltexth->allTexts[508], Colors::YELLOW, Point(481, 28));
-
-	//map size icon
-	sizes = new CAnimImage("SCNRMPSZ",4,0,735, 26);
-	sizes->recActions &= ~(SHOWALL | UPDATE);//explicit draw
-
-	//campaign description
-	graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[38], Colors::YELLOW, Point(481, 63));
-
-	campaignDescription = new CTextBox(ourCampaign->camp->header.description, Rect(480, 86, 286, 117), 1);
-	//campaignDescription->showAll(background);
-
-	//map description
-	mapDescription = new CTextBox("", Rect(480, 280, 286, 117), 1);
-
-	//bonus choosing
-	graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, CGI->generaltexth->allTexts[71], Colors::WHITE, Point(511, 432));
-	bonuses = new CToggleGroup(std::bind(&CBonusSelection::selectBonus, this, _1));
-
-	//set left part of window
-	bool isCurrentMapConquerable = ourCampaign->currentMap && ourCampaign->camp->conquerable(*ourCampaign->currentMap);
-	for(int g = 0; g < ourCampaign->camp->scenarios.size(); ++g)
-	{
-		if(ourCampaign->camp->conquerable(g))
-		{
-			regions.push_back(new CRegion(this, true, true, g));
-			regions[regions.size()-1]->rclickText = ourCampaign->camp->scenarios[g].regionText;
-			if(highlightedRegion == nullptr)
-			{
-				if(!isCurrentMapConquerable || (isCurrentMapConquerable && g == *ourCampaign->currentMap))
-				{
-					highlightedRegion = regions.back();
-					selectMap(g, true);
-				}
-			}
-		}
-		else if (ourCampaign->camp->scenarios[g].conquered) //display as striped
-		{
-			regions.push_back(new CRegion(this, false, false, g));
-			regions[regions.size()-1]->rclickText = ourCampaign->camp->scenarios[g].regionText;
-		}
-	}
-
-	//allies / enemies
-	graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[390] + ":", Colors::WHITE, Point(486, 407));
-	graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[391] + ":", Colors::WHITE, Point(619, 407));
-
-	SDL_FreeSurface(panel);
-
-	//difficulty
-	std::vector<std::string> difficulty;
-	boost::split(difficulty, CGI->generaltexth->allTexts[492], boost::is_any_of(" "));
-	graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, difficulty.back(), Colors::WHITE, Point(689, 432));
-
-	//difficulty pics
-	for (size_t b=0; b < diffPics.size(); ++b)
-	{
-		diffPics[b] = new CAnimImage("GSPBUT" + boost::lexical_cast<std::string>(b+3) + ".DEF", 0, 0, 709, 455);
-		diffPics[b]->recActions &= ~(SHOWALL | UPDATE);//explicit draw
-	}
-
-	//difficulty selection buttons
-	if (ourCampaign->camp->header.difficultyChoosenByPlayer)
-	{
-		diffLb = new CButton(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this));
-		diffRb = new CButton(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this));
-	}
-
-	//load miniflags
-	sFlags = std::make_shared<CAnimation>("ITGFLAGS.DEF");
-	sFlags->load();
-}
-
-CBonusSelection::CBonusSelection(std::shared_ptr<CCampaignState> _ourCampaign) : ourCampaign(_ourCampaign)
-{
-	init();
-}
-
-CBonusSelection::CBonusSelection(const std::string & campaignFName)
-{
-	ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(campaignFName));
-	init();
-}
-
-CBonusSelection::~CBonusSelection()
-{
-	SDL_FreeSurface(background);
-	sFlags->unload();
-}
-
-void CBonusSelection::goBack()
-{
-	GH.popIntTotally(this);
-}
-
-void CBonusSelection::showAll(SDL_Surface * to)
-{
-	blitAt(background, pos.x, pos.y, to);
-	CIntObject::showAll(to);
-
-	show(to);
-	if (pos.h != to->h || pos.w != to->w)
-		CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15);
-}
-
-void CBonusSelection::loadPositionsOfGraphics()
-{
-	const JsonNode config(ResourceID("config/campaign_regions.json"));
-	int idx = 0;
-
-	for(const JsonNode &campaign : config["campaign_regions"].Vector())
-	{
-		SCampPositions sc;
-
-		sc.campPrefix = campaign["prefix"].String();
-		sc.colorSuffixLength = campaign["color_suffix_length"].Float();
-
-		for(const JsonNode &desc :  campaign["desc"].Vector())
-		{
-			SCampPositions::SRegionDesc rd;
-
-			rd.infix = desc["infix"].String();
-			rd.xpos = desc["x"].Float();
-			rd.ypos = desc["y"].Float();
-			sc.regions.push_back(rd);
-		}
-
-		campDescriptions.push_back(sc);
-
-		idx++;
-	}
-
-	assert(idx == CGI->generaltexth->campaignMapNames.size());
-}
-
-void CBonusSelection::selectMap(int mapNr, bool initialSelect)
-{
-	if(initialSelect || selectedMap != mapNr)
-	{
-		// initialize restart / start button
-		if(!ourCampaign->currentMap || *ourCampaign->currentMap != mapNr)
-		{
-			// draw start button
-			restartB->disable();
-			startB->enable();
-			if (!ourCampaign->mapsConquered.empty())
-				backB->block(true);
-			else
-				backB->block(false);
-		}
-		else
-		{
-			// draw restart button
-			startB->disable();
-			restartB->enable();
-			backB->block(false);
-		}
-
-		startInfo.difficulty = ourCampaign->camp->scenarios[mapNr].difficulty;
-		selectedMap = mapNr;
-		selectedBonus = boost::none;
-
-		std::string scenarioName = ourCampaign->camp->header.filename.substr(0, ourCampaign->camp->header.filename.find('.'));
-		boost::to_lower(scenarioName);
-		scenarioName += ':' + boost::lexical_cast<std::string>(selectedMap);
-
-		//get header
-		std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
-		auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
-		CMapService mapService;
-		ourHeader = mapService.loadMapHeader(buffer, headerStr.size(), scenarioName);
-
-		std::map<ui8, std::string> names;
-		names[1] = settings["general"]["playerName"].String();
-		updateStartInfo(ourCampaign->camp->header.filename, startInfo, ourHeader, names);
-
-		mapDescription->setText(ourHeader->description);
-
-		updateBonusSelection();
-
-		GH.totalRedraw();
-	}
-}
-
-void CBonusSelection::show(SDL_Surface * to)
-{
-	//map name
-	std::string mapName = ourHeader->name;
-
-	if (mapName.length())
-		printAtLoc(mapName, 481, 219, FONT_BIG, Colors::YELLOW, to);
-	else
-		printAtLoc("Unnamed", 481, 219, FONT_BIG, Colors::YELLOW, to);
-
-	//map description
-	printAtLoc(CGI->generaltexth->allTexts[496], 481, 253, FONT_SMALL, Colors::YELLOW, to);
-
-	mapDescription->showAll(to); //showAll because CTextBox has no show()
-
-	//map size icon
-	int temp;
-	switch (ourHeader->width)
-	{
-	case 36:
-		temp=0;
-		break;
-	case 72:
-		temp=1;
-		break;
-	case 108:
-		temp=2;
-		break;
-	case 144:
-		temp=3;
-		break;
-	default:
-		temp=4;
-		break;
-	}
-	sizes->setFrame(temp);
-	sizes->showAll(to);
-
-	//flags
-	int fx = 496  + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[390]);
-	int ex = 629 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[391]);
-	TeamID myT;
-	myT = ourHeader->players[playerColor.getNum()].team;
-	for (auto i = startInfo.playerInfos.cbegin(); i != startInfo.playerInfos.cend(); i++)
-	{
-		int *myx = ((i->first == playerColor  ||  ourHeader->players[i->first.getNum()].team == myT) ? &fx : &ex);
-
-		auto flag = sFlags->getImage(i->first.getNum(), 0);
-		flag->draw(to, pos.x + *myx, pos.y + 405);
-		*myx += flag->width();
-		flag->decreaseRef();
-	}
-
-	//difficulty
-	diffPics[startInfo.difficulty]->showAll(to);
-
-	CIntObject::show(to);
-}
-
-void CBonusSelection::updateBonusSelection()
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	//graphics:
-	//spell - SPELLBON.DEF
-	//monster - TWCRPORT.DEF
-	//building - BO*.BMP graphics
-	//artifact - ARTIFBON.DEF
-	//spell scroll - SPELLBON.DEF
-	//prim skill - PSKILBON.DEF
-	//sec skill - SSKILBON.DEF
-	//resource - BORES.DEF
-	//player - CREST58.DEF
-	//hero - PORTRAITSLARGE (HPL###.BMPs)
-	const CCampaignScenario &scenario = ourCampaign->camp->scenarios[selectedMap];
-	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
-
-	updateStartButtonState(-1);
-
-	delete bonuses;
-	bonuses = new CToggleGroup(std::bind(&CBonusSelection::selectBonus, this, _1));
-
-	static const char *bonusPics[] = {"SPELLBON.DEF", "TWCRPORT.DEF", "", "ARTIFBON.DEF", "SPELLBON.DEF",
-		"PSKILBON.DEF", "SSKILBON.DEF", "BORES.DEF", "PORTRAITSLARGE", "PORTRAITSLARGE"};
-
-	for(int i = 0; i < bonDescs.size(); i++)
-	{
-		std::string picName=bonusPics[bonDescs[i].type];
-		size_t picNumber=bonDescs[i].info2;
-
-		std::string desc;
-		switch(bonDescs[i].type)
-		{
-		case CScenarioTravel::STravelBonus::SPELL:
-			desc = CGI->generaltexth->allTexts[715];
-			boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
-			break;
-		case CScenarioTravel::STravelBonus::MONSTER:
-			picNumber = bonDescs[i].info2 + 2;
-			desc = CGI->generaltexth->allTexts[717];
-			boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info3));
-			boost::algorithm::replace_first(desc, "%s", CGI->creh->creatures[bonDescs[i].info2]->namePl);
-			break;
-		case CScenarioTravel::STravelBonus::BUILDING:
-			{
-				int faction = -1;
-				for(auto & elem : startInfo.playerInfos)
-				{
-					if (elem.second.playerID)
-					{
-						faction = elem.second.castle;
-						break;
-					}
-
-				}
-				assert(faction != -1);
-
-				BuildingID buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
-				picName = graphics->ERMUtoPicture[faction][buildID];
-				picNumber = -1;
-
-				if (vstd::contains(CGI->townh->factions[faction]->town->buildings, buildID))
-					desc = CGI->townh->factions[faction]->town->buildings.find(buildID)->second->Name();
-			}
-			break;
-		case CScenarioTravel::STravelBonus::ARTIFACT:
-			desc = CGI->generaltexth->allTexts[715];
-			boost::algorithm::replace_first(desc, "%s", CGI->arth->artifacts[bonDescs[i].info2]->Name());
-			break;
-		case CScenarioTravel::STravelBonus::SPELL_SCROLL:
-			desc = CGI->generaltexth->allTexts[716];
-			boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
-			break;
-		case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
-			{
-				int leadingSkill = -1;
-				std::vector<std::pair<int, int> > toPrint; //primary skills to be listed <num, val>
-				const ui8* ptr = reinterpret_cast<const ui8*>(&bonDescs[i].info2);
-				for (int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
-				{
-					if (leadingSkill == -1 || ptr[g] > ptr[leadingSkill])
-					{
-						leadingSkill = g;
-					}
-					if (ptr[g] != 0)
-					{
-						toPrint.push_back(std::make_pair(g, ptr[g]));
-					}
-				}
-				picNumber = leadingSkill;
-				desc = CGI->generaltexth->allTexts[715];
-
-				std::string substitute; //text to be printed instead of %s
-				for (int v=0; v<toPrint.size(); ++v)
-				{
-					substitute += boost::lexical_cast<std::string>(toPrint[v].second);
-					substitute += " " + CGI->generaltexth->primarySkillNames[toPrint[v].first];
-					if(v != toPrint.size() - 1)
-					{
-						substitute += ", ";
-					}
-				}
-
-				boost::algorithm::replace_first(desc, "%s", substitute);
-				break;
-			}
-		case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
-			desc = CGI->generaltexth->allTexts[718];
-
-			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level
-			boost::algorithm::replace_first(desc, "%s", CGI->skillh->skillName(bonDescs[i].info2)); //skill name
-			picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
-
-			break;
-		case CScenarioTravel::STravelBonus::RESOURCE:
-			{
-				int serialResID = 0;
-				switch(bonDescs[i].info1)
-				{
-				case 0: case 1: case 2: case 3: case 4: case 5: case 6:
-					serialResID = bonDescs[i].info1;
-					break;
-				case 0xFD: //wood + ore
-					serialResID = 7;
-					break;
-				case 0xFE: //rare resources
-					serialResID = 8;
-					break;
-				}
-				picNumber = serialResID;
-
-				desc = CGI->generaltexth->allTexts[717];
-				boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info2));
-				std::string replacement;
-				if (serialResID <= 6)
-				{
-					replacement = CGI->generaltexth->restypes[serialResID];
-				}
-				else
-				{
-					replacement = CGI->generaltexth->allTexts[714 + serialResID];
-				}
-				boost::algorithm::replace_first(desc, "%s", replacement);
-			}
-			break;
-		case CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO:
-			{
-				auto superhero = ourCampaign->camp->scenarios[bonDescs[i].info2].strongestHero(PlayerColor(bonDescs[i].info1));
-				if (!superhero) logGlobal->warn("No superhero! How could it be transferred?");
-				picNumber = superhero ? superhero->portrait : 0;
-				desc = CGI->generaltexth->allTexts[719];
-
-				boost::algorithm::replace_first(desc, "%s", ourCampaign->camp->scenarios[bonDescs[i].info2].scenarioName); //scenario
-			}
-
-			break;
-		case CScenarioTravel::STravelBonus::HERO:
-
-			desc = CGI->generaltexth->allTexts[718];
-			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //hero's color
-
-			if (bonDescs[i].info2 == 0xFFFF)
-			{
-				boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->allTexts[101]); //hero's name
-				picNumber = -1;
-				picName = "CBONN1A3.BMP";
-			}
-			else
-			{
-				boost::algorithm::replace_first(desc, "%s", CGI->heroh->heroes[bonDescs[i].info2]->name); //hero's name
-			}
-			break;
-		}
-
-		CToggleButton *bonusButton = new CToggleButton(Point(475 + i*68, 455), "", CButton::tooltip(desc, desc));
-
-		if (picNumber != -1)
-			picName += ":" + boost::lexical_cast<std::string>(picNumber);
-
-		auto anim = std::make_shared<CAnimation>();
-		anim->setCustom(picName, 0);
-		bonusButton->setImage(anim);
-		const SDL_Color brightYellow = { 242, 226, 110, 0 };
-		bonusButton->setBorderColor({}, {}, {}, brightYellow);
-		bonuses->addToggle(i, bonusButton);
-	}
-
-	// set bonus if already chosen
-	if(vstd::contains(ourCampaign->chosenCampaignBonuses, selectedMap))
-	{
-		bonuses->setSelected(ourCampaign->chosenCampaignBonuses[selectedMap]);
-	}
-}
-
-void CBonusSelection::updateCampaignState()
-{
-	ourCampaign->currentMap = boost::make_optional(selectedMap);
-	if (selectedBonus)
-		ourCampaign->chosenCampaignBonuses[selectedMap] = *selectedBonus;
-}
-
-void CBonusSelection::startMap()
-{
-	auto si = new StartInfo(startInfo);
-	auto showPrologVideo = [=]()
-	{
-		auto exitCb = [=]()
-		{
-			logGlobal->info("Starting scenario %d", selectedMap);
-            CGP->showLoadingScreen(std::bind(&startGame, si, (CConnection *)nullptr));
-		};
-
-		const CCampaignScenario & scenario = ourCampaign->camp->scenarios[selectedMap];
-		if (scenario.prolog.hasPrologEpilog)
-		{
-			GH.pushInt(new CPrologEpilogVideo(scenario.prolog, exitCb));
-		}
-		else
-		{
-			exitCb();
-		}
-	};
-
-	if(LOCPLINT) // we're currently ingame, so ask for starting new map and end game
-	{
-		GH.popInt(this);
-		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
-		{
-			updateCampaignState();
-			endGame();
-			GH.curInt = CGPreGame::create();
-			showPrologVideo();
-		}, 0);
-	}
-	else
-	{
-		updateCampaignState();
-		showPrologVideo();
-	}
-}
-
-void CBonusSelection::restartMap()
-{
-	GH.popInt(this);
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
-	{
-		updateCampaignState();
-		auto si = new StartInfo(startInfo);
-
-		SDL_Event event;
-		event.type = SDL_USEREVENT;
-		event.user.code = PREPARE_RESTART_CAMPAIGN;
-		event.user.data1 = si;
-		SDL_PushEvent(&event);
-	}, 0);
-}
-
-void CBonusSelection::selectBonus(int id)
-{
-	// Total redraw is needed because the border around the bonus images
-	// have to be undrawn/drawn.
-	if (!selectedBonus || *selectedBonus != id)
-	{
-		selectedBonus = boost::make_optional(id);
-		GH.totalRedraw();
-
-		updateStartButtonState(id);
-	}
-
-	const CCampaignScenario &scenario = ourCampaign->camp->scenarios[selectedMap];
-	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
-	if (bonDescs[id].type == CScenarioTravel::STravelBonus::HERO)
-	{
-		std::map<ui8, std::string> names;
-		names[1] = settings["general"]["playerName"].String();
-		for(auto & elem : startInfo.playerInfos)
-		{
-			if(elem.first == PlayerColor(bonDescs[id].info1))
-				::setPlayer(elem.second, 1, names);
-			else
-				::setPlayer(elem.second, 0, names);
-		}
-	}
-}
-
-void CBonusSelection::increaseDifficulty()
-{
-	startInfo.difficulty = std::min(startInfo.difficulty + 1, 4);
-}
-
-void CBonusSelection::decreaseDifficulty()
-{
-	startInfo.difficulty = std::max(startInfo.difficulty - 1, 0);
-}
-
-void CBonusSelection::updateStartButtonState(int selected)
-{
-	if(selected == -1)
-	{
-		startB->block(ourCampaign->camp->scenarios[selectedMap].travelOptions.bonusesToChoose.size());
-	}
-	else if(startB->isBlocked())
-	{
-		startB->block(false);
-	}
-}
-
-CBonusSelection::CRegion::CRegion( CBonusSelection * _owner, bool _accessible, bool _selectable, int _myNumber )
-: owner(_owner), accessible(_accessible), selectable(_selectable), myNumber(_myNumber)
-{
-	OBJ_CONSTRUCTION;
-	addUsedEvents(LCLICK | RCLICK);
-
-	static const std::string colors[2][8] = {
-		{"R", "B", "N", "G", "O", "V", "T", "P"},
-		{"Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi"}};
-
-	const SCampPositions & campDsc = owner->campDescriptions[owner->ourCampaign->camp->header.mapVersion];
-	const SCampPositions::SRegionDesc & desc = campDsc.regions[myNumber];
-	pos.x += desc.xpos;
-	pos.y += desc.ypos;
-
-	//loading of graphics
-
-	std::string prefix = campDsc.campPrefix + desc.infix + "_";
-	std::string suffix = colors[campDsc.colorSuffixLength - 1][owner->ourCampaign->camp->scenarios[myNumber].regionColor];
-
-	static const std::string infix [] = {"En", "Se", "Co"};
-	for (int g = 0; g < ARRAY_COUNT(infix); g++)
-	{
-		graphics[g] = BitmapHandler::loadBitmap(prefix + infix[g] + suffix + ".BMP");
-	}
-	pos.w = graphics[0]->w;
-	pos.h = graphics[0]->h;
-
-}
-
-CBonusSelection::CRegion::~CRegion()
-{
-	for (auto & elem : graphics)
-	{
-		SDL_FreeSurface(elem);
-	}
-}
-
-void CBonusSelection::CRegion::clickLeft( tribool down, bool previousState )
-{
-	//select if selectable & clicked inside our graphic
-	if ( indeterminate(down) )
-	{
-		return;
-	}
-	if( !down && selectable && !CSDL_Ext::isTransparent(graphics[0], GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) )
-	{
-		owner->selectMap(myNumber, false);
-		owner->highlightedRegion = this;
-		parent->showAll(screen);
-	}
-}
-
-void CBonusSelection::CRegion::clickRight( tribool down, bool previousState )
-{
-	//show r-click text
-	if( down && !CSDL_Ext::isTransparent(graphics[0], GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) &&
-		rclickText.size() )
-	{
-		CRClickPopup::createAndPush(rclickText);
-	}
-}
-
-void CBonusSelection::CRegion::show(SDL_Surface * to)
-{
-	//const SCampPositions::SRegionDesc & desc = owner->campDescriptions[owner->ourCampaign->camp->header.mapVersion].regions[myNumber];
-	if (!accessible)
-	{
-		//show as striped
-		blitAtLoc(graphics[2], 0, 0, to);
-	}
-	else if (this == owner->highlightedRegion)
-	{
-		//show as selected
-		blitAtLoc(graphics[1], 0, 0, to);
-	}
-	else
-	{
-		//show as not selected selected
-		blitAtLoc(graphics[0], 0, 0, to);
-	}
-}
-
-CSavingScreen::CSavingScreen(bool hotseat)
-	: CSelectionScreen(
-		CMenuScreen::saveGame,
-		hotseat ? CMenuScreen::MULTI_HOT_SEAT : (LOCPLINT->cb->getStartInfo()->mode == StartInfo::CAMPAIGN ? CMenuScreen::SINGLE_CAMPAIGN : CMenuScreen::SINGLE_PLAYER)
-	)
-{
-	ourGame = mapInfoFromGame();
-	sInfo = *LOCPLINT->cb->getStartInfo();
-	setPlayersFromGame();
-}
-
-CSavingScreen::~CSavingScreen()
-{
-
-}
-
-ISelectionScreenInfo::ISelectionScreenInfo(const std::map<ui8, std::string> * Names)
-{
-	gameMode = CMenuScreen::SINGLE_PLAYER;
-	screenType = CMenuScreen::mainMenu;
-	assert(!SEL);
-	SEL = this;
-	current = nullptr;
-
-	if(Names && !Names->empty()) //if have custom set of player names - use it
-		playerNames = *Names;
-	else
-		playerNames[1] = settings["general"]["playerName"].String(); //by default we have only one player and his name is "Player" (or whatever the last used name was)
-}
-
-ISelectionScreenInfo::~ISelectionScreenInfo()
-{
-	assert(SEL == this);
-	SEL = nullptr;
-}
-
-void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader)
-{
-	::updateStartInfo(filename, sInfo, mapHeader, playerNames);
-}
-
-void ISelectionScreenInfo::setPlayer(PlayerSettings &pset, ui8 player)
-{
-	::setPlayer(pset, player, playerNames);
-}
-
-ui8 ISelectionScreenInfo::getIdOfFirstUnallocatedPlayer()
-{
-	for(auto i = playerNames.cbegin(); i != playerNames.cend(); i++)
-		if(!sInfo.getPlayersSettings(i->first))  //
-			return i->first;
-
-	return 0;
-}
-
-bool ISelectionScreenInfo::isGuest() const
-{
-	return gameMode == CMenuScreen::MULTI_NETWORK_GUEST;
-}
-
-bool ISelectionScreenInfo::isHost() const
-{
-	return gameMode == CMenuScreen::MULTI_NETWORK_HOST;
-}
-
-void ChatMessage::apply(CSelectionScreen *selScreen)
-{
-	selScreen->card->chat->addNewMessage(playerName + ": " + message);
-	GH.totalRedraw();
-}
-
-void QuitMenuWithoutStarting::apply(CSelectionScreen *selScreen)
-{
-	if(!selScreen->ongoingClosing)
-	{
-		*selScreen->serv << this; //resend to confirm
-		GH.popIntTotally(selScreen); //will wait with deleting us before this thread ends
-	}
-
-	vstd::clear_pointer(selScreen->serv);
-}
-
-void PlayerJoined::apply(CSelectionScreen *selScreen)
-{
-	//assert(SEL->playerNames.size() == connectionID); //temporary, TODO when player exits
-	SEL->playerNames[connectionID] = playerName;
-
-	//put new player in first slot with AI
-	for(auto & elem : SEL->sInfo.playerInfos)
-	{
-		if(!elem.second.playerID && !elem.second.compOnly)
-		{
-			selScreen->setPlayer(elem.second, connectionID);
-			selScreen->opt->entries[elem.second.color]->selectButtons();
-			break;
-		}
-	}
-
-	selScreen->propagateNames();
-	selScreen->propagateOptions();
-	selScreen->toggleTab(selScreen->curTab);
-
-	GH.totalRedraw();
-}
-
-void SelectMap::apply(CSelectionScreen *selScreen)
-{
-	if(selScreen->isGuest())
-	{
-		free = false;
-		selScreen->changeSelection(mapInfo);
-	}
-}
-
-void UpdateStartOptions::apply(CSelectionScreen *selScreen)
-{
-	if(!selScreen->isGuest())
-		return;
-
-	selScreen->setSInfo(*options);
-}
-
-void PregameGuiAction::apply(CSelectionScreen *selScreen)
-{
-	if(!selScreen->isGuest())
-		return;
-
-	switch(action)
-	{
-	case NO_TAB:
-		selScreen->toggleTab(selScreen->curTab);
-		break;
-	case OPEN_OPTIONS:
-		selScreen->toggleTab(selScreen->opt);
-		break;
-	case OPEN_SCENARIO_LIST:
-		selScreen->toggleTab(selScreen->sel);
-		break;
-	case OPEN_RANDOM_MAP_OPTIONS:
-		selScreen->toggleTab(selScreen->randMapTab);
-		break;
-	}
-}
-
-void RequestOptionsChange::apply(CSelectionScreen *selScreen)
-{
-	if(!selScreen->isHost())
-		return;
-
-	PlayerColor color = selScreen->sInfo.getPlayersSettings(playerID)->color;
-
-	switch(what)
-	{
-	case TOWN:
-		selScreen->opt->nextCastle(color, direction);
-		break;
-	case HERO:
-		selScreen->opt->nextHero(color, direction);
-		break;
-	case BONUS:
-		selScreen->opt->nextBonus(color, direction);
-		break;
-	}
-}
-
-void PlayerLeft::apply(CSelectionScreen *selScreen)
-{
-	if(selScreen->isGuest())
-		return;
-
-	SEL->playerNames.erase(playerID);
-
-	if(PlayerSettings *s = selScreen->sInfo.getPlayersSettings(playerID)) //it's possible that player was unallocated
-	{
-		selScreen->setPlayer(*s, 0);
-		selScreen->opt->entries[s->color]->selectButtons();
-	}
-
-	selScreen->propagateNames();
-	selScreen->propagateOptions();
-	GH.totalRedraw();
-}
-
-void PlayersNames::apply(CSelectionScreen *selScreen)
-{
-	if(selScreen->isGuest())
-		selScreen->playerNames = playerNames;
-}
-
-void StartWithCurrentSettings::apply(CSelectionScreen *selScreen)
-{
-	startingInfo.reset();
-	startingInfo.serv = selScreen->serv;
-	startingInfo.sInfo = new StartInfo(selScreen->sInfo);
-
-	if(!selScreen->ongoingClosing)
-	{
-		*selScreen->serv << this; //resend to confirm
-	}
-
-	selScreen->serv = nullptr; //hide it so it won't be deleted
-	vstd::clear_pointer(selScreen->serverHandlingThread); //detach us
-	saveGameName.clear();
-
-    CGP->showLoadingScreen(std::bind(&startGame, startingInfo.sInfo, startingInfo.serv));
-	throw 666; //EVIL, EVIL, EVIL workaround to kill thread (does "goto catch" outside listening loop)
-}
-
-CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode &config )
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	pos.x += config["x"].Float();
-	pos.y += config["y"].Float();
-	pos.w = 200;
-	pos.h = 116;
-
-	campFile = config["file"].String();
-	video = config["video"].String();
-
-	status = config["open"].Bool() ? CCampaignScreen::ENABLED : CCampaignScreen::DISABLED;
-
-	CCampaignHeader header = CCampaignHandler::getHeader(campFile);
-	hoverText = header.name;
-
-	hoverLabel = nullptr;
-	if (status != CCampaignScreen::DISABLED)
-	{
-		addUsedEvents(LCLICK | HOVER);
-		new CPicture(config["image"].String());
-
-		hoverLabel = new CLabel(pos.w / 2, pos.h + 20, FONT_MEDIUM, CENTER, Colors::YELLOW, "");
-		parent->addChild(hoverLabel);
-	}
-
-	if (status == CCampaignScreen::COMPLETED)
-		new CPicture("CAMPCHK");
-}
-
-void CCampaignScreen::CCampaignButton::clickLeft(tribool down, bool previousState)
-{
-	if (down)
-	{
-		// Close running video and open the selected campaign
-		CCS->videoh->close();
-		GH.pushInt( new CBonusSelection(campFile) );
-	}
-}
-
-void CCampaignScreen::CCampaignButton::hover(bool on)
-{
-	if (hoverLabel)
-	{
-		if (on)
-			hoverLabel->setText(hoverText); // Shows the name of the campaign when you get into the bounds of the button
-		else
-			hoverLabel->setText(" ");
-	}
-}
-
-void CCampaignScreen::CCampaignButton::show(SDL_Surface * to)
-{
-	if (status == CCampaignScreen::DISABLED)
-		return;
-
-	CIntObject::show(to);
-
-	// Play the campaign button video when the mouse cursor is placed over the button
-	if (hovered)
-	{
-		if (CCS->videoh->fname != video)
-			CCS->videoh->open(video);
-
-		CCS->videoh->update(pos.x, pos.y, to, true, false); // plays sequentially frame by frame, starts at the beginning when the video is over
-	}
-	else if (CCS->videoh->fname == video) // When you got out of the bounds of the button then close the video
-	{
-		CCS->videoh->close();
-		redraw();
-	}
-}
-
-CButton* CCampaignScreen::createExitButton(const JsonNode& button)
-{
-	std::pair<std::string, std::string> help;
-	if (!button["help"].isNull() && button["help"].Float() > 0)
-		help = CGI->generaltexth->zelp[button["help"].Float()];
-
-	std::function<void()> close = std::bind(&CGuiHandler::popIntTotally, &GH, this);
-	return new CButton(Point(button["x"].Float(), button["y"].Float()), button["name"].String(), help, close, button["hotkey"].Float());
-}
-
-
-CCampaignScreen::CCampaignScreen(const JsonNode &config)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	for(const JsonNode& node : config["images"].Vector())
-		images.push_back(createPicture(node));
-
-	if (!images.empty())
-	{
-		images[0]->center();              // move background to center
-		moveTo(images[0]->pos.topLeft()); // move everything else to center
-		images[0]->moveTo(pos.topLeft()); // restore moved twice background
-		pos = images[0]->pos;             // fix height\width of this window
-	}
-
-	if (!config["exitbutton"].isNull())
-	{
-		CButton * back = createExitButton(config["exitbutton"]);
-		back->hoverable = true;
-	}
-
-	for(const JsonNode& node : config["items"].Vector())
-		campButtons.push_back(new CCampaignButton(node));
-}
-
-void CCampaignScreen::showAll(SDL_Surface *to)
-{
-	CIntObject::showAll(to);
-	if (pos.h != to->h || pos.w != to->w)
-		CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15);
-}
-
-void CGPreGame::showLoadingScreen(std::function<void()> loader)
-{
-	if (GH.listInt.size() && GH.listInt.front() == CGP) //pregame active
-		CGP->removeFromGui();
-	GH.pushInt(new CLoadingScreen(loader));
-}
-
-std::string CLoadingScreen::getBackground()
-{
-	const auto & conf = CGPreGameConfig::get().getConfig()["loading"].Vector();
-
-	if(conf.empty())
-	{
-		return "loadbar";
-	}
-	else
-	{
-		return RandomGeneratorUtil::nextItem(conf, CRandomGenerator::getDefault())->String();
-	}
-}
-
-CLoadingScreen::CLoadingScreen(std::function<void ()> loader):
-    CWindowObject(BORDERED, getBackground()),
-    loadingThread(loader)
-{
-	CCS->musich->stopMusic(5000);
-}
-
-CLoadingScreen::~CLoadingScreen()
-{
-	loadingThread.join();
-}
-
-void CLoadingScreen::showAll(SDL_Surface *to)
-{
-	Rect rect(0,0,to->w, to->h);
-	SDL_FillRect(to, &rect, 0);
-
-	CWindowObject::showAll(to);
-}
-
-CPrologEpilogVideo::CPrologEpilogVideo( CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback ):
-    CWindowObject(BORDERED),
-    spe(_spe),
-    positionCounter(0),
-    voiceSoundHandle(-1),
-    exitCb(callback)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	addUsedEvents(LCLICK);
-	pos = center(Rect(0,0, 800, 600));
-	updateShadow();
-
-	CCS->videoh->open(CCampaignHandler::prologVideoName(spe.prologVideo));
-	CCS->musich->playMusic("Music/" + CCampaignHandler::prologMusicName(spe.prologMusic), true);
-	voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo));
-
-	text = new CMultiLineLabel(Rect(100, 500, 600, 100), EFonts::FONT_BIG, CENTER, Colors::METALLIC_GOLD, spe.prologText );
-	text->scrollTextTo(-100);
-}
-
-void CPrologEpilogVideo::show( SDL_Surface * to )
-{
-	CSDL_Ext::fillRectBlack(to, &pos);
-	//BUG: some videos are 800x600 in size while some are 800x400
-	//VCMI should center them in the middle of the screen. Possible but needs modification
-	//of video player API which I'd like to avoid until we'll get rid of Windows-specific player
-	CCS->videoh->update(pos.x, pos.y, to, true, false);
-
-	//move text every 5 calls/frames; seems to be good enough
-	++positionCounter;
-	if(positionCounter % 5 == 0)
-	{
-		text->scrollTextBy(1);
-	}
-	else
-		text->showAll(to);// blit text over video, if needed
-
-	if (text->textSize.y + 100 < positionCounter / 5)
-		clickLeft(false, false);
-}
-
-void CPrologEpilogVideo::clickLeft( tribool down, bool previousState )
-{
-	GH.popInt(this);
-	CCS->soundh->stopSound(voiceSoundHandle);
-	exitCb();
-}
-
-CSimpleJoinScreen::CSimpleJoinScreen(CMenuScreen::EGameMode mode)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	bg = new CPicture("MUDIALOG.bmp"); // address background
-	pos = bg->center(); //center, window has size of bg graphic (x,y = 396,278 w=232 h=212)
-
-	Rect boxRect(20, 20, 205, 50);
-	title = new CTextBox("Enter address:", boxRect, 0, FONT_BIG, CENTER, Colors::WHITE);
-
-	address = new CTextInput(Rect(25, 68, 175, 16), *bg);
-    address->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
-
-	port = new CTextInput(Rect(25, 115, 175, 16), *bg);
-    port->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
-    port->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535);
-
-	ok     = new CButton(Point( 26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::enterSelectionScreen, this, mode), SDLK_RETURN);
-	cancel = new CButton(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE);
-	bar = new CGStatusBar(new CPicture(Rect(7, 186, 218, 18), 0));
-
-	port->setText(CServerHandler::getDefaultPortStr(), true);
-	address->setText(settings["server"]["server"].String(), true);
-	address->giveFocus();
-}
-
-void CSimpleJoinScreen::enterSelectionScreen(CMenuScreen::EGameMode mode)
-{
-	std::string textAddress = address->text;
-	std::string textPort = port->text;
-
-	GH.popIntTotally(this);
-
-	GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, boost::lexical_cast<ui16>(textPort)));
-}
-void CSimpleJoinScreen::onChange(const std::string & newText)
-{
-	ok->block(address->text.empty() || port->text.empty());
-}
-

+ 0 - 650
client/CPreGame.h

@@ -1,650 +0,0 @@
-/*
- * CPreGame.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/StartInfo.h"
-#include "../lib/FunctionList.h"
-#include "../lib/mapping/CMapInfo.h"
-#include "../lib/rmg/CMapGenerator.h"
-#include "windows/CWindowObject.h"
-
-class CMapInfo;
-class CMusicHandler;
-class CMapHeader;
-class CCampaignHeader;
-class CTextInput;
-class CCampaign;
-class CGStatusBar;
-class CTextBox;
-class CCampaignState;
-class CConnection;
-class JsonNode;
-class CMapGenOptions;
-class CRandomMapTab;
-struct CPackForSelectionScreen;
-struct PlayerInfo;
-class CMultiLineLabel;
-class CToggleButton;
-class CToggleGroup;
-class CTabbedInt;
-class IImage;
-class CAnimation;
-class CAnimImage;
-class CButton;
-class CLabel;
-class CSlider;
-
-namespace boost{ class thread; class recursive_mutex;}
-
-enum ESortBy{_playerAm, _size, _format, _name, _viccon, _loscon, _numOfMaps, _fileName}; //_numOfMaps is for campaigns
-
-/// Class which handles map sorting by different criteria
-class mapSorter
-{
-public:
-	ESortBy sortBy;
-	bool operator()(const CMapInfo *aaa, const CMapInfo *bbb);
-	mapSorter(ESortBy es):sortBy(es){};
-};
-
-/// The main menu screens listed in the EState enum
-class CMenuScreen : public CIntObject
-{
-	const JsonNode& config;
-
-	CTabbedInt *tabs;
-
-	CPicture * background;
-	std::vector<CPicture*> images;
-
-	CIntObject *createTab(size_t index);
-public:
-	std::vector<std::string> menuNameToEntry;
-
-	enum EState { //where are we?
-		mainMenu, newGame, loadGame, campaignMain, saveGame, scenarioInfo, campaignList
-	};
-
-	enum EGameMode {
-		SINGLE_PLAYER = 0, MULTI_HOT_SEAT, MULTI_NETWORK_HOST, MULTI_NETWORK_GUEST, SINGLE_CAMPAIGN
-	};
-	CMenuScreen(const JsonNode& configNode);
-
-	void showAll(SDL_Surface * to) override;
-	void show(SDL_Surface * to) override;
-	void activate() override;
-	void deactivate() override;
-
-	void switchToTab(size_t index);
-};
-
-class CMenuEntry : public CIntObject
-{
-	std::vector<CPicture*> images;
-	std::vector<CButton*> buttons;
-
-	CButton* createButton(CMenuScreen* parent, const JsonNode& button);
-public:
-	CMenuEntry(CMenuScreen* parent, const JsonNode &config);
-};
-
-class CreditsScreen : public CIntObject
-{
-	int positionCounter;
-	CMultiLineLabel* credits;
-public:
-	CreditsScreen();
-
-	void show(SDL_Surface * to) override;
-
-	void clickLeft(tribool down, bool previousState) override;
-	void clickRight(tribool down, bool previousState) override;
-};
-
-/// Implementation of the chat box
-class CChatBox : public CIntObject
-{
-public:
-	CTextBox *chatHistory;
-	CTextInput *inputBox;
-
-	CChatBox(const Rect &rect);
-
-	void keyPressed(const SDL_KeyboardEvent & key) override;
-
-	void addNewMessage(const std::string &text);
-};
-
-class InfoCard : public CIntObject
-{
-	CAnimImage * victory, * loss, *sizes;
-	std::shared_ptr<CAnimation> sFlags;
-public:
-	CPicture *bg;
-
-	bool network;
-	bool chatOn;  //if chat is shown, then description is hidden
-	CTextBox *mapDescription;
-	CChatBox *chat;
-	CPicture *playerListBg;
-
-	CToggleGroup *difficulty;
-
-	void changeSelection(const CMapInfo *to);
-	void showAll(SDL_Surface * to) override;
-	void clickRight(tribool down, bool previousState) override;
-	void showTeamsPopup();
-	void toggleChat();
-	void setChat(bool activateChat);
-	InfoCard(bool Network = false);
-	~InfoCard();
-};
-
-/// The selection tab which is shown at the map selection screen
-class SelectionTab : public CIntObject
-{
-private:
-	std::shared_ptr<CAnimation> formatIcons;
-
-	void parseMaps(const std::unordered_set<ResourceID> &files);
-	void parseGames(const std::unordered_set<ResourceID> &files, CMenuScreen::EGameMode gameMode);
-	std::unordered_set<ResourceID> getFiles(std::string dirURI, int resType);
-	void parseCampaigns(const std::unordered_set<ResourceID> & files );
-	CMenuScreen::EState tabType;
-public:
-	int positions; //how many entries (games/maps) can be shown
-	CPicture *bg; //general bg image
-	CSlider *slider;
-	std::vector<CMapInfo> allItems;
-	std::vector<CMapInfo*> curItems;
-	size_t selectionPos;
-	std::function<void(CMapInfo *)> onSelect;
-
-	ESortBy sortingBy;
-	ESortBy generalSortingBy;
-	bool ascending;
-
-	CTextInput *txt;
-
-
-	void filter(int size, bool selectFirst = false); //0 - all
-	void select(int position); //position: <0 - positions>  position on the screen
-	void selectAbs(int position); //position: absolute position in curItems vector
-	int getPosition(int x, int y); //convert mouse coords to entry position; -1 means none
-	void sliderMove(int slidPos);
-	void sortBy(int criteria);
-	void sort();
-	void printMaps(SDL_Surface *to);
-	int getLine();
-	void selectFName(std::string fname);
-	const CMapInfo * getSelectedMapInfo() const;
-
-	void showAll(SDL_Surface * to) override;
-	void clickLeft(tribool down, bool previousState) override;
-	void keyPressed(const SDL_KeyboardEvent & key) override;
-	void onDoubleClick() override;
-	SelectionTab(CMenuScreen::EState Type, const std::function<void(CMapInfo *)> &OnSelect, CMenuScreen::EGameMode GameMode = CMenuScreen::SINGLE_PLAYER);
-    ~SelectionTab();
-};
-
-/// The options tab which is shown at the map selection phase.
-class OptionsTab : public CIntObject
-{
-	CPicture *bg;
-public:
-	enum SelType {TOWN, HERO, BONUS};
-
-	struct CPlayerSettingsHelper
-	{
-		const PlayerSettings & settings;
-		const SelType type;
-
-		CPlayerSettingsHelper(const PlayerSettings & settings, SelType type):
-		    settings(settings),
-		    type(type)
-		{}
-
-		/// visible image settings
-		size_t getImageIndex();
-		std::string getImageName();
-
-		std::string getName();       /// name visible in options dialog
-		std::string getTitle();      /// title in popup box
-		std::string getSubtitle();   /// popup box subtitle
-		std::string getDescription();/// popup box description, not always present
-	};
-
-	class CPregameTooltipBox : public CWindowObject, public CPlayerSettingsHelper
-	{
-		void genHeader();
-		void genTownWindow();
-		void genHeroWindow();
-		void genBonusWindow();
-	public:
-		CPregameTooltipBox(CPlayerSettingsHelper & helper);
-	};
-
-	struct SelectedBox : public CIntObject, public CPlayerSettingsHelper //img with current town/hero/bonus
-	{
-		CAnimImage * image;
-		CLabel *subtitle;
-
-		SelectedBox(Point position, PlayerSettings & settings, SelType type);
-		void clickRight(tribool down, bool previousState) override;
-
-		void update();
-	};
-
-	struct PlayerOptionsEntry : public CIntObject
-	{
-		PlayerInfo &pi;
-		PlayerSettings &s;
-		CPicture *bg;
-		CButton *btns[6]; //left and right for town, hero, bonus
-		CButton *flag;
-		SelectedBox *town;
-		SelectedBox *hero;
-		SelectedBox *bonus;
-		enum {HUMAN_OR_CPU, HUMAN, CPU} whoCanPlay;
-
-		PlayerOptionsEntry(OptionsTab *owner, PlayerSettings &S);
-		void selectButtons(); //hides unavailable buttons
-		void showAll(SDL_Surface * to) override;
-		void update();
-	};
-
-	CSlider *turnDuration;
-
-	std::set<int> usedHeroes;
-
-	struct PlayerToRestore
-	{
-		PlayerColor color;
-		int id;
-		void reset() { id = -1; color = PlayerColor::CANNOT_DETERMINE; }
-		PlayerToRestore(){ reset(); }
-	} playerToRestore;
-
-
-	std::map<PlayerColor, PlayerOptionsEntry *> entries; //indexed by color
-
-	void nextCastle(PlayerColor player, int dir); //dir == -1 or +1
-	void nextHero(PlayerColor player, int dir); //dir == -1 or +1
-	void nextBonus(PlayerColor player, int dir); //dir == -1 or +1
-	void setTurnLength(int npos);
-	void flagPressed(PlayerColor player);
-
-	void recreate();
-	OptionsTab();
-	~OptionsTab();
-	void showAll(SDL_Surface * to) override;
-
-	int nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir );
-
-	bool canUseThisHero(PlayerColor player, int ID );
-};
-
-/// The random map tab shows options for generating a random map.
-class CRandomMapTab : public CIntObject
-{
-public:
-	CRandomMapTab();
-
-    void showAll(SDL_Surface * to) override;
-	void updateMapInfo();
-	CFunctionList<void (const CMapInfo *)> & getMapInfoChanged();
-	const CMapInfo * getMapInfo() const;
-	const CMapGenOptions & getMapGenOptions() const;
-	void setMapGenOptions(std::shared_ptr<CMapGenOptions> opts);
-
-private:
-    void addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex) const;
-    void addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex) const;
-    void deactivateButtonsFrom(CToggleGroup * group, int startId);
-    void validatePlayersCnt(int playersCnt);
-    void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt);
-	std::vector<int> getPossibleMapSizes();
-
-    CPicture * bg;
-	CToggleButton * twoLevelsBtn;
-	CToggleGroup * mapSizeBtnGroup, * playersCntGroup, * teamsCntGroup, * compOnlyPlayersCntGroup,
-		* compOnlyTeamsCntGroup, * waterContentGroup, * monsterStrengthGroup;
-    CButton * showRandMaps;
-	CMapGenOptions mapGenOptions;
-	std::unique_ptr<CMapInfo> mapInfo;
-	CFunctionList<void(const CMapInfo *)> mapInfoChanged;
-};
-
-/// Interface for selecting a map.
-class ISelectionScreenInfo
-{
-public:
-	CMenuScreen::EGameMode gameMode;
-	CMenuScreen::EState screenType; //new/save/load#Game
-	const CMapInfo *current;
-	StartInfo sInfo;
-	std::map<ui8, std::string> playerNames; // id of player <-> player name; 0 is reserved as ID of AI "players"
-
-	ISelectionScreenInfo(const std::map<ui8, std::string> *Names = nullptr);
-	virtual ~ISelectionScreenInfo();
-	virtual void update(){};
-	virtual void propagateOptions() {};
-	virtual void postRequest(ui8 what, ui8 dir) {};
-	virtual void postChatMessage(const std::string &txt){};
-
-	void setPlayer(PlayerSettings &pset, ui8 player);
-	void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader);
-
-	ui8 getIdOfFirstUnallocatedPlayer(); //returns 0 if none
-	bool isGuest() const;
-	bool isHost() const;
-
-};
-
-/// The actual map selection screen which consists of the options and selection tab
-class CSelectionScreen : public CIntObject, public ISelectionScreenInfo
-{
-	bool bordered;
-public:
-	CPicture *bg; //general bg image
-	InfoCard *card;
-	OptionsTab *opt;
-	CRandomMapTab * randMapTab;
-	CButton *start, *back;
-
-	SelectionTab *sel;
-	CIntObject *curTab;
-
-	boost::thread *serverHandlingThread;
-	boost::recursive_mutex *mx;
-	std::list<CPackForSelectionScreen *> upcomingPacks; //protected by mx
-
-	CConnection *serv; //connection to server, used in MP mode
-	bool ongoingClosing;
-	ui8 myNameID; //used when networking - otherwise all player are "mine"
-
-	CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EGameMode GameMode = CMenuScreen::SINGLE_PLAYER, const std::map<ui8, std::string> * Names = nullptr, const std::string & Address = "", const ui16 Port = 0);
-	~CSelectionScreen();
-	void toggleTab(CIntObject *tab);
-	void changeSelection(const CMapInfo *to);
-	void startCampaign();
-	void startScenario();
-	void difficultyChange(int to);
-
-	void handleConnection();
-
-	void processPacks();
-	void setSInfo(const StartInfo &si);
-	void update() override;
-	void propagateOptions() override;
-	void postRequest(ui8 what, ui8 dir) override;
-	void postChatMessage(const std::string &txt) override;
-	void propagateNames();
-	void showAll(SDL_Surface *to) override;
-};
-
-/// Save game screen
-class CSavingScreen : public CSelectionScreen
-{
-public:
-	const CMapInfo *ourGame;
-
-
-	CSavingScreen(bool hotseat = false);
-	~CSavingScreen();
-};
-
-/// Scenario information screen shown during the game (thus not really a "pre-game" but fits here anyway)
-class CScenarioInfo : public CIntObject, public ISelectionScreenInfo
-{
-public:
-	CButton *back;
-	InfoCard *card;
-	OptionsTab *opt;
-
-	CScenarioInfo(const CMapHeader *mapHeader, const StartInfo *startInfo);
-	~CScenarioInfo();
-};
-
-/// Multiplayer mode
-class CMultiMode : public CIntObject
-{
-public:
-	CPicture *bg;
-	CTextInput *txt;
-	CButton *btns[7]; //0 - hotseat, 6 - cancel
-	CGStatusBar *bar;
-
-	CMultiMode();
-	void openHotseat();
-	void hostTCP();
-	void joinTCP();
-};
-
-/// Hot seat player window
-class CHotSeatPlayers : public CIntObject
-{
-	CPicture *bg;
-	CTextBox *title;
-	CTextInput* txt[8];
-	CButton *ok, *cancel;
-	CGStatusBar *bar;
-
-	void onChange(std::string newText);
-	void enterSelectionScreen();
-
-public:
-	CHotSeatPlayers(const std::string &firstPlayer);
-};
-
-
-class CPrologEpilogVideo : public CWindowObject
-{
-	CCampaignScenario::SScenarioPrologEpilog spe;
-	int positionCounter;
-	int voiceSoundHandle;
-	std::function<void()> exitCb;
-
-	CMultiLineLabel * text;
-public:
-	CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback);
-
-	void clickLeft(tribool down, bool previousState) override;
-	void show(SDL_Surface * to) override;
-};
-
-/// Campaign screen where you can choose one out of three starting bonuses
-class CBonusSelection : public CIntObject
-{
-public:
-	CBonusSelection(const std::string & campaignFName);
-	CBonusSelection(std::shared_ptr<CCampaignState> _ourCampaign);
-	~CBonusSelection();
-
-	void showAll(SDL_Surface * to) override;
-	void show(SDL_Surface * to) override;
-
-private:
-	struct SCampPositions
-	{
-		std::string campPrefix;
-		int colorSuffixLength;
-
-		struct SRegionDesc
-		{
-			std::string infix;
-			int xpos, ypos;
-		};
-
-		std::vector<SRegionDesc> regions;
-
-	};
-
-	class CRegion : public CIntObject
-	{
-		CBonusSelection * owner;
-		SDL_Surface* graphics[3]; //[0] - not selected, [1] - selected, [2] - striped
-		bool accessible; //false if region should be striped
-		bool selectable; //true if region should be selectable
-		int myNumber; //number of region
-
-	public:
-		std::string rclickText;
-		CRegion(CBonusSelection * _owner, bool _accessible, bool _selectable, int _myNumber);
-		~CRegion();
-
-		void clickLeft(tribool down, bool previousState) override;
-		void clickRight(tribool down, bool previousState) override;
-		void show(SDL_Surface * to) override;
-	};
-
-	void init();
-	void loadPositionsOfGraphics();
-	void updateStartButtonState(int selected = -1); //-1 -- no bonus is selected
-	void updateBonusSelection();
-	void updateCampaignState();
-
-	// Event handlers
-	void goBack();
-	void startMap();
-	void restartMap();
-	void selectMap(int mapNr, bool initialSelect);
-	void selectBonus(int id);
-	void increaseDifficulty();
-	void decreaseDifficulty();
-
-	// GUI components
-	SDL_Surface * background;
-	CButton * startB, * restartB, * backB;
-	CTextBox * campaignDescription, * mapDescription;
-	std::vector<SCampPositions> campDescriptions;
-	std::vector<CRegion *> regions;
-	CRegion * highlightedRegion;
-	CToggleGroup * bonuses;
-	std::array<CAnimImage *, 5> diffPics; //pictures of difficulties, user-selectable (or not if campaign locks this)
-	CButton * diffLb, * diffRb; //buttons for changing difficulty
-	CAnimImage * sizes;//icons of map sizes
-	std::shared_ptr<CAnimation> sFlags;
-
-	// Data
-	std::shared_ptr<CCampaignState> ourCampaign;
-	int selectedMap;
-	boost::optional<int> selectedBonus;
-	StartInfo startInfo;
-	std::unique_ptr<CMapHeader> ourHeader;
-};
-
-/// Campaign selection screen
-class CCampaignScreen : public CIntObject
-{
-public:
-	enum CampaignStatus {DEFAULT = 0, ENABLED, DISABLED, COMPLETED}; // the status of the campaign
-
-private:
-	/// A button which plays a video when you move the mouse cursor over it
-	class CCampaignButton : public CIntObject
-	{
-	private:
-		CLabel *hoverLabel;
-		CampaignStatus status;
-
-		std::string campFile; // the filename/resourcename of the campaign
-		std::string video; // the resource name of the video
-		std::string hoverText;
-
-		void clickLeft(tribool down, bool previousState) override;
-		void hover(bool on) override;
-
-	public:
-		CCampaignButton(const JsonNode &config );
-		void show(SDL_Surface * to) override;
-	};
-
-	std::vector<CCampaignButton*> campButtons;
-	std::vector<CPicture*> images;
-
-	CButton* createExitButton(const JsonNode& button);
-
-public:
-	enum CampaignSet {ROE, AB, SOD, WOG};
-
-	CCampaignScreen(const JsonNode &config);
-	void showAll(SDL_Surface *to) override;
-};
-
-/// Manages the configuration of pregame GUI elements like campaign screen, main menu, loading screen,...
-class CGPreGameConfig
-{
-public:
-	static CGPreGameConfig & get();
-	const JsonNode & getConfig() const;
-	const JsonNode & getCampaigns() const;
-
-private:
-	CGPreGameConfig();
-
-	const JsonNode campaignSets;
-	const JsonNode config;
-};
-
-/// Handles background screen, loads graphics for victory/loss condition and random town or hero selection
-class CGPreGame : public CIntObject, public IUpdateable
-{
-	void loadGraphics();
-	void disposeGraphics();
-
-	CGPreGame(); //Use createIfNotPresent
-
-public:
-	CMenuScreen * menu;
-
-	std::shared_ptr<CAnimation> victoryIcons, lossIcons;
-
-	~CGPreGame();
-	void update() override;
-	void openSel(CMenuScreen::EState type, CMenuScreen::EGameMode gameMode = CMenuScreen::SINGLE_PLAYER);
-
-	void openCampaignScreen(std::string name);
-
-	static CGPreGame * create();
-	void removeFromGui();
-	static void showLoadingScreen(std::function<void()> loader);
-};
-
-class CLoadingScreen : public CWindowObject
-{
-	boost::thread loadingThread;
-
-	std::string getBackground();
-public:
-	CLoadingScreen(std::function<void()> loader);
-	~CLoadingScreen();
-
-	void showAll(SDL_Surface *to) override;
-};
-
-/// Simple window to enter the server's address.
-class CSimpleJoinScreen : public CIntObject
-{
-	CPicture * bg;
-	CTextBox * title;
-	CButton * ok, * cancel;
-	CGStatusBar * bar;
-	CTextInput * address;
-	CTextInput * port;
-
-	void enterSelectionScreen(CMenuScreen::EGameMode mode);
-	void onChange(const std::string & newText);
-public:
-	CSimpleJoinScreen(CMenuScreen::EGameMode mode);
-};
-
-extern ISelectionScreenInfo *SEL;
-extern CGPreGame *CGP;

+ 674 - 0
client/CServerHandler.cpp

@@ -0,0 +1,674 @@
+/*
+ * CServerHandler.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 "CServerHandler.h"
+#include "Client.h"
+#include "CGameInfo.h"
+#include "CPlayerInterface.h"
+#include "gui/CGuiHandler.h"
+
+#include "lobby/CSelectionBase.h"
+#include "lobby/CLobbyScreen.h"
+
+#include "mainmenu/CMainMenu.h"
+
+#ifndef VCMI_ANDROID
+#include "../lib/Interprocess.h"
+#endif
+#include "../lib/CConfigHandler.h"
+#include "../lib/CGeneralTextHandler.h"
+#include "../lib/CThreadHelper.h"
+#include "../lib/NetPacks.h"
+#include "../lib/StartInfo.h"
+#include "../lib/VCMIDirs.h"
+#include "../lib/mapping/CCampaignHandler.h"
+#include "../lib/mapping/CMap.h"
+#include "../lib/mapping/CMapInfo.h"
+#include "../lib/mapObjects/MiscObjects.h"
+#include "../lib/rmg/CMapGenOptions.h"
+#include "../lib/registerTypes/RegisterTypes.h"
+#include "../lib/serializer/Connection.h"
+#include "../lib/serializer/CMemorySerializer.h"
+
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_io.hpp>
+#include <boost/uuid/uuid_generators.hpp>
+
+template<typename T> class CApplyOnLobby;
+
+class CBaseForLobbyApply
+{
+public:
+	virtual bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const = 0;
+	virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const = 0;
+	virtual ~CBaseForLobbyApply(){};
+	template<typename U> static CBaseForLobbyApply * getApplier(const U * t = nullptr)
+	{
+		return new CApplyOnLobby<U>();
+	}
+};
+
+template<typename T> class CApplyOnLobby : public CBaseForLobbyApply
+{
+public:
+	bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override
+	{
+		T * ptr = static_cast<T *>(pack);
+		logNetwork->trace("\tImmidiately apply on lobby: %s", typeList.getTypeInfo(ptr)->name());
+		return ptr->applyOnLobbyHandler(handler);
+	}
+
+	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override
+	{
+		T * ptr = static_cast<T *>(pack);
+		logNetwork->trace("\tApply on lobby from queue: %s", typeList.getTypeInfo(ptr)->name());
+		ptr->applyOnLobbyScreen(lobby, handler);
+	}
+};
+
+template<> class CApplyOnLobby<CPack>: public CBaseForLobbyApply
+{
+public:
+	bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override
+	{
+		logGlobal->error("Cannot apply plain CPack!");
+		assert(0);
+		return false;
+	}
+
+	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override
+	{
+		logGlobal->error("Cannot apply plain CPack!");
+		assert(0);
+	}
+};
+
+extern std::string NAME;
+
+CServerHandler::CServerHandler()
+	: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr)
+{
+	uuid = boost::uuids::to_string(boost::uuids::random_generator()());
+	applier = std::make_shared<CApplier<CBaseForLobbyApply>>();
+	registerTypesLobbyPacks(*applier);
+}
+
+void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names)
+{
+	hostClientId = -1;
+	state = EClientState::NONE;
+	th = make_unique<CStopWatch>();
+	packsForLobbyScreen.clear();
+	c.reset();
+	si.reset(new StartInfo());
+	playerNames.clear();
+	si->difficulty = 1;
+	si->mode = mode;
+	myNames.clear();
+	if(names && !names->empty()) //if have custom set of player names - use it
+		myNames = *names;
+	else
+		myNames.push_back(settings["general"]["playerName"].String());
+
+#ifndef VCMI_ANDROID
+	shm.reset();
+
+	if(!settings["session"]["disable-shm"].Bool())
+	{
+		std::string sharedMemoryName = "vcmi_memory";
+		if(settings["session"]["enable-shm-uuid"].Bool())
+		{
+			//used or automated testing when multiple clients start simultaneously
+			sharedMemoryName += "_" + uuid;
+		}
+		try
+		{
+			shm = std::make_shared<SharedMemory>(sharedMemoryName, true);
+		}
+		catch(...)
+		{
+			shm.reset();
+			logNetwork->error("Cannot open interprocess memory. Continue without it...");
+		}
+	}
+#endif
+}
+
+void CServerHandler::startLocalServerAndConnect()
+{
+	if(threadRunLocalServer)
+		threadRunLocalServer->join();
+
+	th->update();
+#ifdef VCMI_ANDROID
+	CAndroidVMHelper envHelper;
+	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true);
+#else
+	threadRunLocalServer = std::make_shared<boost::thread>(&CServerHandler::threadRunServer, this); //runs server executable;
+#endif
+	logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff());
+
+	th->update();
+
+#ifndef VCMI_ANDROID
+	if(shm)
+		shm->sr->waitTillReady();
+#else
+	logNetwork->info("waiting for server");
+	while(!androidTestServerReadyFlag.load())
+	{
+		logNetwork->info("still waiting...");
+		boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
+	}
+	logNetwork->info("waiting for server finished...");
+	androidTestServerReadyFlag = false;
+#endif
+	logNetwork->trace("Waiting for server: %d ms", th->getDiff());
+
+	th->update(); //put breakpoint here to attach to server before it does something stupid
+
+#ifndef VCMI_ANDROID
+	justConnectToServer(settings["server"]["server"].String(), shm ? shm->sr->port : 0);
+#else
+	justConnectToServer(settings["server"]["server"].String());
+#endif
+
+	logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff());
+}
+
+void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port)
+{
+	state = EClientState::CONNECTING;
+	while(!c && state != EClientState::CONNECTION_CANCELLED)
+	{
+		try
+		{
+			logNetwork->info("Establishing connection...");
+			c = std::make_shared<CConnection>(
+					addr.size() ? addr : settings["server"]["server"].String(),
+					port ? port : getDefaultPort(),
+					NAME, uuid);
+		}
+		catch(...)
+		{
+			logNetwork->error("\nCannot establish connection! Retrying within 1 second");
+			boost::this_thread::sleep(boost::posix_time::seconds(1));
+		}
+	}
+
+	if(state == EClientState::CONNECTION_CANCELLED)
+		logNetwork->info("Connection aborted by player!");
+	else
+		c->handler = std::make_shared<boost::thread>(&CServerHandler::threadHandleConnection, this);
+}
+
+void CServerHandler::applyPacksOnLobbyScreen()
+{
+	if(!c || !c->handler)
+		return;
+
+	boost::unique_lock<boost::recursive_mutex> lock(*mx);
+	while(!packsForLobbyScreen.empty())
+	{
+		CPackForLobby * pack = packsForLobbyScreen.front();
+		packsForLobbyScreen.pop_front();
+		CBaseForLobbyApply * apply = applier->getApplier(typeList.getTypeID(pack)); //find the applier
+		apply->applyOnLobbyScreen(static_cast<CLobbyScreen *>(SEL), this, pack);
+		GH.totalRedraw();
+		delete pack;
+	}
+}
+
+void CServerHandler::stopServerConnection()
+{
+	if(c->handler)
+	{
+		while(!c->handler->timed_join(boost::posix_time::milliseconds(50)))
+			applyPacksOnLobbyScreen();
+		c->handler->join();
+	}
+}
+
+std::set<PlayerColor> CServerHandler::getHumanColors()
+{
+	return clientHumanColors(c->connectionID);
+}
+
+
+PlayerColor CServerHandler::myFirstColor() const
+{
+	return clientFirstColor(c->connectionID);
+}
+
+bool CServerHandler::isMyColor(PlayerColor color) const
+{
+	return isClientColor(c->connectionID, color);
+}
+
+ui8 CServerHandler::myFirstId() const
+{
+	return clientFirstId(c->connectionID);
+}
+
+bool CServerHandler::isServerLocal() const
+{
+	if(threadRunLocalServer)
+		return true;
+
+	return false;
+}
+
+bool CServerHandler::isHost() const
+{
+	return c && hostClientId == c->connectionID;
+}
+
+bool CServerHandler::isGuest() const
+{
+	return !c || hostClientId != c->connectionID;
+}
+
+ui16 CServerHandler::getDefaultPort()
+{
+	if(settings["session"]["serverport"].Integer())
+		return settings["session"]["serverport"].Integer();
+	else
+		return settings["server"]["port"].Integer();
+}
+
+std::string CServerHandler::getDefaultPortStr()
+{
+	return boost::lexical_cast<std::string>(getDefaultPort());
+}
+
+void CServerHandler::sendClientConnecting() const
+{
+	LobbyClientConnected lcc;
+	lcc.uuid = uuid;
+	lcc.names = myNames;
+	lcc.mode = si->mode;
+	sendLobbyPack(lcc);
+}
+
+void CServerHandler::sendClientDisconnecting()
+{
+	// FIXME: This is workaround needed to make sure client not trying to sent anything to non existed server
+	if(state == EClientState::DISCONNECTING)
+		return;
+
+	state = EClientState::DISCONNECTING;
+	LobbyClientDisconnected lcd;
+	lcd.clientId = c->connectionID;
+	logNetwork->info("Connection has been requested to be closed.");
+	if(isServerLocal())
+	{
+		lcd.shutdownServer = true;
+		logNetwork->info("Sent closing signal to the server");
+	}
+	else
+	{
+		logNetwork->info("Sent leaving signal to the server");
+	}
+	sendLobbyPack(lcd);
+}
+
+void CServerHandler::setCampaignState(std::shared_ptr<CCampaignState> newCampaign)
+{
+	state = EClientState::LOBBY_CAMPAIGN;
+	LobbySetCampaign lsc;
+	lsc.ourCampaign = newCampaign;
+	sendLobbyPack(lsc);
+}
+
+void CServerHandler::setCampaignMap(int mapId) const
+{
+	if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
+		return;
+
+	LobbySetCampaignMap lscm;
+	lscm.mapId = mapId;
+	sendLobbyPack(lscm);
+}
+
+void CServerHandler::setCampaignBonus(int bonusId) const
+{
+	if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
+		return;
+
+	LobbySetCampaignBonus lscb;
+	lscb.bonusId = bonusId;
+	sendLobbyPack(lscb);
+}
+
+void CServerHandler::setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts) const
+{
+	LobbySetMap lsm;
+	lsm.mapInfo = to;
+	lsm.mapGenOpts = mapGenOpts;
+	sendLobbyPack(lsm);
+}
+
+void CServerHandler::setPlayer(PlayerColor color) const
+{
+	LobbySetPlayer lsp;
+	lsp.clickedColor = color;
+	sendLobbyPack(lsp);
+}
+
+void CServerHandler::setPlayerOption(ui8 what, ui8 dir, PlayerColor player) const
+{
+	LobbyChangePlayerOption lcpo;
+	lcpo.what = what;
+	lcpo.direction = dir;
+	lcpo.color = player;
+	sendLobbyPack(lcpo);
+}
+
+void CServerHandler::setDifficulty(int to) const
+{
+	LobbySetDifficulty lsd;
+	lsd.difficulty = to;
+	sendLobbyPack(lsd);
+}
+
+void CServerHandler::setTurnLength(int npos) const
+{
+	vstd::amin(npos, GameConstants::POSSIBLE_TURNTIME.size() - 1);
+	LobbySetTurnTime lstt;
+	lstt.turnTime = GameConstants::POSSIBLE_TURNTIME[npos];
+	sendLobbyPack(lstt);
+}
+
+void CServerHandler::sendMessage(const std::string & txt) const
+{
+	std::istringstream readed;
+	readed.str(txt);
+	std::string command;
+	readed >> command;
+	if(command == "!passhost")
+	{
+		std::string id;
+		readed >> id;
+		if(id.length())
+		{
+			LobbyChangeHost lch;
+			lch.newHostConnectionId = boost::lexical_cast<int>(id);
+			sendLobbyPack(lch);
+		}
+	}
+	else if(command == "!forcep")
+	{
+		std::string connectedId, playerColorId;
+		readed >> connectedId;
+		readed >> playerColorId;
+		if(connectedId.length(), playerColorId.length())
+		{
+			ui8 connected = boost::lexical_cast<int>(connectedId);
+			auto color = PlayerColor(boost::lexical_cast<int>(playerColorId));
+			if(color.isValidPlayer() && playerNames.find(connected) != playerNames.end())
+			{
+				LobbyForceSetPlayer lfsp;
+				lfsp.targetConnectedPlayer = connected;
+				lfsp.targetPlayerColor = color;
+				sendLobbyPack(lfsp);
+			}
+		}
+	}
+	else
+	{
+		LobbyChatMessage lcm;
+		lcm.message = txt;
+		lcm.playerName = playerNames.find(myFirstId())->second.name;
+		sendLobbyPack(lcm);
+	}
+}
+
+void CServerHandler::sendGuiAction(ui8 action) const
+{
+	LobbyGuiAction lga;
+	lga.action = static_cast<LobbyGuiAction::EAction>(action);
+	sendLobbyPack(lga);
+}
+
+void CServerHandler::sendStartGame(bool allowOnlyAI) const
+{
+	verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
+	LobbyStartGame lsg;
+	sendLobbyPack(lsg);
+}
+
+void CServerHandler::startGameplay()
+{
+	client = new CClient();
+
+	switch(si->mode)
+	{
+	case StartInfo::NEW_GAME:
+		client->newGame();
+		break;
+	case StartInfo::CAMPAIGN:
+		client->newGame();
+		break;
+	case StartInfo::LOAD_GAME:
+		client->loadGame();
+		break;
+	default:
+		throw std::runtime_error("Invalid mode");
+	}
+	// After everything initialized we can accept CPackToClient netpacks
+	c->enterGameplayConnectionMode(client->gameState());
+	state = EClientState::GAMEPLAY;
+}
+
+void CServerHandler::endGameplay(bool closeConnection)
+{
+	client->endGame();
+	vstd::clear_pointer(client);
+
+	// Game is ending
+	// Tell the network thread to reach a stable state
+	CSH->sendClientDisconnecting();
+	logNetwork->info("Closed connection.");
+}
+
+void CServerHandler::startCampaignScenario(std::shared_ptr<CCampaignState> cs)
+{
+	SDL_Event event;
+	event.type = SDL_USEREVENT;
+	event.user.code = EUserEvent::CAMPAIGN_START_SCENARIO;
+	if(cs)
+		event.user.data1 = CMemorySerializer::deepCopy(*cs.get()).release();
+	else
+		event.user.data1 = CMemorySerializer::deepCopy(*si->campState.get()).release();
+	SDL_PushEvent(&event);
+}
+
+int CServerHandler::howManyPlayerInterfaces()
+{
+	int playerInts = 0;
+	for(auto pint : client->playerint)
+	{
+		if(dynamic_cast<CPlayerInterface *>(pint.second.get()))
+			playerInts++;
+	}
+
+	return playerInts;
+}
+
+ui8 CServerHandler::getLoadMode()
+{
+	if(state == EClientState::GAMEPLAY)
+	{
+		if(si->campState)
+			return ELoadMode::CAMPAIGN;
+		for(auto pn : playerNames)
+		{
+			if(pn.second.connection != c->connectionID)
+				return ELoadMode::MULTI;
+		}
+
+		return ELoadMode::SINGLE;
+	}
+	return loadMode;
+}
+
+void CServerHandler::debugStartTest(std::string filename, bool save)
+{
+	logGlobal->info("Starting debug test with file: %s", filename);
+	auto mapInfo = std::make_shared<CMapInfo>();
+	if(save)
+	{
+		resetStateForLobby(StartInfo::LOAD_GAME);
+		mapInfo->saveInit(ResourceID(filename, EResType::CLIENT_SAVEGAME));
+		screenType = ESelectionScreen::loadGame;
+	}
+	else
+	{
+		resetStateForLobby(StartInfo::NEW_GAME);
+		mapInfo->mapInit(filename);
+		screenType = ESelectionScreen::newGame;
+	}
+	if(settings["session"]["donotstartserver"].Bool())
+		justConnectToServer("127.0.0.1", 3030);
+	else
+		startLocalServerAndConnect();
+
+	while(!settings["session"]["headless"].Bool() && !dynamic_cast<CLobbyScreen *>(GH.topInt()))
+		boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+	while(!mi || mapInfo->fileURI != CSH->mi->fileURI)
+	{
+		setMapInfo(mapInfo);
+		boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+	}
+	// "Click" on color to remove us from it
+	setPlayer(myFirstColor());
+	while(myFirstColor() != PlayerColor::CANNOT_DETERMINE)
+		boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+
+	while(true)
+	{
+		try
+		{
+			sendStartGame();
+			break;
+		}
+		catch(...)
+		{
+
+		}
+		boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+	}
+}
+
+void CServerHandler::threadHandleConnection()
+{
+	setThreadName("CServerHandler::threadHandleConnection");
+	c->enterLobbyConnectionMode();
+
+	try
+	{
+		sendClientConnecting();
+		while(c->connected)
+		{
+			while(state == EClientState::STARTING)
+				boost::this_thread::sleep(boost::posix_time::milliseconds(10));
+
+			CPack * pack = c->retrievePack();
+			if(state == EClientState::DISCONNECTING)
+			{
+				// FIXME: server shouldn't really send netpacks after it's tells client to disconnect
+				// Though currently they'll be delivered and might cause crash.
+				vstd::clear_pointer(pack);
+			}
+			else if(auto lobbyPack = dynamic_cast<CPackForLobby *>(pack))
+			{
+				if(applier->getApplier(typeList.getTypeID(pack))->applyOnLobbyHandler(this, pack))
+				{
+					if(!settings["session"]["headless"].Bool())
+					{
+						boost::unique_lock<boost::recursive_mutex> lock(*mx);
+						packsForLobbyScreen.push_back(lobbyPack);
+					}
+				}
+			}
+			else if(auto clientPack = dynamic_cast<CPackForClient *>(pack))
+			{
+				client->handlePack(clientPack);
+			}
+		}
+	}
+	//catch only asio exceptions
+	catch(const boost::system::system_error & e)
+	{
+		if(state == EClientState::DISCONNECTING)
+		{
+			logNetwork->info("Successfully closed connection to server, ending listening thread!");
+		}
+		else
+		{
+			logNetwork->error("Lost connection to server, ending listening thread!");
+			logNetwork->error(e.what());
+			if(client)
+			{
+				CGuiHandler::pushSDLEvent(SDL_USEREVENT, EUserEvent::RETURN_TO_MAIN_MENU);
+			}
+			else
+			{
+				auto lcd = new LobbyClientDisconnected();
+				lcd->clientId = c->connectionID;
+				boost::unique_lock<boost::recursive_mutex> lock(*mx);
+				packsForLobbyScreen.push_back(lcd);
+			}
+		}
+	}
+	catch(...)
+	{
+		handleException();
+		throw;
+	}
+}
+
+void CServerHandler::threadRunServer()
+{
+#ifndef VCMI_ANDROID
+	setThreadName("CServerHandler::threadRunServer");
+	const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();
+	std::string comm = VCMIDirs::get().serverPath().string()
+		+ " --port=" + getDefaultPortStr()
+		+ " --run-by-client"
+		+ " --uuid=" + uuid;
+	if(shm)
+	{
+		comm += " --enable-shm";
+		if(settings["session"]["enable-shm-uuid"].Bool())
+			comm += " --enable-shm-uuid";
+	}
+	comm += " > \"" + logName + '\"';
+
+	int result = std::system(comm.c_str());
+	if (result == 0)
+	{
+		logNetwork->info("Server closed correctly");
+	}
+	else
+	{
+		logNetwork->error("Error: server failed to close correctly or crashed!");
+		logNetwork->error("Check %s for more info", logName);
+	}
+	threadRunLocalServer.reset();
+#endif
+}
+
+void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
+{
+	if(state != EClientState::STARTING)
+		c->sendPack(&pack);
+}

+ 145 - 0
client/CServerHandler.h

@@ -0,0 +1,145 @@
+/*
+ * CServerHandler.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/CStopWatch.h"
+
+#include "../lib/StartInfo.h"
+
+struct SharedMemory;
+class CConnection;
+class PlayerColor;
+struct StartInfo;
+
+class CMapInfo;
+struct ClientPlayer;
+struct CPack;
+struct CPackForLobby;
+class CClient;
+
+template<typename T> class CApplier;
+class CBaseForLobbyApply;
+
+// TODO: Add mutex so we can't set CONNECTION_CANCELLED if client already connected, but thread not setup yet
+enum class EClientState : ui8
+{
+	NONE = 0,
+	CONNECTING, // Trying to connect to server
+	CONNECTION_CANCELLED, // Connection cancelled by player, stop attempts to connect
+	LOBBY, // Client is connected to lobby
+	LOBBY_CAMPAIGN, // Client is on scenario bonus selection screen
+	STARTING, // Gameplay interfaces being created, we pause netpacks retrieving
+	GAMEPLAY, // In-game, used by some UI
+	DISCONNECTING // We disconnecting, drop all netpacks
+};
+
+class IServerAPI
+{
+protected:
+	virtual void sendLobbyPack(const CPackForLobby & pack) const = 0;
+
+public:
+	virtual ~IServerAPI() {}
+
+	virtual void sendClientConnecting() const = 0;
+	virtual void sendClientDisconnecting() = 0;
+	virtual void setCampaignState(std::shared_ptr<CCampaignState> newCampaign) = 0;
+	virtual void setCampaignMap(int mapId) const = 0;
+	virtual void setCampaignBonus(int bonusId) const = 0;
+	virtual void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const = 0;
+	virtual void setPlayer(PlayerColor color) const = 0;
+	virtual void setPlayerOption(ui8 what, ui8 dir, PlayerColor player) const = 0;
+	virtual void setDifficulty(int to) const = 0;
+	virtual void setTurnLength(int npos) const = 0;
+	virtual void sendMessage(const std::string & txt) const = 0;
+	virtual void sendGuiAction(ui8 action) const = 0; // TODO: possibly get rid of it?
+	virtual void sendStartGame(bool allowOnlyAI = false) const = 0;
+};
+
+/// structure to handle running server and connecting to it
+class CServerHandler : public IServerAPI, public LobbyInfo
+{
+	std::shared_ptr<CApplier<CBaseForLobbyApply>> applier;
+
+	std::shared_ptr<boost::recursive_mutex> mx;
+	std::list<CPackForLobby *> packsForLobbyScreen; //protected by mx
+
+	std::vector<std::string> myNames;
+
+	void threadHandleConnection();
+	void threadRunServer();
+	void sendLobbyPack(const CPackForLobby & pack) const override;
+
+public:
+	std::atomic<EClientState> state;
+	////////////////////
+	// FIXME: Bunch of crutches to glue it all together
+
+	// For starting non-custom campaign and continue to next mission
+	std::shared_ptr<CCampaignState> campaignStateToSend;
+
+	ui8 screenType; // To create lobby UI only after server is setup
+	ui8 loadMode; // For saves filtering in SelectionTab
+	////////////////////
+
+	std::unique_ptr<CStopWatch> th;
+	std::shared_ptr<boost::thread> threadRunLocalServer;
+
+	std::shared_ptr<CConnection> c;
+	CClient * client;
+
+	CServerHandler();
+
+	void resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names = nullptr);
+	void startLocalServerAndConnect();
+	void justConnectToServer(const std::string &addr = "", const ui16 port = 0);
+	void applyPacksOnLobbyScreen();
+	void stopServerConnection();
+
+	// Helpers for lobby state access
+	std::set<PlayerColor> getHumanColors();
+	PlayerColor myFirstColor() const;
+	bool isMyColor(PlayerColor color) const;
+	ui8 myFirstId() const; // Used by chat only!
+
+	bool isServerLocal() const;
+	bool isHost() const;
+	bool isGuest() const;
+
+	static ui16 getDefaultPort();
+	static std::string getDefaultPortStr();
+
+	// Lobby server API for UI
+	void sendClientConnecting() const override;
+	void sendClientDisconnecting() override;
+	void setCampaignState(std::shared_ptr<CCampaignState> newCampaign) override;
+	void setCampaignMap(int mapId) const override;
+	void setCampaignBonus(int bonusId) const override;
+	void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const override;
+	void setPlayer(PlayerColor color) const override;
+	void setPlayerOption(ui8 what, ui8 dir, PlayerColor player) const override;
+	void setDifficulty(int to) const override;
+	void setTurnLength(int npos) const override;
+	void sendMessage(const std::string & txt) const override;
+	void sendGuiAction(ui8 action) const override;
+	void sendStartGame(bool allowOnlyAI = false) const override;
+
+	void startGameplay();
+	void endGameplay(bool closeConnection = true);
+	void startCampaignScenario(std::shared_ptr<CCampaignState> cs = {});
+
+	// TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle
+	int howManyPlayerInterfaces();
+	ui8 getLoadMode();
+
+	void debugStartTest(std::string filename, bool save = false);
+};
+
+extern CServerHandler * CSH;

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 229 - 544
client/Client.cpp


+ 71 - 124
client/Client.h

@@ -9,25 +9,24 @@
  */
 #pragma once
 
-
 #include "../lib/IGameCallback.h"
 #include "../lib/battle/BattleAction.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/int3.h"
+#include "../lib/CondSh.h"
+#include "../lib/CPathfinder.h"
 
 struct CPack;
+struct CPackForServer;
 class CCampaignState;
 class CBattleCallback;
 class IGameEventsReceiver;
 class IBattleEventsReceiver;
 class CBattleGameInterface;
-struct StartInfo;
 class CGameState;
 class CGameInterface;
-class CConnection;
 class CCallback;
-class BattleAction;
-struct SharedMemory;
+struct BattleAction;
 class CClient;
 class CScriptingModule;
 struct CPathsInfo;
@@ -35,31 +34,8 @@ class BinaryDeserializer;
 class BinarySerializer;
 namespace boost { class thread; }
 
-/// structure to handle running server and connecting to it
-class CServerHandler
-{
-private:
-	void callServer(); //calls server via system(), should be called as thread
-public:
-	CStopWatch th;
-	boost::thread *serverThread; //thread that called system to run server
-	SharedMemory * shared;
-	std::string uuid;
-	bool verbose; //whether to print log msgs
-
-	//functions setting up local server
-	void startServer(); //creates a thread with callServer
-	void waitForServer(); //waits till server is ready
-	CConnection * connectToServer(); //connects to server
-
-	//////////////////////////////////////////////////////////////////////////
-	static CConnection * justConnectToServer(const std::string &host = "", const ui16 port = 0); //connects to given host without taking any other actions (like setting up server)
-	static ui16 getDefaultPort();
-	static std::string getDefaultPortStr();
-
-	CServerHandler(bool runServer = false);
-	virtual ~CServerHandler();
-};
+template<typename T> class CApplier;
+class CBaseForCLApply;
 
 template<typename T>
 class ThreadSafeVector
@@ -78,7 +54,7 @@ public:
 		cond.notify_all();
 	}
 
-	void pushBack(const T &item)
+	void pushBack(const T & item)
 	{
 		TLock lock(mx);
 		items.push_back(item);
@@ -97,14 +73,14 @@ public:
 		return TLock(mx);
 	}
 
-	void waitWhileContains(const T &item)
+	void waitWhileContains(const T & item)
 	{
 		auto lock = getLock();
 		while(vstd::contains(items, item))
 			cond.wait(lock);
 	}
 
-	bool tryRemovingElement(const T&item) //returns false if element was not present
+	bool tryRemovingElement(const T & item) //returns false if element was not present
 	{
 		auto lock = getLock();
 		auto itr = vstd::find(items, item);
@@ -122,105 +98,104 @@ public:
 /// Class which handles client - server logic
 class CClient : public IGameCallback
 {
+	std::shared_ptr<CApplier<CBaseForCLApply>> applier;
 	std::unique_ptr<CPathsInfo> pathInfo;
 
 	std::map<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
+	void waitForMoveAndSend(PlayerColor color);
+
 public:
-	std::map<PlayerColor,std::shared_ptr<CCallback> > callbacks; //callbacks given to player interfaces
-	std::map<PlayerColor,std::shared_ptr<CBattleCallback> > battleCallbacks; //callbacks given to player interfaces
+	std::map<PlayerColor, std::shared_ptr<CCallback>> callbacks; //callbacks given to player interfaces
+	std::map<PlayerColor, std::shared_ptr<CBattleCallback>> battleCallbacks; //callbacks given to player interfaces
 	std::vector<std::shared_ptr<IGameEventsReceiver>> privilegedGameEventReceivers; //scripting modules, spectator interfaces
 	std::vector<std::shared_ptr<IBattleEventsReceiver>> privilegedBattleEventReceivers; //scripting modules, spectator interfaces
 	std::map<PlayerColor, std::shared_ptr<CGameInterface>> playerint;
 	std::map<PlayerColor, std::shared_ptr<CBattleGameInterface>> battleints;
 
-	std::map<PlayerColor,std::vector<std::shared_ptr<IGameEventsReceiver>>> additionalPlayerInts;
-	std::map<PlayerColor,std::vector<std::shared_ptr<IBattleEventsReceiver>>> additionalBattleInts;
-
-	bool hotSeat;
-	CConnection *serv;
+	std::map<PlayerColor, std::vector<std::shared_ptr<IGameEventsReceiver>>> additionalPlayerInts;
+	std::map<PlayerColor, std::vector<std::shared_ptr<IBattleEventsReceiver>>> additionalBattleInts;
 
 	boost::optional<BattleAction> curbaction;
 
-	CScriptingModule *erm;
-
-	static ThreadSafeVector<int> waitingRequest;//FIXME: make this normal field (need to join all threads before client destruction)
-
-
-	//void sendRequest(const CPackForServer *request, bool waitForRealization);
+	CScriptingModule * erm;
 	CClient();
-	CClient(CConnection *con, StartInfo *si);
-	~CClient();
 
-	void init();
-	void newGame(CConnection *con, StartInfo *si); //con - connection to server
+	void newGame();
+	void loadGame();
+	void serialize(BinarySerializer & h, const int version);
+	void serialize(BinaryDeserializer & h, const int version);
+
+	void save(const std::string & fname);
+	void endGame();
 
-	void loadNeutralBattleAI();
+	void initMapHandler();
+	void initPlayerInterfaces();
+	std::string aiNameForPlayer(const PlayerSettings & ps, bool battleAI); //empty means no AI -> human
+	std::string aiNameForPlayer(bool battleAI);
 	void installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color, bool battlecb = false);
 	void installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback = true);
-	std::string aiNameForPlayer(const PlayerSettings &ps, bool battleAI); //empty means no AI -> human
-	std::string aiNameForPlayer(bool battleAI);
 
-	void endGame(bool closeConnection = true);
-	void stopConnection();
-	void save(const std::string & fname);
-	void loadGame(const std::string & fname, const bool server = true, const std::vector<int>& humanplayerindices = std::vector<int>(), const int loadnumplayers = 1, int player_ = -1, const std::string & ipaddr = "", const ui16 port = 0);
-	void run();
-	void campaignMapFinished( std::shared_ptr<CCampaignState> camp );
-	void finishCampaign( std::shared_ptr<CCampaignState> camp );
-	void proposeNextMission(std::shared_ptr<CCampaignState> camp);
 
-	void invalidatePaths();
-	const CPathsInfo * getPathsInfo(const CGHeroInstance *h);
+	static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
+
+	void handlePack(CPack * pack); //applies the given pack and deletes it
+	void commitPackage(CPackForClient * pack) override;
+	int sendRequest(const CPackForServer * request, PlayerColor player); //returns ID given to that request
 
-	bool terminate;	// tell to terminate
-	std::unique_ptr<boost::thread> connectionHandler; //thread running run() method
-	boost::mutex connectionHandlerMutex;
+	void battleStarted(const BattleInfo * info);
+	void commenceTacticPhaseForInt(std::shared_ptr<CBattleGameInterface> battleInt); //will be called as separate thread
+	void battleFinished();
+	void startPlayerBattleAction(PlayerColor color);
+	void stopPlayerBattleAction(PlayerColor color);
+	void stopAllBattleActions();
 
-	//////////////////////////////////////////////////////////////////////////
+	void invalidatePaths();
+	const CPathsInfo * getPathsInfo(const CGHeroInstance * h);
 	virtual PlayerColor getLocalPlayer() const override;
 
-	//////////////////////////////////////////////////////////////////////////
-	//not working yet, will be implement somewhen later with support for local-sim-based gameplay
-	void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> &spells) override {};
+	friend class CCallback; //handling players actions
+	friend class CBattleCallback; //handling players actions
+
+	void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> & spells) override {};
 	bool removeObject(const CGObjectInstance * obj) override {return false;};
 	void setBlockVis(ObjectInstanceID objid, bool bv) override {};
 	void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {};
-	void changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs=false) override {};
-	void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false) override {};
+	void changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs = false) override {};
+	void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs = false) override {};
 
-	void showBlockingDialog(BlockingDialog *iw) override {};
+	void showBlockingDialog(BlockingDialog * iw) override {};
 	void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override {};
-	void showTeleportDialog(TeleportDialog *iw) override {};
+	void showTeleportDialog(TeleportDialog * iw) override {};
 	void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) override {};
 	void giveResource(PlayerColor player, Res::ERes which, int val) override {};
 	virtual void giveResources(PlayerColor player, TResources resources) override {};
 
-	void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override {};
-	void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) override {};
-	bool changeStackType(const StackLocation &sl, const CCreature *c) override {return false;};
-	bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) override {return false;};
-	bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count) override {return false;};
-	bool eraseStack(const StackLocation &sl, bool forceRemoval = false) override{return false;};
-	bool swapStacks(const StackLocation &sl1, const StackLocation &sl2) override {return false;}
-	bool addToSlot(const StackLocation &sl, const CCreature *c, TQuantity count) override {return false;}
-	void tryJoiningArmy(const CArmedInstance *src, const CArmedInstance *dst, bool removeObjWhenFinished, bool allowMerging) override {}
-	bool moveStack(const StackLocation &src, const StackLocation &dst, TQuantity count = -1) override {return false;}
-
-	void removeAfterVisit(const CGObjectInstance *object) override {};
-
-	void giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos) override {};
-	void giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos) override {};
-	void putArtifact(const ArtifactLocation &al, const CArtifactInstance *a) override {};
-	void removeArtifact(const ArtifactLocation &al) override {};
-	bool moveArtifact(const ArtifactLocation &al1, const ArtifactLocation &al2) override {return false;};
+	void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet & creatures, bool remove) override {};
+	void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> & creatures) override {};
+	bool changeStackType(const StackLocation & sl, const CCreature * c) override {return false;};
+	bool changeStackCount(const StackLocation & sl, TQuantity count, bool absoluteValue = false) override {return false;};
+	bool insertNewStack(const StackLocation & sl, const CCreature * c, TQuantity count) override {return false;};
+	bool eraseStack(const StackLocation & sl, bool forceRemoval = false) override {return false;};
+	bool swapStacks(const StackLocation & sl1, const StackLocation & sl2) override {return false;}
+	bool addToSlot(const StackLocation & sl, const CCreature * c, TQuantity count) override {return false;}
+	void tryJoiningArmy(const CArmedInstance * src, const CArmedInstance * dst, bool removeObjWhenFinished, bool allowMerging) override {}
+	bool moveStack(const StackLocation & src, const StackLocation & dst, TQuantity count = -1) override {return false;}
+
+	void removeAfterVisit(const CGObjectInstance * object) override {};
+
+	void giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, ArtifactPosition pos) override {};
+	void giveHeroArtifact(const CGHeroInstance * h, const CArtifactInstance * a, ArtifactPosition pos) override {};
+	void putArtifact(const ArtifactLocation & al, const CArtifactInstance * a) override {};
+	void removeArtifact(const ArtifactLocation & al) override {};
+	bool moveArtifact(const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;};
 	void synchronizeArtifactHandlerLists() override {};
 
 	void showCompInfo(ShowInInfobox * comp) override {};
 	void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
 	void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
-	void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override {}; //use hero=nullptr for no hero
-	void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
-	void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
+	void startBattlePrimary(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool creatureBank = false, const CGTownInstance * town = nullptr) override {}; //use hero=nullptr for no hero
+	void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
+	void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
 	void setAmount(ObjectInstanceID objid, ui32 val) override {};
 	bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
 	void giveHeroBonus(GiveBonus * bonus) override {};
@@ -232,33 +207,5 @@ public:
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {};
 
 	void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) override {}
-	void changeFogOfWar(std::unordered_set<int3, ShashInt3> &tiles, PlayerColor player, bool hide) override {}
-
-	//////////////////////////////////////////////////////////////////////////
-	friend class CCallback; //handling players actions
-	friend class CBattleCallback; //handling players actions
-
-	int sendRequest(const CPack *request, PlayerColor player); //returns ID given to that request
-
-	void handlePack( CPack * pack ); //applies the given pack and deletes it
-	void battleStarted(const BattleInfo * info);
-	void commenceTacticPhaseForInt(std::shared_ptr<CBattleGameInterface> battleInt); //will be called as separate thread
-
-	void commitPackage(CPackForClient *pack) override;
-
-	//////////////////////////////////////////////////////////////////////////
-
-	void serialize(BinarySerializer & h, const int version);
-	void serialize(BinaryDeserializer & h, const int version);
-
-	void serialize(BinarySerializer & h, const int version, const std::set<PlayerColor>& playerIDs);
-	void serialize(BinaryDeserializer & h, const int version, const std::set<PlayerColor>& playerIDs);
-	void battleFinished();
-
-    void startPlayerBattleAction(PlayerColor color);
-
-    void stopPlayerBattleAction(PlayerColor color);
-    void stopAllBattleActions();
-private:
-	void waitForMoveAndSend(PlayerColor color);
+	void changeFogOfWar(std::unordered_set<int3, ShashInt3> & tiles, PlayerColor player, bool hide) override {}
 };

+ 6 - 14
client/NetPacksClient.cpp

@@ -41,6 +41,7 @@
 #include "widgets/MiscWidgets.h"
 #include "widgets/AdventureMapClasses.h"
 #include "CMT.h"
+#include "CServerHandler.h"
 
 // TODO: as Tow suggested these template should all be part of CClient
 // This will require rework spectator interface properly though
@@ -357,17 +358,6 @@ void RemoveBonus::applyCl(CClient *cl)
 	}
 }
 
-void UpdateCampaignState::applyCl(CClient *cl)
-{
-	cl->stopConnection();
-	cl->campaignMapFinished(camp);
-}
-
-void PrepareForAdvancingCampaign::applyCl(CClient *cl)
-{
-	cl->serv->prepareForSendingHeroes();
-}
-
 void RemoveObject::applyFirstCl(CClient *cl)
 {
 	const CGObjectInstance *o = cl->getObj(id);
@@ -757,7 +747,7 @@ void PackageApplied::applyCl(CClient *cl)
 {
 	callInterfaceIfPresent(cl, player, &IGameEventsReceiver::requestRealized, this);
 	if(!CClient::waitingRequest.tryRemovingElement(requestID))
-		logNetwork->warn("Surprising server message!");
+		logNetwork->warn("Surprising server message! PackageApplied for unknown requestID!");
 }
 
 void SystemMessage::applyCl(CClient *cl)
@@ -777,11 +767,13 @@ void PlayerBlocked::applyCl(CClient *cl)
 
 void YourTurn::applyCl(CClient *cl)
 {
+	logNetwork->debug("Server gives turn to %s", player.getStr());
+
 	callAllInterfaces(cl, &IGameEventsReceiver::playerStartsTurn, player);
 	callOnlyThatInterface(cl, player, &CGameInterface::yourTurn);
 }
 
-void SaveGame::applyCl(CClient *cl)
+void SaveGameClient::applyCl(CClient *cl)
 {
 	const auto stem = FileInfo::GetPathStem(fname);
 	CResourceHandler::get("local")->createResource(stem.to_string() + ".vcgm1");
@@ -798,7 +790,7 @@ void SaveGame::applyCl(CClient *cl)
 	}
 }
 
-void PlayerMessage::applyCl(CClient *cl)
+void PlayerMessageClient::applyCl(CClient *cl)
 {
 	logNetwork->debug("Player %s sends a message: %s", player.getStr(), text);
 

+ 142 - 0
client/NetPacksLobbyClient.cpp

@@ -0,0 +1,142 @@
+/*
+ * NetPacksLobbyClient.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 "lobby/CSelectionBase.h"
+#include "lobby/CLobbyScreen.h"
+
+#include "lobby/OptionsTab.h"
+#include "lobby/RandomMapTab.h"
+#include "lobby/SelectionTab.h"
+#include "lobby/CBonusSelection.h"
+
+#include "CServerHandler.h"
+#include "CGameInfo.h"
+#include "gui/CGuiHandler.h"
+#include "widgets/Buttons.h"
+#include "widgets/TextControls.h"
+
+#include "../lib/CConfigHandler.h"
+#include "../lib/CGeneralTextHandler.h"
+#include "../lib/NetPacksLobby.h"
+#include "../lib/serializer/Connection.h"
+
+bool LobbyClientConnected::applyOnLobbyHandler(CServerHandler * handler)
+{
+	// Check if it's LobbyClientConnected for our client
+	if(uuid == handler->c->uuid)
+	{
+		handler->c->connectionID = clientId;
+		if(!settings["session"]["headless"].Bool())
+			GH.pushInt(new CLobbyScreen(static_cast<ESelectionScreen>(handler->screenType)));
+		handler->state = EClientState::LOBBY;
+		return true;
+	}
+	return false;
+}
+
+void LobbyClientConnected::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
+{
+	if(uuid == handler->c->uuid)
+	{
+	}
+}
+
+bool LobbyClientDisconnected::applyOnLobbyHandler(CServerHandler * handler)
+{
+	if(clientId != c->connectionID)
+		return false;
+
+	handler->stopServerConnection();
+	return true;
+}
+
+void LobbyClientDisconnected::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
+{
+	GH.popIntTotally(lobby);
+}
+
+void LobbyChatMessage::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
+{
+	if(lobby)
+	{
+		lobby->card->chat->addNewMessage(playerName + ": " + message);
+		lobby->card->setChat(true);
+		if(lobby->buttonChat)
+			lobby->buttonChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL);
+	}
+}
+
+void LobbyGuiAction::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
+{
+	if(!handler->isGuest())
+		return;
+
+	switch(action)
+	{
+	case NO_TAB:
+		lobby->toggleTab(lobby->curTab);
+		break;
+	case OPEN_OPTIONS:
+		lobby->toggleTab(lobby->tabOpt);
+		break;
+	case OPEN_SCENARIO_LIST:
+		lobby->toggleTab(lobby->tabSel);
+		break;
+	case OPEN_RANDOM_MAP_OPTIONS:
+		lobby->toggleTab(lobby->tabRand);
+		break;
+	}
+}
+
+bool LobbyStartGame::applyOnLobbyHandler(CServerHandler * handler)
+{
+	if(handler->state == EClientState::GAMEPLAY)
+	{
+		handler->endGameplay(false);
+	}
+	handler->state = EClientState::STARTING;
+	if(handler->si->mode != StartInfo::LOAD_GAME)
+	{
+		handler->si = initializedStartInfo;
+	}
+	if(settings["session"]["headless"].Bool())
+		handler->startGameplay();
+	return true;
+}
+
+void LobbyStartGame::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
+{
+	CMM->showLoadingScreen(std::bind(&CServerHandler::startGameplay, handler));
+}
+
+bool LobbyUpdateState::applyOnLobbyHandler(CServerHandler * handler)
+{
+	hostChanged = state.hostClientId != handler->hostClientId;
+	static_cast<LobbyState &>(*handler) = state;
+	return true;
+}
+
+void LobbyUpdateState::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
+{
+	if(!lobby->bonusSel && handler->si->campState && handler->state == EClientState::LOBBY_CAMPAIGN)
+	{
+		lobby->bonusSel = new CBonusSelection();
+		GH.pushInt(lobby->bonusSel);
+	}
+
+	if(lobby->bonusSel)
+		lobby->bonusSel->updateAfterStateChange();
+	else
+		lobby->updateAfterStateChange();
+
+	if(hostChanged)
+		lobby->toggleMode(handler->isHost());
+}

+ 3 - 3
client/gui/CGuiHandler.cpp

@@ -106,7 +106,7 @@ void CGuiHandler::popInt(IShowActivatable *top)
 		listInt.front()->activate();
 	totalRedraw();
 
-	pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
+	pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
 }
 
 void CGuiHandler::popIntTotally(IShowActivatable *top)
@@ -132,7 +132,7 @@ void CGuiHandler::pushInt(IShowActivatable *newInt)
 	objsToBlit.push_back(newInt);
 	totalRedraw();
 
-	pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
+	pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
 }
 
 void CGuiHandler::popInts(int howMany)
@@ -155,7 +155,7 @@ void CGuiHandler::popInts(int howMany)
 	}
 	fakeMouseMove();
 
-	pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
+	pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
 }
 
 IShowActivatable * CGuiHandler::topInt()

+ 15 - 5
client/gui/CGuiHandler.h

@@ -22,6 +22,20 @@ class IShowable;
 enum class EIntObjMouseBtnType;
 template <typename T> struct CondSh;
 
+// TODO: event handling need refactoring
+enum EUserEvent
+{
+	/*CHANGE_SCREEN_RESOLUTION = 1,*/
+	RETURN_TO_MAIN_MENU = 2,
+	//STOP_CLIENT = 3,
+	RESTART_GAME = 4,
+	RETURN_TO_MENU_LOAD,
+	FULLSCREEN_TOGGLED,
+	CAMPAIGN_START_SCENARIO,
+	FORCE_QUIT, //quit client without question
+	INTERFACE_CHANGED
+};
+
 // A fps manager which holds game updates at a constant rate
 class CFramerateManager
 {
@@ -119,11 +133,6 @@ public:
 
 extern CGuiHandler GH; //global gui handler
 
-template <typename T> void pushIntT()
-{
-	GH.pushInt(new T());
-}
-
 struct SObjectConstruction
 {
 	CIntObject *myObj;
@@ -142,5 +151,6 @@ struct SSetCaptureState
 #define OBJ_CONSTRUCTION SObjectConstruction obj__i(this)
 #define OBJECT_CONSTRUCTION_CAPTURING(actions) defActions = actions; SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this)
 #define OBJ_CONSTRUCTION_CAPTURING_ALL defActions = 255; SSetCaptureState obj__i1(true, 255); SObjectConstruction obj__i(this)
+#define OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE defActions = 255 - DISPOSE; SSetCaptureState obj__i1(true, 255 - DISPOSE); SObjectConstruction obj__i(this)
 #define BLOCK_CAPTURING SSetCaptureState obj__i(false, 0)
 #define BLOCK_CAPTURING_DONT_TOUCH_REC_ACTIONS SSetCaptureState obj__i(false, GH.defActionsDef)

+ 2 - 0
client/gui/SDL_Extensions.cpp

@@ -20,6 +20,8 @@ const SDL_Color Colors::YELLOW = { 229, 215, 123, 0 };
 const SDL_Color Colors::WHITE = { 255, 243, 222, 0 };
 const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, 0 };
 const SDL_Color Colors::GREEN = { 0, 255, 0, 0 };
+const SDL_Color Colors::ORANGE = { 232, 184, 32, 0 };
+const SDL_Color Colors::BRIGHT_YELLOW = { 242, 226, 110, 0 };
 const SDL_Color Colors::DEFAULT_KEY_COLOR = {0, 255, 255, 0};
 
 void SDL_UpdateRect(SDL_Surface *surface, int x, int y, int w, int h)

+ 6 - 0
client/gui/SDL_Extensions.h

@@ -89,6 +89,12 @@ public:
 	/** green color used for in-game console */
 	static const SDL_Color GREEN;
 
+	/** the h3 orange color, used for blocked buttons */
+	static const SDL_Color ORANGE;
+
+	/** the h3 bright yellow color, used for selection border */
+	static const SDL_Color BRIGHT_YELLOW;
+
 	/** default key color for all 8 & 24 bit graphics */
 	static const SDL_Color DEFAULT_KEY_COLOR;
 };

+ 534 - 0
client/lobby/CBonusSelection.cpp

@@ -0,0 +1,534 @@
+/*
+ * CBonusSelection.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 "CBonusSelection.h"
+#include "CSelectionBase.h"
+
+#include "../CGameInfo.h"
+#include "../CMessage.h"
+#include "../CBitmapHandler.h"
+#include "../CMusicHandler.h"
+#include "../CVideoHandler.h"
+#include "../CPlayerInterface.h"
+#include "../CServerHandler.h"
+#include "../gui/CAnimation.h"
+#include "../gui/CGuiHandler.h"
+#include "../mainmenu/CMainMenu.h"
+#include "../mainmenu/CPrologEpilogVideo.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+#include "../windows/GUIClasses.h"
+#include "../windows/InfoWindows.h"
+
+#include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/CGeneralTextHandler.h"
+
+#include "../../lib/CArtHandler.h"
+#include "../../lib/CBuildingHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
+
+#include "../../lib/CSkillHandler.h"
+#include "../../lib/CTownHandler.h"
+#include "../../lib/CHeroHandler.h"
+#include "../../lib/CCreatureHandler.h"
+#include "../../lib/StartInfo.h"
+
+#include "../../lib/mapping/CCampaignHandler.h"
+#include "../../lib/mapping/CMapService.h"
+#include "../../lib/mapping/CMapInfo.h"
+
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+std::shared_ptr<CCampaignState> CBonusSelection::getCampaign()
+{
+	return CSH->si->campState;
+}
+
+CBonusSelection::CBonusSelection()
+	: CWindowObject(BORDERED)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	static const std::string bgNames[] =
+	{
+		"E1_BG.BMP", "G2_BG.BMP", "E2_BG.BMP", "G1_BG.BMP", "G3_BG.BMP", "N1_BG.BMP",
+		"S1_BG.BMP", "BR_BG.BMP", "IS_BG.BMP", "KR_BG.BMP", "NI_BG.BMP", "TA_BG.BMP", "AR_BG.BMP", "HS_BG.BMP",
+		"BB_BG.BMP", "NB_BG.BMP", "EL_BG.BMP", "RN_BG.BMP", "UA_BG.BMP", "SP_BG.BMP"
+	};
+	loadPositionsOfGraphics();
+	setBackground(bgNames[getCampaign()->camp->header.mapVersion]);
+
+	panelBackground = std::make_shared<CPicture>("CAMPBRF.BMP", 456, 6);
+
+	buttonStart = std::make_shared<CButton>(Point(475, 536), "CBBEGIB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), SDLK_RETURN);
+	buttonRestart = std::make_shared<CButton>(Point(475, 536), "CBRESTB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), SDLK_RETURN);
+	buttonBack = std::make_shared<CButton>(Point(624, 536), "CBCANCB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), SDLK_ESCAPE);
+
+	campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, EAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
+
+	iconsMapSizes = std::make_shared<CAnimImage>("SCNRMPSZ", 4, 0, 735, 26);
+
+	labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, EAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
+	campaignDescription = std::make_shared<CTextBox>(getCampaign()->camp->header.description, Rect(480, 86, 286, 117), 1);
+
+	mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, EAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getName());
+	labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, EAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
+	mapDescription = std::make_shared<CTextBox>("", Rect(480, 280, 286, 117), 1);
+
+	labelChooseBonus = std::make_shared<CLabel>(511, 432, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[71]);
+	groupBonuses = std::make_shared<CToggleGroup>(std::bind(&IServerAPI::setCampaignBonus, CSH, _1));
+
+	flagbox = std::make_shared<CFlagBox>(Rect(486, 407, 335, 23));
+
+	std::vector<std::string> difficulty;
+	boost::split(difficulty, CGI->generaltexth->allTexts[492], boost::is_any_of(" "));
+	labelDifficulty = std::make_shared<CLabel>(689, 432, FONT_MEDIUM, EAlignment::TOPLEFT, Colors::WHITE, difficulty.back());
+
+	for(size_t b = 0; b < difficultyIcons.size(); ++b)
+	{
+		difficultyIcons[b] = std::make_shared<CAnimImage>("GSPBUT" + boost::lexical_cast<std::string>(b + 3) + ".DEF", 0, 0, 709, 455);
+	}
+
+	if(getCampaign()->camp->header.difficultyChoosenByPlayer)
+	{
+		buttonDifficultyLeft = std::make_shared<CButton>(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this));
+		buttonDifficultyRight = std::make_shared<CButton>(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this));
+	}
+
+	for(int g = 0; g < getCampaign()->camp->scenarios.size(); ++g)
+	{
+		if(getCampaign()->camp->conquerable(g))
+			regions.push_back(std::make_shared<CRegion>(g, true, true, campDescriptions[getCampaign()->camp->header.mapVersion]));
+		else if(getCampaign()->camp->scenarios[g].conquered) //display as striped
+			regions.push_back(std::make_shared<CRegion>(g, false, false, campDescriptions[getCampaign()->camp->header.mapVersion]));
+	}
+}
+
+void CBonusSelection::loadPositionsOfGraphics()
+{
+	const JsonNode config(ResourceID("config/campaign_regions.json"));
+	int idx = 0;
+
+	for(const JsonNode & campaign : config["campaign_regions"].Vector())
+	{
+		SCampPositions sc;
+
+		sc.campPrefix = campaign["prefix"].String();
+		sc.colorSuffixLength = campaign["color_suffix_length"].Float();
+
+		for(const JsonNode & desc : campaign["desc"].Vector())
+		{
+			SCampPositions::SRegionDesc rd;
+
+			rd.infix = desc["infix"].String();
+			rd.xpos = desc["x"].Float();
+			rd.ypos = desc["y"].Float();
+			sc.regions.push_back(rd);
+		}
+
+		campDescriptions.push_back(sc);
+
+		idx++;
+	}
+
+	assert(idx == CGI->generaltexth->campaignMapNames.size());
+}
+
+void CBonusSelection::createBonusesIcons()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	const CCampaignScenario & scenario = getCampaign()->camp->scenarios[CSH->campaignMap];
+	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
+	groupBonuses = std::make_shared<CToggleGroup>(std::bind(&IServerAPI::setCampaignBonus, CSH, _1));
+
+	static const char * bonusPics[] =
+	{
+		"SPELLBON.DEF",	// Spell
+		"TWCRPORT.DEF", // Monster
+		"", // Building - BO*.BMP
+		"ARTIFBON.DEF", // Artifact
+		"SPELLBON.DEF", // Spell scroll
+		"PSKILBON.DEF", // Primary skill
+		"SSKILBON.DEF", // Secondary skill
+		"BORES.DEF", // Resource
+		"PORTRAITSLARGE", // Hero HPL*.BMP
+		"PORTRAITSLARGE"
+		// Player - CREST58.DEF
+	};
+
+	for(int i = 0; i < bonDescs.size(); i++)
+	{
+		std::string picName = bonusPics[bonDescs[i].type];
+		size_t picNumber = bonDescs[i].info2;
+
+		std::string desc;
+		switch(bonDescs[i].type)
+		{
+		case CScenarioTravel::STravelBonus::SPELL:
+			desc = CGI->generaltexth->allTexts[715];
+			boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
+			break;
+		case CScenarioTravel::STravelBonus::MONSTER:
+			picNumber = bonDescs[i].info2 + 2;
+			desc = CGI->generaltexth->allTexts[717];
+			boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info3));
+			boost::algorithm::replace_first(desc, "%s", CGI->creh->creatures[bonDescs[i].info2]->namePl);
+			break;
+		case CScenarioTravel::STravelBonus::BUILDING:
+		{
+			int faction = -1;
+			for(auto & elem : CSH->si->playerInfos)
+			{
+				if(elem.second.isControlledByHuman())
+				{
+					faction = elem.second.castle;
+					break;
+				}
+
+			}
+			assert(faction != -1);
+
+			BuildingID buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
+			picName = graphics->ERMUtoPicture[faction][buildID];
+			picNumber = -1;
+
+			if(vstd::contains(CGI->townh->factions[faction]->town->buildings, buildID))
+				desc = CGI->townh->factions[faction]->town->buildings.find(buildID)->second->Name();
+
+			break;
+		}
+		case CScenarioTravel::STravelBonus::ARTIFACT:
+			desc = CGI->generaltexth->allTexts[715];
+			boost::algorithm::replace_first(desc, "%s", CGI->arth->artifacts[bonDescs[i].info2]->Name());
+			break;
+		case CScenarioTravel::STravelBonus::SPELL_SCROLL:
+			desc = CGI->generaltexth->allTexts[716];
+			boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
+			break;
+		case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
+		{
+			int leadingSkill = -1;
+			std::vector<std::pair<int, int>> toPrint; //primary skills to be listed <num, val>
+			const ui8 * ptr = reinterpret_cast<const ui8 *>(&bonDescs[i].info2);
+			for(int g = 0; g < GameConstants::PRIMARY_SKILLS; ++g)
+			{
+				if(leadingSkill == -1 || ptr[g] > ptr[leadingSkill])
+				{
+					leadingSkill = g;
+				}
+				if(ptr[g] != 0)
+				{
+					toPrint.push_back(std::make_pair(g, ptr[g]));
+				}
+			}
+			picNumber = leadingSkill;
+			desc = CGI->generaltexth->allTexts[715];
+
+			std::string substitute; //text to be printed instead of %s
+			for(int v = 0; v < toPrint.size(); ++v)
+			{
+				substitute += boost::lexical_cast<std::string>(toPrint[v].second);
+				substitute += " " + CGI->generaltexth->primarySkillNames[toPrint[v].first];
+				if(v != toPrint.size() - 1)
+				{
+					substitute += ", ";
+				}
+			}
+
+			boost::algorithm::replace_first(desc, "%s", substitute);
+			break;
+		}
+		case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
+			desc = CGI->generaltexth->allTexts[718];
+
+			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level
+			boost::algorithm::replace_first(desc, "%s", CGI->skillh->skillName(bonDescs[i].info2)); //skill name
+			picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
+
+			break;
+		case CScenarioTravel::STravelBonus::RESOURCE:
+		{
+			int serialResID = 0;
+			switch(bonDescs[i].info1)
+			{
+			case 0:
+			case 1:
+			case 2:
+			case 3:
+			case 4:
+			case 5:
+			case 6:
+				serialResID = bonDescs[i].info1;
+				break;
+			case 0xFD: //wood + ore
+				serialResID = 7;
+				break;
+			case 0xFE: //rare resources
+				serialResID = 8;
+				break;
+			}
+			picNumber = serialResID;
+
+			desc = CGI->generaltexth->allTexts[717];
+			boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info2));
+			std::string replacement;
+			if(serialResID <= 6)
+			{
+				replacement = CGI->generaltexth->restypes[serialResID];
+			}
+			else
+			{
+				replacement = CGI->generaltexth->allTexts[714 + serialResID];
+			}
+			boost::algorithm::replace_first(desc, "%s", replacement);
+			break;
+		}
+		case CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO:
+		{
+			auto superhero = getCampaign()->camp->scenarios[bonDescs[i].info2].strongestHero(PlayerColor(bonDescs[i].info1));
+			if(!superhero)
+				logGlobal->warn("No superhero! How could it be transferred?");
+			picNumber = superhero ? superhero->portrait : 0;
+			desc = CGI->generaltexth->allTexts[719];
+
+			boost::algorithm::replace_first(desc, "%s", getCampaign()->camp->scenarios[bonDescs[i].info2].scenarioName);
+			break;
+		}
+
+		case CScenarioTravel::STravelBonus::HERO:
+
+			desc = CGI->generaltexth->allTexts[718];
+			boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //hero's color
+
+			if(bonDescs[i].info2 == 0xFFFF)
+			{
+				boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->allTexts[101]); //hero's name
+				picNumber = -1;
+				picName = "CBONN1A3.BMP";
+			}
+			else
+			{
+				boost::algorithm::replace_first(desc, "%s", CGI->heroh->heroes[bonDescs[i].info2]->name);
+			}
+			break;
+		}
+
+		CToggleButton * bonusButton = new CToggleButton(Point(475 + i * 68, 455), "", CButton::tooltip(desc, desc));
+
+		if(picNumber != -1)
+			picName += ":" + boost::lexical_cast<std::string>(picNumber);
+
+		auto anim = std::make_shared<CAnimation>();
+		anim->setCustom(picName, 0);
+		bonusButton->setImage(anim);
+		if(CSH->campaignBonus == i)
+			bonusButton->setBorderColor(Colors::BRIGHT_YELLOW);
+		groupBonuses->addToggle(i, bonusButton);
+	}
+
+	if(vstd::contains(getCampaign()->chosenCampaignBonuses, CSH->campaignMap))
+	{
+		groupBonuses->setSelected(getCampaign()->chosenCampaignBonuses[CSH->campaignMap]);
+	}
+}
+
+void CBonusSelection::updateAfterStateChange()
+{
+	if(CSH->state != EClientState::GAMEPLAY)
+	{
+		buttonRestart->disable();
+		buttonStart->enable();
+		if(!getCampaign()->mapsConquered.empty())
+			buttonBack->block(true);
+		else
+			buttonBack->block(false);
+	}
+	else
+	{
+		buttonStart->disable();
+		buttonRestart->enable();
+		buttonBack->block(false);
+	}
+	if(CSH->campaignBonus == -1)
+	{
+		buttonStart->block(getCampaign()->camp->scenarios[CSH->campaignMap].travelOptions.bonusesToChoose.size());
+	}
+	else if(buttonStart->isBlocked())
+	{
+		buttonStart->block(false);
+	}
+
+	for(auto region : regions)
+		region->updateState();
+
+	if(!CSH->mi)
+		return;
+	iconsMapSizes->setFrame(CSH->mi->getMapSizeIconId());
+	mapDescription->setText(CSH->mi->getDescription());
+	for(size_t i = 0; i < difficultyIcons.size(); i++)
+	{
+		if(i == CSH->si->difficulty)
+			difficultyIcons[i]->enable();
+		else
+			difficultyIcons[i]->disable();
+	}
+	flagbox->recreate();
+	createBonusesIcons();
+}
+
+void CBonusSelection::goBack()
+{
+	if(CSH->state != EClientState::GAMEPLAY)
+	{
+		GH.popInts(2);
+	}
+	else
+	{
+		GH.popIntTotally(this);
+	}
+	// TODO: we can actually only pop bonus selection interface for custom campaigns
+	// Though this would require clearing CLobbyScreen::bonusSel pointer when poping this interface
+/*
+	else
+	{
+		GH.popIntTotally(this);
+		CSH->state = EClientState::LOBBY;
+	}
+*/
+}
+
+void CBonusSelection::startMap()
+{
+	auto showPrologVideo = [=]()
+	{
+		auto exitCb = [=]()
+		{
+			logGlobal->info("Starting scenario %d", CSH->campaignMap);
+			CSH->sendStartGame();
+		};
+
+		const CCampaignScenario & scenario = getCampaign()->camp->scenarios[CSH->campaignMap];
+		if(scenario.prolog.hasPrologEpilog)
+		{
+			GH.pushInt(new CPrologEpilogVideo(scenario.prolog, exitCb));
+		}
+		else
+		{
+			exitCb();
+		}
+	};
+
+	if(LOCPLINT) // we're currently ingame, so ask for starting new map and end game
+	{
+		GH.popInt(this);
+		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
+		{
+			GH.curInt = CMainMenu::create();
+			showPrologVideo();
+		}, 0);
+	}
+	else
+	{
+		showPrologVideo();
+	}
+}
+
+void CBonusSelection::restartMap()
+{
+	GH.popInt(this);
+	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
+	{
+		CSH->startCampaignScenario();
+	}, 0);
+}
+
+void CBonusSelection::increaseDifficulty()
+{
+	CSH->setDifficulty(CSH->si->difficulty + 1);
+}
+
+void CBonusSelection::decreaseDifficulty()
+{
+	CSH->setDifficulty(CSH->si->difficulty - 1);
+}
+
+CBonusSelection::CRegion::CRegion(int id, bool accessible, bool selectable, const SCampPositions & campDsc)
+	: CIntObject(LCLICK | RCLICK), idOfMapAndRegion(id), accessible(accessible), selectable(selectable)
+{
+	OBJ_CONSTRUCTION;
+	static const std::string colors[2][8] =
+	{
+		{"R", "B", "N", "G", "O", "V", "T", "P"},
+		{"Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi"}
+	};
+
+	const SCampPositions::SRegionDesc & desc = campDsc.regions[idOfMapAndRegion];
+	pos.x += desc.xpos;
+	pos.y += desc.ypos;
+
+	std::string prefix = campDsc.campPrefix + desc.infix + "_";
+	std::string suffix = colors[campDsc.colorSuffixLength - 1][CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionColor];
+	graphicsNotSelected = std::make_shared<CPicture>(prefix + "En" + suffix + ".BMP");
+	graphicsNotSelected->disable();
+	graphicsSelected = std::make_shared<CPicture>(prefix + "Se" + suffix + ".BMP");
+	graphicsSelected->disable();
+	graphicsStriped = std::make_shared<CPicture>(prefix + "Co" + suffix + ".BMP");
+	graphicsStriped->disable();
+	pos.w = graphicsNotSelected->bg->w;
+	pos.h = graphicsNotSelected->bg->h;
+
+}
+
+void CBonusSelection::CRegion::updateState()
+{
+	if(!accessible)
+	{
+		graphicsNotSelected->disable();
+		graphicsSelected->disable();
+		graphicsStriped->enable();
+	}
+	else if(CSH->campaignMap == idOfMapAndRegion)
+	{
+		graphicsNotSelected->disable();
+		graphicsSelected->enable();
+		graphicsStriped->disable();
+	}
+	else
+	{
+		graphicsNotSelected->enable();
+		graphicsSelected->disable();
+		graphicsStriped->disable();
+	}
+}
+
+void CBonusSelection::CRegion::clickLeft(tribool down, bool previousState)
+{
+	//select if selectable & clicked inside our graphic
+	if(indeterminate(down))
+		return;
+
+	if(!down && selectable && !CSDL_Ext::isTransparent(*graphicsNotSelected, GH.current->motion.x - pos.x, GH.current->motion.y - pos.y))
+	{
+		CSH->setCampaignMap(idOfMapAndRegion);
+	}
+}
+
+void CBonusSelection::CRegion::clickRight(tribool down, bool previousState)
+{
+	// FIXME: For some reason "down" is only ever contain indeterminate_value
+	auto text = CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionText;
+	if(!CSDL_Ext::isTransparent(*graphicsNotSelected, GH.current->motion.x - pos.x, GH.current->motion.y - pos.y) && text.size())
+	{
+		CRClickPopup::createAndPush(text);
+	}
+}

+ 93 - 0
client/lobby/CBonusSelection.h

@@ -0,0 +1,93 @@
+/*
+ * CBonusSelection.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 "../windows/CWindowObject.h"
+
+class SDL_Surface;
+class CCampaignState;
+class CButton;
+class CTextBox;
+class CToggleGroup;
+class CAnimImage;
+class CLabel;
+class CFlagBox;
+
+/// Campaign screen where you can choose one out of three starting bonuses
+class CBonusSelection : public CWindowObject
+{
+public:
+	std::shared_ptr<CCampaignState> getCampaign();
+	CBonusSelection();
+
+	struct SCampPositions
+	{
+		std::string campPrefix;
+		int colorSuffixLength;
+
+		struct SRegionDesc
+		{
+			std::string infix;
+			int xpos, ypos;
+		};
+
+		std::vector<SRegionDesc> regions;
+
+	};
+
+	class CRegion
+		: public CIntObject
+	{
+		CBonusSelection * owner;
+		std::shared_ptr<CPicture> graphicsNotSelected;
+		std::shared_ptr<CPicture> graphicsSelected;
+		std::shared_ptr<CPicture> graphicsStriped;
+		int idOfMapAndRegion;
+		bool accessible; // false if region should be striped
+		bool selectable; // true if region should be selectable
+	public:
+		CRegion(int id, bool accessible, bool selectable, const SCampPositions & campDsc);
+		void updateState();
+		void clickLeft(tribool down, bool previousState) override;
+		void clickRight(tribool down, bool previousState) override;
+	};
+
+	void loadPositionsOfGraphics();
+	void createBonusesIcons();
+	void updateAfterStateChange();
+
+	// Event handlers
+	void goBack();
+	void startMap();
+	void restartMap();
+	void increaseDifficulty();
+	void decreaseDifficulty();
+
+	std::shared_ptr<CPicture> panelBackground;
+	std::shared_ptr<CButton> buttonStart;
+	std::shared_ptr<CButton> buttonRestart;
+	std::shared_ptr<CButton> buttonBack;
+	std::shared_ptr<CLabel> campaignName;
+	std::shared_ptr<CLabel> labelCampaignDescription;
+	std::shared_ptr<CTextBox> campaignDescription;
+	std::shared_ptr<CLabel> mapName;
+	std::shared_ptr<CLabel> labelMapDescription;
+	std::shared_ptr<CTextBox> mapDescription;
+	std::vector<SCampPositions> campDescriptions;
+	std::vector<std::shared_ptr<CRegion>> regions;
+	std::shared_ptr<CFlagBox> flagbox;
+
+	std::shared_ptr<CLabel> labelChooseBonus;
+	std::shared_ptr<CToggleGroup> groupBonuses;
+	std::shared_ptr<CLabel> labelDifficulty;
+	std::array<std::shared_ptr<CAnimImage>, 5> difficultyIcons;
+	std::shared_ptr<CButton> buttonDifficultyLeft;
+	std::shared_ptr<CButton> buttonDifficultyRight;
+	std::shared_ptr<CAnimImage> iconsMapSizes;
+};

+ 201 - 0
client/lobby/CLobbyScreen.cpp

@@ -0,0 +1,201 @@
+/*
+ * CLobbyScreen.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 "CLobbyScreen.h"
+#include "CBonusSelection.h"
+#include "SelectionTab.h"
+#include "RandomMapTab.h"
+#include "OptionsTab.h"
+#include "../CServerHandler.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../widgets/Buttons.h"
+#include "../windows/InfoWindows.h"
+
+#include "../../CCallback.h"
+
+#include "../CGameInfo.h"
+#include "../../lib/NetPacksLobby.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/mapping/CMapInfo.h"
+#include "../../lib/mapping/CCampaignHandler.h"
+#include "../../lib/rmg/CMapGenOptions.h"
+
+CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
+	: CSelectionBase(screenType), bonusSel(nullptr)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	tabSel = std::make_shared<SelectionTab>(screenType);
+	curTab = tabSel;
+
+	auto initLobby = [&]()
+	{
+		tabSel->callOnSelect = std::bind(&IServerAPI::setMapInfo, CSH, _1, nullptr);
+
+		buttonSelect = std::make_shared<CButton>(Point(411, 80), "GSPBUTT.DEF", CGI->generaltexth->zelp[45], 0, SDLK_s);
+		buttonSelect->addCallback([&]()
+		{
+			toggleTab(tabSel);
+			CSH->setMapInfo(tabSel->getSelectedMapInfo());
+		});
+
+		buttonOptions = std::make_shared<CButton>(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), SDLK_a);
+	};
+
+	buttonChat = std::make_shared<CButton>(Point(619, 83), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), SDLK_h);
+	buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL);
+
+	switch(screenType)
+	{
+	case ESelectionScreen::newGame:
+	{
+		tabOpt = std::make_shared<OptionsTab>();
+		tabRand = std::make_shared<RandomMapTab>();
+		tabRand->mapInfoChanged += std::bind(&IServerAPI::setMapInfo, CSH, _1, _2);
+		buttonRMG = std::make_shared<CButton>(Point(411, 105), "GSPBUTT.DEF", CGI->generaltexth->zelp[47], 0, SDLK_r);
+		buttonRMG->addCallback([&]()
+		{
+			toggleTab(tabRand);
+			tabRand->updateMapInfoByHost(); // TODO: This is only needed to force-update mapInfo in CSH when tab is opened
+		});
+
+		card->iconDifficulty->addCallback(std::bind(&IServerAPI::setDifficulty, CSH, _1));
+
+		buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRBEG.DEF", CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, false), SDLK_b);
+		initLobby();
+		break;
+	}
+	case ESelectionScreen::loadGame:
+	{
+		tabOpt = std::make_shared<OptionsTab>();
+		buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRLOD.DEF", CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, false), SDLK_l);
+		initLobby();
+		break;
+	}
+	case ESelectionScreen::campaignList:
+		tabSel->callOnSelect = std::bind(&IServerAPI::setMapInfo, CSH, _1, nullptr);
+		buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRLOD.DEF", CButton::tooltip(), std::bind(&CLobbyScreen::startCampaign, this), SDLK_b);
+		break;
+	}
+
+	buttonStart->assignedKeys.insert(SDLK_RETURN);
+
+	buttonBack = std::make_shared<CButton>(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], [&](){CSH->sendClientDisconnecting(); GH.popIntTotally(this);}, SDLK_ESCAPE);
+}
+
+CLobbyScreen::~CLobbyScreen()
+{
+	// TODO: For now we always destroy whole lobby when leaving bonus selection screen
+	if(CSH->state == EClientState::LOBBY_CAMPAIGN)
+		CSH->sendClientDisconnecting();
+}
+
+void CLobbyScreen::toggleTab(std::shared_ptr<CIntObject> tab)
+{
+	if(tab == curTab)
+		CSH->sendGuiAction(LobbyGuiAction::NO_TAB);
+	else if(tab == tabOpt)
+		CSH->sendGuiAction(LobbyGuiAction::OPEN_OPTIONS);
+	else if(tab == tabSel)
+		CSH->sendGuiAction(LobbyGuiAction::OPEN_SCENARIO_LIST);
+	else if(tab == tabRand)
+		CSH->sendGuiAction(LobbyGuiAction::OPEN_RANDOM_MAP_OPTIONS);
+	CSelectionBase::toggleTab(tab);
+}
+
+void CLobbyScreen::startCampaign()
+{
+	if(CSH->mi)
+	{
+		auto ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(CSH->mi->fileURI));
+		CSH->setCampaignState(ourCampaign);
+	}
+}
+
+void CLobbyScreen::startScenario(bool allowOnlyAI)
+{
+	try
+	{
+		CSH->sendStartGame(allowOnlyAI);
+		buttonStart->block(true);
+	}
+	catch(ExceptionMapMissing & e)
+	{
+
+	}
+	catch(ExceptionNoHuman & e)
+	{
+		// You must position yourself prior to starting the game.
+		CInfoWindow::showYesNoDialog(std::ref(CGI->generaltexth->allTexts[530]), nullptr, 0, std::bind(&CLobbyScreen::startScenario, this, true), false, PlayerColor(1));
+	}
+	catch(ExceptionNoTemplate & e)
+	{
+		GH.pushInt(CInfoWindow::create(CGI->generaltexth->allTexts[751]));
+	}
+	catch(...)
+	{
+
+	}
+}
+
+void CLobbyScreen::toggleMode(bool host)
+{
+	tabSel->toggleMode();
+	buttonStart->block(!host);
+	if(screenType == ESelectionScreen::campaignList)
+		return;
+
+	auto buttonColor = host ? Colors::WHITE : Colors::ORANGE;
+	buttonSelect->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor);
+	buttonOptions->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor);
+	if(buttonRMG)
+	{
+		buttonRMG->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor);
+		buttonRMG->block(!host);
+	}
+	buttonSelect->block(!host);
+	buttonOptions->block(!host);
+
+	if(CSH->mi)
+		tabOpt->recreate();
+}
+
+void CLobbyScreen::toggleChat()
+{
+	card->toggleChat();
+	if(card->showChat)
+		buttonChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL);
+	else
+		buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL);
+}
+
+void CLobbyScreen::updateAfterStateChange()
+{
+	if(CSH->mi && tabOpt)
+		tabOpt->recreate();
+
+	card->changeSelection();
+	if(card->iconDifficulty)
+		card->iconDifficulty->setSelected(CSH->si->difficulty);
+
+	if(curTab == tabRand && CSH->si->mapGenOptions)
+		tabRand->setMapGenOptions(CSH->si->mapGenOptions);
+}
+
+const StartInfo * CLobbyScreen::getStartInfo()
+{
+	return CSH->si.get();
+}
+
+const CMapInfo * CLobbyScreen::getMapInfo()
+{
+	return CSH->mi.get();
+}

+ 35 - 0
client/lobby/CLobbyScreen.h

@@ -0,0 +1,35 @@
+/*
+ * CLobbyScreen.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 "CSelectionBase.h"
+
+class CBonusSelection;
+
+class CLobbyScreen : public CSelectionBase
+{
+public:
+	std::shared_ptr<CButton> buttonChat;
+
+	CLobbyScreen(ESelectionScreen type);
+	~CLobbyScreen();
+	void toggleTab(std::shared_ptr<CIntObject> tab) override;
+	void startCampaign();
+	void startScenario(bool allowOnlyAI = false);
+	void toggleMode(bool host);
+	void toggleChat();
+
+	void updateAfterStateChange();
+
+	const CMapInfo * getMapInfo() override;
+	const StartInfo * getStartInfo() override;
+
+	CBonusSelection * bonusSel;
+};

+ 97 - 0
client/lobby/CSavingScreen.cpp

@@ -0,0 +1,97 @@
+/*
+ * CSavingScreen.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 "CSavingScreen.h"
+#include "SelectionTab.h"
+
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/TextControls.h"
+
+#include "../../CCallback.h"
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/StartInfo.h"
+#include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/mapping/CMapInfo.h"
+
+CSavingScreen::CSavingScreen()
+	: CSelectionBase(ESelectionScreen::saveGame)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	center(pos);
+	// TODO: we should really use std::shared_ptr for passing StartInfo around.
+	localSi = new StartInfo(*LOCPLINT->cb->getStartInfo());
+	localMi = new CMapInfo();
+	localMi->mapHeader = std::unique_ptr<CMapHeader>(new CMapHeader(*LOCPLINT->cb->getMapHeader()));
+
+	tabSel = std::make_shared<SelectionTab>(screenType);
+	curTab = tabSel;
+	tabSel->toggleMode();
+
+	tabSel->callOnSelect = std::bind(&CSavingScreen::changeSelection, this, _1);
+	buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRSAV.DEF", CGI->generaltexth->zelp[103], std::bind(&CSavingScreen::saveGame, this), SDLK_s);
+	buttonStart->assignedKeys.insert(SDLK_RETURN);
+}
+
+CSavingScreen::~CSavingScreen()
+{
+	vstd::clear_pointer(localMi);
+}
+
+const CMapInfo * CSavingScreen::getMapInfo()
+{
+	return localMi;
+}
+
+const StartInfo * CSavingScreen::getStartInfo()
+{
+	return localSi;
+}
+
+void CSavingScreen::changeSelection(std::shared_ptr<CMapInfo> to)
+{
+	if(localMi == to.get())
+		return;
+
+	localMi = to.get();
+	localSi = localMi->scenarioOptionsOfSave;
+	card->changeSelection();
+}
+
+void CSavingScreen::saveGame()
+{
+	if(!(tabSel && tabSel->inputName && tabSel->inputName->text.size()))
+		return;
+
+	std::string path = "Saves/" + tabSel->inputName->text;
+
+	auto overWrite = [&]() -> void
+	{
+		Settings lastSave = settings.write["general"]["lastSave"];
+		lastSave->String() = path;
+		LOCPLINT->cb->save(path);
+		GH.popIntTotally(this);
+	};
+
+	if(CResourceHandler::get("local")->existsResource(ResourceID(path, EResType::CLIENT_SAVEGAME)))
+	{
+		std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite?
+		boost::algorithm::replace_first(hlp, "%s", tabSel->inputName->text);
+		LOCPLINT->showYesNoDialog(hlp, overWrite, 0, false);
+	}
+	else
+	{
+		overWrite();
+	}
+}

+ 32 - 0
client/lobby/CSavingScreen.h

@@ -0,0 +1,32 @@
+/*
+ * CSavingScreen.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 "CSelectionBase.h"
+
+class CSelectionBase;
+struct StartInfo;
+class CMapInfo;
+
+class CSavingScreen : public CSelectionBase
+{
+public:
+	const StartInfo * localSi;
+	CMapInfo * localMi;
+
+	CSavingScreen();
+	~CSavingScreen();
+
+	void changeSelection(std::shared_ptr<CMapInfo> to);
+	void saveGame();
+
+	const CMapInfo * getMapInfo() override;
+	const StartInfo * getStartInfo() override;
+};

+ 59 - 0
client/lobby/CScenarioInfoScreen.cpp

@@ -0,0 +1,59 @@
+/*
+ * CScenarioInfoScreen.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 "CScenarioInfoScreen.h"
+#include "OptionsTab.h"
+
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/Buttons.h"
+
+#include "../../CCallback.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/StartInfo.h"
+#include "../../lib/mapping/CMapInfo.h"
+
+CScenarioInfoScreen::CScenarioInfoScreen()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	localSi = new StartInfo(*LOCPLINT->cb->getStartInfo());
+	localMi = new CMapInfo();
+	localMi->mapHeader = std::unique_ptr<CMapHeader>(new CMapHeader(*LOCPLINT->cb->getMapHeader()));
+
+	screenType = ESelectionScreen::scenarioInfo;
+
+	card = std::make_shared<InfoCard>();
+	opt = std::make_shared<OptionsTab>();
+	opt->recActions = UPDATE | SHOWALL;
+	opt->recreate();
+	card->changeSelection();
+
+	card->iconDifficulty->setSelected(getCurrentDifficulty());
+	buttonBack = std::make_shared<CButton>(Point(584, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
+}
+
+CScenarioInfoScreen::~CScenarioInfoScreen()
+{
+	vstd::clear_pointer(localSi);
+	vstd::clear_pointer(localMi);
+}
+
+const CMapInfo * CScenarioInfoScreen::getMapInfo()
+{
+	return localMi;
+}
+
+const StartInfo * CScenarioInfoScreen::getStartInfo()
+{
+	return localSi;
+}

+ 30 - 0
client/lobby/CScenarioInfoScreen.h

@@ -0,0 +1,30 @@
+/*
+ * CScenarioInfoScreen.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 "CSelectionBase.h"
+
+/// Scenario information screen shown during the game
+class CScenarioInfoScreen : public CIntObject, public ISelectionScreenInfo
+{
+public:
+	std::shared_ptr<CButton> buttonBack;
+	std::shared_ptr<InfoCard> card;
+	std::shared_ptr<OptionsTab> opt;
+
+	const StartInfo * localSi;
+	CMapInfo * localMi;
+
+	CScenarioInfoScreen();
+	~CScenarioInfoScreen();
+
+	const CMapInfo * getMapInfo() override;
+	const StartInfo * getStartInfo() override;
+};

+ 407 - 0
client/lobby/CSelectionBase.cpp

@@ -0,0 +1,407 @@
+/*
+ * CSelectionBase.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 "CSelectionBase.h"
+#include "CBonusSelection.h"
+#include "CLobbyScreen.h"
+#include "OptionsTab.h"
+#include "RandomMapTab.h"
+#include "SelectionTab.h"
+
+#include "../../CCallback.h"
+
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../CMessage.h"
+#include "../CBitmapHandler.h"
+#include "../CMusicHandler.h"
+#include "../CVideoHandler.h"
+#include "../CPlayerInterface.h"
+#include "../CServerHandler.h"
+#include "../gui/CAnimation.h"
+#include "../gui/CGuiHandler.h"
+#include "../mainmenu/CMainMenu.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+#include "../windows/GUIClasses.h"
+#include "../windows/InfoWindows.h"
+
+#include "../../lib/NetPacksLobby.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CHeroHandler.h"
+#include "../../lib/CThreadHelper.h"
+#include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/mapping/CMapInfo.h"
+#include "../../lib/serializer/Connection.h"
+
+ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType)
+	: screenType(ScreenType)
+{
+	assert(!SEL);
+	SEL = this;
+}
+
+ISelectionScreenInfo::~ISelectionScreenInfo()
+{
+	assert(SEL == this);
+	SEL = nullptr;
+}
+
+int ISelectionScreenInfo::getCurrentDifficulty()
+{
+	return getStartInfo()->difficulty;
+}
+
+PlayerInfo ISelectionScreenInfo::getPlayerInfo(int color)
+{
+	return getMapInfo()->mapHeader->players[color];
+}
+
+CSelectionBase::CSelectionBase(ESelectionScreen type)
+	: CWindowObject(BORDERED | SHADOW_DISABLED), ISelectionScreenInfo(type)
+{
+	CMainMenu::create(); //we depend on its graphics
+
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	IShowActivatable::type = BLOCK_ADV_HOTKEYS;
+	pos.w = 762;
+	pos.h = 584;
+	if(screenType == ESelectionScreen::campaignList)
+	{
+		setBackground("CamCust.bmp");
+	}
+	else
+	{
+		const JsonVector & bgNames = CMainMenuConfig::get().getConfig()["game-select"].Vector();
+		setBackground(RandomGeneratorUtil::nextItem(bgNames, CRandomGenerator::getDefault())->String());
+	}
+	pos = background->center();
+	card = std::make_shared<InfoCard>();
+	buttonBack = std::make_shared<CButton>(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
+}
+
+void CSelectionBase::toggleTab(std::shared_ptr<CIntObject> tab)
+{
+	if(curTab && curTab->active)
+	{
+		curTab->deactivate();
+		curTab->recActions = 0;
+	}
+
+	if(curTab != tab)
+	{
+		tab->recActions = 255 - DISPOSE;
+		tab->activate();
+		curTab = tab;
+	}
+	else
+	{
+		curTab.reset();
+	}
+	GH.totalRedraw();
+}
+
+InfoCard::InfoCard()
+	: showChat(true)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	CIntObject::type |= REDRAW_PARENT;
+	pos.x += 393;
+	pos.y += 6;
+
+	labelSaveDate = std::make_shared<CLabel>(158, 19, FONT_SMALL, TOPLEFT, Colors::WHITE);
+	mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, TOPLEFT, Colors::YELLOW);
+	Rect descriptionRect(26, 149, 320, 115);
+	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
+	playerListBg = std::make_shared<CPicture>("CHATPLUG.bmp", 16, 276);
+	chat = std::make_shared<CChatBox>(Rect(26, 132, 340, 132));
+
+	if(SEL->screenType == ESelectionScreen::campaignList)
+	{
+		labelCampaignDescription = std::make_shared<CLabel>(26, 132, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
+	}
+	else
+	{
+		background = std::make_shared<CPicture>("GSELPOP1.bmp", 0, 0);
+		parent->addChild(background.get());
+		auto it = vstd::find(parent->children, this); //our position among parent children
+		parent->children.insert(it, background.get()); //put BG before us
+		parent->children.pop_back();
+		pos.w = background->pos.w;
+		pos.h = background->pos.h;
+		iconsMapSizes = std::make_shared<CAnimImage>("SCNRMPSZ", 4, 0, 318, 22); //let it be custom size (frame 4) by default
+
+		iconDifficulty = std::make_shared<CToggleGroup>(0);
+		{
+			static const char * difButns[] = {"GSPBUT3.DEF", "GSPBUT4.DEF", "GSPBUT5.DEF", "GSPBUT6.DEF", "GSPBUT7.DEF"};
+
+			for(int i = 0; i < 5; i++)
+			{
+				auto button = new CToggleButton(Point(110 + i * 32, 450), difButns[i], CGI->generaltexth->zelp[24 + i]);
+
+				iconDifficulty->addToggle(i, button);
+				if(SEL->screenType != ESelectionScreen::newGame)
+					button->block(true);
+			}
+		}
+
+		flagbox = std::make_shared<CFlagBox>(Rect(24, 400, 335, 23));
+		labelMapDiff = std::make_shared<CLabel>(33, 430, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[494]);
+		labelPlayerDifficulty = std::make_shared<CLabel>(133, 430, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[492] + ":");
+		labelRating = std::make_shared<CLabel>(290, 430, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[218] + ":");
+		labelScenarioName = std::make_shared<CLabel>(26, 22, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[495]);
+		labelScenarioDescription = std::make_shared<CLabel>(26, 132, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
+		labelVictoryCondition = std::make_shared<CLabel>(26, 283, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[497]);
+		labelLossCondition = std::make_shared<CLabel>(26, 339, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[498]);
+		iconsVictoryCondition = std::make_shared<CAnimImage>("SCNRVICT", 0, 0, 24, 302);
+		iconsLossCondition = std::make_shared<CAnimImage>("SCNRLOSS", 0, 0, 24, 359);
+
+		labelVictoryConditionText = std::make_shared<CLabel>(60, 307, FONT_SMALL, TOPLEFT, Colors::WHITE);
+		labelLossConditionText = std::make_shared<CLabel>(60, 366, FONT_SMALL, TOPLEFT, Colors::WHITE);
+
+		labelDifficulty = std::make_shared<CLabel>(62, 472, FONT_SMALL, CENTER, Colors::WHITE);
+		labelDifficultyPercent = std::make_shared<CLabel>(311, 472, FONT_SMALL, CENTER, Colors::WHITE);
+
+		labelGroupPlayersAssigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
+		labelGroupPlayersUnassigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
+	}
+	setChat(false);
+}
+
+void InfoCard::changeSelection()
+{
+	if(!SEL->getMapInfo())
+		return;
+
+	labelSaveDate->setText(SEL->getMapInfo()->date);
+	mapName->setText(SEL->getMapInfo()->getName());
+	mapDescription->setText(SEL->getMapInfo()->getDescription());
+
+	mapDescription->label->scrollTextTo(0);
+	if(mapDescription->slider)
+		mapDescription->slider->moveToMin();
+
+	if(SEL->screenType == ESelectionScreen::campaignList)
+		return;
+
+	iconsMapSizes->setFrame(SEL->getMapInfo()->getMapSizeIconId());
+	const CMapHeader * header = SEL->getMapInfo()->mapHeader.get();
+	iconsVictoryCondition->setFrame(header->victoryIconIndex);
+	labelVictoryConditionText->setText(header->victoryMessage);
+	iconsLossCondition->setFrame(header->defeatIconIndex);
+	labelLossConditionText->setText(header->defeatMessage);
+	flagbox->recreate();
+	labelDifficulty->setText(CGI->generaltexth->arraytxt[142 + SEL->getMapInfo()->mapHeader->difficulty]);
+	iconDifficulty->setSelected(SEL->getCurrentDifficulty());
+	const std::array<std::string, 5> difficultyPercent = {"80%", "100%", "130%", "160%", "200%"};
+	labelDifficultyPercent->setText(difficultyPercent[SEL->getCurrentDifficulty()]);
+
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	// FIXME: We recreate them each time because CLabelGroup don't use smart pointers
+	labelGroupPlayersAssigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
+	labelGroupPlayersUnassigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
+	if(!showChat)
+	{
+		labelGroupPlayersAssigned->disable();
+		labelGroupPlayersUnassigned->disable();
+	}
+	for(auto & p : CSH->playerNames)
+	{
+		const auto pset = CSH->si->getPlayersSettings(p.first);
+		int pid = p.first;
+		if(pset)
+		{
+			auto name = boost::str(boost::format("%s (%d-%d %s)") % p.second.name % p.second.connection % pid % pset->color.getStr());
+			labelGroupPlayersAssigned->add(24, 285 + labelGroupPlayersAssigned->currentSize()*graphics->fonts[FONT_SMALL]->getLineHeight(), name);
+		}
+		else
+		{
+			auto name = boost::str(boost::format("%s (%d-%d)") % p.second.name % p.second.connection % pid);
+			labelGroupPlayersUnassigned->add(193, 285 + labelGroupPlayersUnassigned->currentSize()*graphics->fonts[FONT_SMALL]->getLineHeight(), name);
+		}
+	}
+}
+
+void InfoCard::toggleChat()
+{
+	setChat(!showChat);
+}
+
+void InfoCard::setChat(bool activateChat)
+{
+	if(showChat == activateChat)
+		return;
+
+	if(activateChat)
+	{
+		if(SEL->screenType == ESelectionScreen::campaignList)
+		{
+			labelCampaignDescription->disable();
+		}
+		else
+		{
+			labelScenarioDescription->disable();
+			labelVictoryCondition->disable();
+			labelLossCondition->disable();
+			iconsVictoryCondition->disable();
+			labelVictoryConditionText->disable();
+			iconsLossCondition->disable();
+			labelLossConditionText->disable();
+			labelGroupPlayersAssigned->enable();
+			labelGroupPlayersUnassigned->enable();
+		}
+		mapDescription->disable();
+		chat->enable();
+		playerListBg->enable();
+	}
+	else
+	{
+		mapDescription->enable();
+		chat->disable();
+		playerListBg->disable();
+
+		if(SEL->screenType == ESelectionScreen::campaignList)
+		{
+			labelCampaignDescription->enable();
+		}
+		else
+		{
+			labelScenarioDescription->enable();
+			labelVictoryCondition->enable();
+			labelLossCondition->enable();
+			iconsVictoryCondition->enable();
+			iconsLossCondition->enable();
+			labelVictoryConditionText->enable();
+			labelLossConditionText->enable();
+			labelGroupPlayersAssigned->disable();
+			labelGroupPlayersUnassigned->disable();
+		}
+	}
+
+	showChat = activateChat;
+	GH.totalRedraw();
+}
+
+CChatBox::CChatBox(const Rect & rect)
+	: CIntObject(KEYBOARD | TEXTINPUT)
+{
+	OBJ_CONSTRUCTION;
+	pos += rect;
+	captureAllKeys = true;
+	type |= REDRAW_PARENT;
+
+	const int height = graphics->fonts[FONT_SMALL]->getLineHeight();
+	inputBox = std::make_shared<CTextInput>(Rect(0, rect.h - height, rect.w, height));
+	inputBox->removeUsedEvents(KEYBOARD);
+	chatHistory = std::make_shared<CTextBox>("", Rect(0, 0, rect.w, rect.h - height), 1);
+
+	chatHistory->label->color = Colors::GREEN;
+}
+
+void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
+{
+	if(key.keysym.sym == SDLK_RETURN && key.state == SDL_PRESSED && inputBox->text.size())
+	{
+		CSH->sendMessage(inputBox->text);
+		inputBox->setText("");
+	}
+	else
+		inputBox->keyPressed(key);
+}
+
+void CChatBox::addNewMessage(const std::string & text)
+{
+	CCS->soundh->playSound("CHAT");
+	chatHistory->setText(chatHistory->label->text + text + "\n");
+	if(chatHistory->slider)
+		chatHistory->slider->moveToMax();
+}
+
+CFlagBox::CFlagBox(const Rect & rect)
+	: CIntObject(RCLICK)
+{
+	pos += rect;
+	pos.w = rect.w;
+	pos.h = rect.h;
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	labelAllies = std::make_shared<CLabel>(0, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
+	labelEnemies = std::make_shared<CLabel>(133, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
+
+	iconsTeamFlags = std::make_shared<CAnimation>("ITGFLAGS.DEF");
+	iconsTeamFlags->preload();
+}
+
+void CFlagBox::recreate()
+{
+	flagsAllies.clear();
+	flagsEnemies.clear();
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	const int alliesX = 5 + labelAllies->getWidth();
+	const int enemiesX = 5 + 133 + labelEnemies->getWidth();
+	for(auto i = CSH->si->playerInfos.cbegin(); i != CSH->si->playerInfos.cend(); i++)
+	{
+		auto flag = std::make_shared<CAnimImage>(iconsTeamFlags, i->first.getNum(), 0);
+		if(i->first == CSH->myFirstColor() || CSH->getPlayerTeamId(i->first) == CSH->getPlayerTeamId(CSH->myFirstColor()))
+		{
+			flag->moveTo(Point(pos.x + alliesX + flagsAllies.size()*flag->pos.w, pos.y));
+			flagsAllies.push_back(flag);
+		}
+		else
+		{
+			flag->moveTo(Point(pos.x + enemiesX + flagsEnemies.size()*flag->pos.w, pos.y));
+			flagsEnemies.push_back(flag);
+		}
+	}
+}
+
+void CFlagBox::clickRight(tribool down, bool previousState)
+{
+	if(down && SEL->getMapInfo())
+		GH.pushInt(new CFlagBoxTooltipBox(iconsTeamFlags));
+}
+
+CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox(std::shared_ptr<CAnimation> icons)
+	: CWindowObject(BORDERED | RCLICK_POPUP | SHADOW_DISABLED, "DIBOXBCK")
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	pos.w = 256;
+	pos.h = 90 + 50 * SEL->getMapInfo()->mapHeader->howManyTeams;
+
+	labelTeamAlignment = std::make_shared<CLabel>(128, 30, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[657]);
+	labelGroupTeams = std::make_shared<CLabelGroup>(FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
+	for(int i = 0; i < SEL->getMapInfo()->mapHeader->howManyTeams; i++)
+	{
+		std::vector<ui8> flags;
+		labelGroupTeams->add(128, 65 + 50 * i, boost::str(boost::format(CGI->generaltexth->allTexts[656]) % (i+1)));
+
+		for(int j = 0; j < PlayerColor::PLAYER_LIMIT_I; j++)
+		{
+			if((SEL->getPlayerInfo(j).canHumanPlay || SEL->getPlayerInfo(j).canComputerPlay)
+				&& SEL->getPlayerInfo(j).team == TeamID(i))
+			{
+				flags.push_back(j);
+			}
+		}
+
+		int curx = 128 - 9 * flags.size();
+		for(auto & flag : flags)
+		{
+			iconsFlags.push_back(std::make_shared<CAnimImage>(icons, flag, 0, curx, 75 + 50 * i));
+			curx += 18;
+		}
+	}
+	background->scaleTo(Point(pos.w, pos.h));
+	center();
+}

+ 146 - 0
client/lobby/CSelectionBase.h

@@ -0,0 +1,146 @@
+/*
+ * CSelectionBase.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 "../mainmenu/CMainMenu.h"
+
+class CButton;
+class CTextBox;
+class CTextInput;
+class CAnimImage;
+class CToggleGroup;
+class RandomMapTab;
+class OptionsTab;
+class SelectionTab;
+class InfoCard;
+class CChatBox;
+class CMapInfo;
+struct StartInfo;
+struct PlayerInfo;
+class CLabel;
+class CFlagBox;
+class CLabelGroup;
+
+class ISelectionScreenInfo
+{
+public:
+	ESelectionScreen screenType;
+
+	ISelectionScreenInfo(ESelectionScreen ScreenType = ESelectionScreen::unknown);
+	virtual ~ISelectionScreenInfo();
+	virtual const CMapInfo * getMapInfo() = 0;
+	virtual const StartInfo * getStartInfo() = 0;
+
+	virtual int getCurrentDifficulty();
+	virtual PlayerInfo getPlayerInfo(int color);
+
+};
+
+/// The actual map selection screen which consists of the options and selection tab
+class CSelectionBase : public CWindowObject, public ISelectionScreenInfo
+{
+public:
+	std::shared_ptr<InfoCard> card;
+
+	std::shared_ptr<CButton> buttonSelect;
+	std::shared_ptr<CButton> buttonRMG;
+	std::shared_ptr<CButton> buttonOptions;
+	std::shared_ptr<CButton> buttonStart;
+	std::shared_ptr<CButton> buttonBack;
+
+	std::shared_ptr<SelectionTab> tabSel;
+	std::shared_ptr<OptionsTab> tabOpt;
+	std::shared_ptr<RandomMapTab> tabRand;
+	std::shared_ptr<CIntObject> curTab;
+
+	CSelectionBase(ESelectionScreen type);
+	virtual void toggleTab(std::shared_ptr<CIntObject> tab);
+};
+
+class InfoCard : public CIntObject
+{
+	std::shared_ptr<CPicture> playerListBg;
+	std::shared_ptr<CPicture> background;
+
+	std::shared_ptr<CAnimImage> iconsVictoryCondition;
+	std::shared_ptr<CAnimImage> iconsLossCondition;
+	std::shared_ptr<CAnimImage> iconsMapSizes;
+
+	std::shared_ptr<CLabel> labelSaveDate;
+	std::shared_ptr<CLabel> labelScenarioName;
+	std::shared_ptr<CLabel> labelScenarioDescription;
+	std::shared_ptr<CLabel> labelVictoryCondition;
+	std::shared_ptr<CLabel> labelLossCondition;
+	std::shared_ptr<CLabel> labelMapDiff;
+	std::shared_ptr<CLabel> labelPlayerDifficulty;
+	std::shared_ptr<CLabel> labelRating;
+	std::shared_ptr<CLabel> labelCampaignDescription;
+
+	std::shared_ptr<CLabel> mapName;
+	std::shared_ptr<CTextBox> mapDescription;
+	std::shared_ptr<CLabel> labelDifficulty;
+	std::shared_ptr<CLabel> labelDifficultyPercent;
+	std::shared_ptr<CLabel> labelVictoryConditionText;
+	std::shared_ptr<CLabel> labelLossConditionText;
+
+	std::shared_ptr<CLabelGroup> labelGroupPlayersAssigned;
+	std::shared_ptr<CLabelGroup> labelGroupPlayersUnassigned;
+public:
+
+	bool showChat;
+	std::shared_ptr<CChatBox> chat;
+	std::shared_ptr<CFlagBox> flagbox;
+
+	std::shared_ptr<CToggleGroup> iconDifficulty;
+
+	InfoCard();
+	void changeSelection();
+	void toggleChat();
+	void setChat(bool activateChat);
+};
+
+class CChatBox : public CIntObject
+{
+public:
+	std::shared_ptr<CTextBox> chatHistory;
+	std::shared_ptr<CTextInput> inputBox;
+
+	CChatBox(const Rect & rect);
+
+	void keyPressed(const SDL_KeyboardEvent & key) override;
+
+	void addNewMessage(const std::string & text);
+};
+
+class CFlagBox : public CIntObject
+{
+	std::shared_ptr<CAnimation> iconsTeamFlags;
+	std::shared_ptr<CLabel> labelAllies;
+	std::shared_ptr<CLabel> labelEnemies;
+	std::vector<std::shared_ptr<CAnimImage>> flagsAllies;
+	std::vector<std::shared_ptr<CAnimImage>> flagsEnemies;
+
+public:
+	CFlagBox(const Rect & rect);
+	void recreate();
+	void clickRight(tribool down, bool previousState) override;
+	void showTeamsPopup();
+
+	class CFlagBoxTooltipBox : public CWindowObject
+	{
+		std::shared_ptr<CLabel> labelTeamAlignment;
+		std::shared_ptr<CLabelGroup> labelGroupTeams;
+		std::vector<std::shared_ptr<CAnimImage>> iconsFlags;
+	public:
+		CFlagBoxTooltipBox(std::shared_ptr<CAnimation> icons);
+	};
+};
+
+extern ISelectionScreenInfo * SEL;

+ 537 - 0
client/lobby/OptionsTab.cpp

@@ -0,0 +1,537 @@
+/*
+ * OptionsTab.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 "CSelectionBase.h"
+#include "OptionsTab.h"
+
+#include "../CBitmapHandler.h"
+#include "../CGameInfo.h"
+#include "../CServerHandler.h"
+#include "../gui/CAnimation.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+#include "../windows/GUIClasses.h"
+#include "../windows/InfoWindows.h"
+
+#include "../../lib/NetPacksLobby.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CArtHandler.h"
+#include "../../lib/CTownHandler.h"
+#include "../../lib/CHeroHandler.h"
+#include "../../lib/mapping/CMap.h"
+#include "../../lib/mapping/CMapInfo.h"
+
+OptionsTab::OptionsTab()
+{
+	recActions = 0;
+	OBJ_CONSTRUCTION;
+	background = std::make_shared<CPicture>("ADVOPTBK", 0, 6);
+	pos = background->pos;
+	labelTitle = std::make_shared<CLabel>(222, 30, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[515]);
+	labelSubTitle = std::make_shared<CMultiLineLabel>(Rect(60, 44, 320, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[516]);
+
+	labelPlayerNameAndHandicap = std::make_shared<CMultiLineLabel>(Rect(58, 86, 100, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[517]);
+	labelStartingTown = std::make_shared<CMultiLineLabel>(Rect(163, 86, 70, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[518]);
+	labelStartingHero = std::make_shared<CMultiLineLabel>(Rect(239, 86, 70, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[519]);
+	labelStartingBonus = std::make_shared<CMultiLineLabel>(Rect(315, 86, 70, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[520]);
+	if(SEL->screenType == ESelectionScreen::newGame || SEL->screenType == ESelectionScreen::loadGame || SEL->screenType == ESelectionScreen::scenarioInfo)
+	{
+		sliderTurnDuration = std::make_shared<CSlider>(Point(55, 551), 194, std::bind(&IServerAPI::setTurnLength, CSH, _1), 1, GameConstants::POSSIBLE_TURNTIME.size(), GameConstants::POSSIBLE_TURNTIME.size(), true, CSlider::BLUE);
+		labelPlayerTurnDuration = std::make_shared<CLabel>(222, 538, FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[521]);
+		labelTurnDurationValue = std::make_shared<CLabel>(319, 559, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
+	}
+}
+
+void OptionsTab::recreate()
+{
+	entries.clear();
+
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	for(auto & pInfo : SEL->getStartInfo()->playerInfos)
+	{
+		entries.insert(std::make_pair(pInfo.first, std::make_shared<PlayerOptionsEntry>(pInfo.second)));
+	}
+
+	if(sliderTurnDuration)
+	{
+		sliderTurnDuration->moveTo(vstd::find_pos(GameConstants::POSSIBLE_TURNTIME, SEL->getStartInfo()->turnTime));
+		labelTurnDurationValue->setText(CGI->generaltexth->turnDurations[sliderTurnDuration->getValue()]);
+	}
+}
+
+size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
+{
+	enum EBonusSelection //frames of bonuses file
+	{
+		WOOD_ORE = 0,   CRYSTAL = 1,    GEM  = 2,
+		MERCURY  = 3,   SULFUR  = 5,    GOLD = 8,
+		ARTIFACT = 9,   RANDOM  = 10,
+		WOOD = 0,       ORE     = 0,    MITHRIL = 10, // resources unavailable in bonuses file
+
+		TOWN_RANDOM = 38,  TOWN_NONE = 39, // Special frames in ITPA
+		HERO_RANDOM = 163, HERO_NONE = 164 // Special frames in PortraitsSmall
+	};
+
+	switch(type)
+	{
+	case TOWN:
+		switch(settings.castle)
+		{
+		case PlayerSettings::NONE:
+			return TOWN_NONE;
+		case PlayerSettings::RANDOM:
+			return TOWN_RANDOM;
+		default:
+			return CGI->townh->factions[settings.castle]->town->clientInfo.icons[true][false] + 2;
+		}
+
+	case HERO:
+		switch(settings.hero)
+		{
+		case PlayerSettings::NONE:
+			return HERO_NONE;
+		case PlayerSettings::RANDOM:
+			return HERO_RANDOM;
+		default:
+		{
+			if(settings.heroPortrait >= 0)
+				return settings.heroPortrait;
+			return CGI->heroh->heroes[settings.hero]->imageIndex;
+		}
+		}
+
+	case BONUS:
+	{
+		switch(settings.bonus)
+		{
+		case PlayerSettings::RANDOM:
+			return RANDOM;
+		case PlayerSettings::ARTIFACT:
+			return ARTIFACT;
+		case PlayerSettings::GOLD:
+			return GOLD;
+		case PlayerSettings::RESOURCE:
+		{
+			switch(CGI->townh->factions[settings.castle]->town->primaryRes)
+			{
+			case Res::WOOD_AND_ORE:
+				return WOOD_ORE;
+			case Res::WOOD:
+				return WOOD;
+			case Res::MERCURY:
+				return MERCURY;
+			case Res::ORE:
+				return ORE;
+			case Res::SULFUR:
+				return SULFUR;
+			case Res::CRYSTAL:
+				return CRYSTAL;
+			case Res::GEMS:
+				return GEM;
+			case Res::GOLD:
+				return GOLD;
+			case Res::MITHRIL:
+				return MITHRIL;
+			}
+		}
+		}
+	}
+	}
+	return 0;
+}
+
+std::string OptionsTab::CPlayerSettingsHelper::getImageName()
+{
+	switch(type)
+	{
+	case OptionsTab::TOWN:
+		return "ITPA";
+	case OptionsTab::HERO:
+		return "PortraitsSmall";
+	case OptionsTab::BONUS:
+		return "SCNRSTAR";
+	}
+	return "";
+}
+
+std::string OptionsTab::CPlayerSettingsHelper::getName()
+{
+	switch(type)
+	{
+	case TOWN:
+	{
+		switch(settings.castle)
+		{
+		case PlayerSettings::NONE:
+			return CGI->generaltexth->allTexts[523];
+		case PlayerSettings::RANDOM:
+			return CGI->generaltexth->allTexts[522];
+		default:
+			return CGI->townh->factions[settings.castle]->name;
+		}
+	}
+	case HERO:
+	{
+		switch(settings.hero)
+		{
+		case PlayerSettings::NONE:
+			return CGI->generaltexth->allTexts[523];
+		case PlayerSettings::RANDOM:
+			return CGI->generaltexth->allTexts[522];
+		default:
+		{
+			if(!settings.heroName.empty())
+				return settings.heroName;
+			return CGI->heroh->heroes[settings.hero]->name;
+		}
+		}
+	}
+	case BONUS:
+	{
+		switch(settings.bonus)
+		{
+		case PlayerSettings::RANDOM:
+			return CGI->generaltexth->allTexts[522];
+		default:
+			return CGI->generaltexth->arraytxt[214 + settings.bonus];
+		}
+	}
+	}
+	return "";
+}
+
+
+std::string OptionsTab::CPlayerSettingsHelper::getTitle()
+{
+	switch(type)
+	{
+	case OptionsTab::TOWN:
+		return (settings.castle < 0) ? CGI->generaltexth->allTexts[103] : CGI->generaltexth->allTexts[80];
+	case OptionsTab::HERO:
+		return (settings.hero < 0) ? CGI->generaltexth->allTexts[101] : CGI->generaltexth->allTexts[77];
+	case OptionsTab::BONUS:
+	{
+		switch(settings.bonus)
+		{
+		case PlayerSettings::RANDOM:
+			return CGI->generaltexth->allTexts[86]; //{Random Bonus}
+		case PlayerSettings::ARTIFACT:
+			return CGI->generaltexth->allTexts[83]; //{Artifact Bonus}
+		case PlayerSettings::GOLD:
+			return CGI->generaltexth->allTexts[84]; //{Gold Bonus}
+		case PlayerSettings::RESOURCE:
+			return CGI->generaltexth->allTexts[85]; //{Resource Bonus}
+		}
+	}
+	}
+	return "";
+}
+std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
+{
+	switch(type)
+	{
+	case TOWN:
+		return getName();
+	case HERO:
+	{
+		if(settings.hero >= 0)
+			return getName() + " - " + CGI->heroh->heroes[settings.hero]->heroClass->name;
+		return getName();
+	}
+
+	case BONUS:
+	{
+		switch(settings.bonus)
+		{
+		case PlayerSettings::GOLD:
+			return CGI->generaltexth->allTexts[87]; //500-1000
+		case PlayerSettings::RESOURCE:
+		{
+			switch(CGI->townh->factions[settings.castle]->town->primaryRes)
+			{
+			case Res::MERCURY:
+				return CGI->generaltexth->allTexts[694];
+			case Res::SULFUR:
+				return CGI->generaltexth->allTexts[695];
+			case Res::CRYSTAL:
+				return CGI->generaltexth->allTexts[692];
+			case Res::GEMS:
+				return CGI->generaltexth->allTexts[693];
+			case Res::WOOD_AND_ORE:
+				return CGI->generaltexth->allTexts[89]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
+			}
+		}
+		}
+	}
+	}
+	return "";
+}
+
+std::string OptionsTab::CPlayerSettingsHelper::getDescription()
+{
+	switch(type)
+	{
+	case TOWN:
+		return CGI->generaltexth->allTexts[104];
+	case HERO:
+		return CGI->generaltexth->allTexts[102];
+	case BONUS:
+	{
+		switch(settings.bonus)
+		{
+		case PlayerSettings::RANDOM:
+			return CGI->generaltexth->allTexts[94]; //Gold, wood and ore, or an artifact is randomly chosen as your starting bonus
+		case PlayerSettings::ARTIFACT:
+			return CGI->generaltexth->allTexts[90]; //An artifact is randomly chosen and equipped to your starting hero
+		case PlayerSettings::GOLD:
+			return CGI->generaltexth->allTexts[92]; //At the start of the game, 500-1000 gold is added to your Kingdom's resource pool
+		case PlayerSettings::RESOURCE:
+		{
+			switch(CGI->townh->factions[settings.castle]->town->primaryRes)
+			{
+			case Res::MERCURY:
+				return CGI->generaltexth->allTexts[690];
+			case Res::SULFUR:
+				return CGI->generaltexth->allTexts[691];
+			case Res::CRYSTAL:
+				return CGI->generaltexth->allTexts[688];
+			case Res::GEMS:
+				return CGI->generaltexth->allTexts[689];
+			case Res::WOOD_AND_ORE:
+				return CGI->generaltexth->allTexts[93]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
+			}
+		}
+		}
+	}
+	}
+	return "";
+}
+
+OptionsTab::CPlayerOptionTooltipBox::CPlayerOptionTooltipBox(CPlayerSettingsHelper & helper)
+	: CWindowObject(BORDERED | RCLICK_POPUP), CPlayerSettingsHelper(helper)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	int value = PlayerSettings::NONE;
+
+	switch(CPlayerSettingsHelper::type)
+	{
+		break;
+	case TOWN:
+		value = settings.castle;
+		break;
+	case HERO:
+		value = settings.hero;
+		break;
+	case BONUS:
+		value = settings.bonus;
+	}
+
+	if(value == PlayerSettings::RANDOM)
+		genBonusWindow();
+	else if(CPlayerSettingsHelper::type == BONUS)
+		genBonusWindow();
+	else if(CPlayerSettingsHelper::type == HERO)
+		genHeroWindow();
+	else if(CPlayerSettingsHelper::type == TOWN)
+		genTownWindow();
+
+	center();
+}
+
+void OptionsTab::CPlayerOptionTooltipBox::genHeader()
+{
+	backgroundTexture = std::make_shared<CFilledTexture>("DIBOXBCK", pos);
+	updateShadow();
+
+	labelTitle = std::make_shared<CLabel>(pos.w / 2 + 8, 21, FONT_MEDIUM, CENTER, Colors::YELLOW, getTitle());
+	labelSubTitle = std::make_shared<CLabel>(pos.w / 2, 88, FONT_SMALL, CENTER, Colors::WHITE, getSubtitle());
+	image = std::make_shared<CAnimImage>(getImageName(), getImageIndex(), 0, pos.w / 2 - 24, 45);
+}
+
+void OptionsTab::CPlayerOptionTooltipBox::genTownWindow()
+{
+	pos = Rect(0, 0, 228, 290);
+	genHeader();
+	labelAssociatedCreatures = std::make_shared<CLabel>(pos.w / 2 + 8, 122, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[79]);
+
+	std::vector<CComponent *> components;
+	const CTown * town = CGI->townh->factions[settings.castle]->town;
+	for(auto & elem : town->creatures)
+	{
+		if(!elem.empty())
+			components.push_back(new CComponent(CComponent::creature, elem.front(), 0, CComponent::tiny));
+	}
+	boxAssociatedCreatures = std::make_shared<CComponentBox>(components, Rect(10, 140, pos.w - 20, 140));
+}
+
+void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow()
+{
+	pos = Rect(0, 0, 292, 226);
+	genHeader();
+	labelHeroSpeciality = std::make_shared<CLabel>(pos.w / 2 + 4, 117, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[78]);
+
+	imageSpeciality = std::make_shared<CAnimImage>("UN44", CGI->heroh->heroes[settings.hero]->imageIndex, 0, pos.w / 2 - 22, 134);
+	labelSpecialityName = std::make_shared<CLabel>(pos.w / 2, 188, FONT_SMALL, CENTER, Colors::WHITE, CGI->heroh->heroes[settings.hero]->specName);
+}
+
+void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow()
+{
+	pos = Rect(0, 0, 228, 162);
+	genHeader();
+
+	textBonusDescription = std::make_shared<CTextBox>(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, CENTER, Colors::WHITE);
+}
+
+OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & settings, SelType type)
+	: CIntObject(RCLICK, position), CPlayerSettingsHelper(settings, type)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	image = std::make_shared<CAnimImage>(getImageName(), getImageIndex());
+	subtitle = std::make_shared<CLabel>(23, 39, FONT_TINY, CENTER, Colors::WHITE, getName());
+
+	pos = image->pos;
+}
+
+void OptionsTab::SelectedBox::update()
+{
+	image->setFrame(getImageIndex());
+	subtitle->setText(getName());
+}
+
+void OptionsTab::SelectedBox::clickRight(tribool down, bool previousState)
+{
+	if(down)
+	{
+		// cases when we do not need to display a message
+		if(settings.castle == -2 && CPlayerSettingsHelper::type == TOWN)
+			return;
+		if(settings.hero == -2 && !SEL->getPlayerInfo(settings.color.getNum()).hasCustomMainHero() && CPlayerSettingsHelper::type == HERO)
+			return;
+
+		GH.pushInt(new CPlayerOptionTooltipBox(*this));
+	}
+}
+
+OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S)
+	: pi(SEL->getPlayerInfo(S.color.getNum())), s(S)
+{
+	OBJ_CONSTRUCTION;
+	defActions |= SHARE_POS;
+
+	int serial = 0;
+	for(int g = 0; g < s.color.getNum(); ++g)
+	{
+		auto itred = SEL->getPlayerInfo(g);
+		if(itred.canComputerPlay || itred.canHumanPlay)
+			serial++;
+	}
+
+	pos.x += 54;
+	pos.y += 122 + serial * 50;
+
+	assert(CSH->mi && CSH->mi->mapHeader);
+	const PlayerInfo & p = SEL->getPlayerInfo(s.color.getNum());
+	assert(p.canComputerPlay || p.canHumanPlay); //someone must be able to control this player
+	if(p.canHumanPlay && p.canComputerPlay)
+		whoCanPlay = HUMAN_OR_CPU;
+	else if(p.canComputerPlay)
+		whoCanPlay = CPU;
+	else
+		whoCanPlay = HUMAN;
+
+	static const char * flags[] =
+	{
+		"AOFLGBR.DEF", "AOFLGBB.DEF", "AOFLGBY.DEF", "AOFLGBG.DEF",
+		"AOFLGBO.DEF", "AOFLGBP.DEF", "AOFLGBT.DEF", "AOFLGBS.DEF"
+	};
+	static const char * bgs[] =
+	{
+		"ADOPRPNL.bmp", "ADOPBPNL.bmp", "ADOPYPNL.bmp", "ADOPGPNL.bmp",
+		"ADOPOPNL.bmp", "ADOPPPNL.bmp", "ADOPTPNL.bmp", "ADOPSPNL.bmp"
+	};
+
+	background = std::make_shared<CPicture>(BitmapHandler::loadBitmap(bgs[s.color.getNum()]), 0, 0, true);
+	labelPlayerName = std::make_shared<CLabel>(55, 10, EFonts::FONT_SMALL, EAlignment::CENTER, Colors::WHITE, s.name);
+	labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
+
+	if(SEL->screenType == ESelectionScreen::newGame)
+	{
+		buttonTownLeft = std::make_shared<CButton>(Point(107, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[132], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::TOWN, -1, s.color));
+		buttonTownRight = std::make_shared<CButton>(Point(168, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[133], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::TOWN, +1, s.color));
+		buttonHeroLeft = std::make_shared<CButton>(Point(183, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[148], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::HERO, -1, s.color));
+		buttonHeroRight = std::make_shared<CButton>(Point(244, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[149], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::HERO, +1, s.color));
+		buttonBonusLeft = std::make_shared<CButton>(Point(259, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[164], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::BONUS, -1, s.color));
+		buttonBonusRight = std::make_shared<CButton>(Point(320, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[165], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::BONUS, +1, s.color));
+	}
+
+	hideUnavailableButtons();
+
+	if(SEL->screenType != ESelectionScreen::scenarioInfo && SEL->getPlayerInfo(s.color.getNum()).canHumanPlay)
+	{
+		flag = std::make_shared<CButton>(Point(-43, 2), flags[s.color.getNum()], CGI->generaltexth->zelp[180], std::bind(&IServerAPI::setPlayer, CSH, s.color));
+		flag->hoverable = true;
+		flag->block(CSH->isGuest());
+	}
+	else
+		flag = nullptr;
+
+	town = std::make_shared<SelectedBox>(Point(119, 2), s, TOWN);
+	hero = std::make_shared<SelectedBox>(Point(195, 2), s, HERO);
+	bonus = std::make_shared<SelectedBox>(Point(271, 2), s, BONUS);
+}
+
+void OptionsTab::PlayerOptionsEntry::hideUnavailableButtons()
+{
+	if(!buttonTownLeft)
+		return;
+
+	const bool foreignPlayer = CSH->isGuest() && !CSH->isMyColor(s.color);
+
+	if((pi.allowedFactions.size() < 2 && !pi.isFactionRandom) || foreignPlayer)
+	{
+		buttonTownLeft->disable();
+		buttonTownRight->disable();
+	}
+	else
+	{
+		buttonTownLeft->enable();
+		buttonTownRight->enable();
+	}
+
+	if((pi.defaultHero() != -1 || s.castle < 0) //fixed hero
+		|| foreignPlayer) //or not our player
+	{
+		buttonHeroLeft->disable();
+		buttonHeroRight->disable();
+	}
+	else
+	{
+		buttonHeroLeft->enable();
+		buttonHeroRight->enable();
+	}
+
+	if(foreignPlayer)
+	{
+		buttonBonusLeft->disable();
+		buttonBonusRight->disable();
+	}
+	else
+	{
+		buttonBonusLeft->enable();
+		buttonBonusRight->enable();
+	}
+}

+ 127 - 0
client/lobby/OptionsTab.h

@@ -0,0 +1,127 @@
+/*
+ * OptionsTab.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/StartInfo.h"
+#include "../../lib/mapping/CMap.h"
+
+class CSlider;
+class CLabel;
+class CMultiLineLabel;
+class CFilledTexture;
+class CAnimImage;
+class CComponentBox;
+/// The options tab which is shown at the map selection phase.
+class OptionsTab : public CIntObject
+{
+	std::shared_ptr<CPicture> background;
+	std::shared_ptr<CLabel> labelTitle;
+	std::shared_ptr<CMultiLineLabel> labelSubTitle;
+	std::shared_ptr<CMultiLineLabel> labelPlayerNameAndHandicap;
+	std::shared_ptr<CMultiLineLabel> labelStartingTown;
+	std::shared_ptr<CMultiLineLabel> labelStartingHero;
+	std::shared_ptr<CMultiLineLabel> labelStartingBonus;
+
+	std::shared_ptr<CLabel> labelPlayerTurnDuration;
+	std::shared_ptr<CLabel> labelTurnDurationValue;
+
+public:
+	enum SelType
+	{
+		TOWN,
+		HERO,
+		BONUS
+	};
+
+	struct CPlayerSettingsHelper
+	{
+		const PlayerSettings & settings;
+		const SelType type;
+
+		CPlayerSettingsHelper(const PlayerSettings & settings, SelType type)
+			: settings(settings), type(type)
+		{}
+
+		/// visible image settings
+		size_t getImageIndex();
+		std::string getImageName();
+
+		std::string getName(); /// name visible in options dialog
+		std::string getTitle(); /// title in popup box
+		std::string getSubtitle(); /// popup box subtitle
+		std::string getDescription(); /// popup box description, not always present
+	};
+
+	class CPlayerOptionTooltipBox : public CWindowObject, public CPlayerSettingsHelper
+	{
+		std::shared_ptr<CFilledTexture> backgroundTexture;
+		std::shared_ptr<CLabel> labelTitle;
+		std::shared_ptr<CLabel> labelSubTitle;
+		std::shared_ptr<CAnimImage> image;
+
+		std::shared_ptr<CLabel> labelAssociatedCreatures;
+		std::shared_ptr<CComponentBox> boxAssociatedCreatures;
+
+		std::shared_ptr<CLabel> labelHeroSpeciality;
+		std::shared_ptr<CAnimImage> imageSpeciality;
+		std::shared_ptr<CLabel> labelSpecialityName;
+
+		std::shared_ptr<CTextBox> textBonusDescription;
+
+		void genHeader();
+		void genTownWindow();
+		void genHeroWindow();
+		void genBonusWindow();
+
+	public:
+		CPlayerOptionTooltipBox(CPlayerSettingsHelper & helper);
+	};
+
+	/// Image with current town/hero/bonus
+	struct SelectedBox : public CIntObject, public CPlayerSettingsHelper
+	{
+		std::shared_ptr<CAnimImage> image;
+		std::shared_ptr<CLabel> subtitle;
+
+		SelectedBox(Point position, PlayerSettings & settings, SelType type);
+		void clickRight(tribool down, bool previousState) override;
+
+		void update();
+	};
+
+	struct PlayerOptionsEntry : public CIntObject
+	{
+		PlayerInfo pi;
+		PlayerSettings s;
+		std::shared_ptr<CLabel> labelPlayerName;
+		std::shared_ptr<CMultiLineLabel> labelWhoCanPlay;
+		std::shared_ptr<CPicture> background;
+		std::shared_ptr<CButton> buttonTownLeft;
+		std::shared_ptr<CButton> buttonTownRight;
+		std::shared_ptr<CButton> buttonHeroLeft;
+		std::shared_ptr<CButton> buttonHeroRight;
+		std::shared_ptr<CButton> buttonBonusLeft;
+		std::shared_ptr<CButton> buttonBonusRight;
+		std::shared_ptr<CButton> flag;
+		std::shared_ptr<SelectedBox> town;
+		std::shared_ptr<SelectedBox> hero;
+		std::shared_ptr<SelectedBox> bonus;
+		enum {HUMAN_OR_CPU, HUMAN, CPU} whoCanPlay;
+
+		PlayerOptionsEntry(const PlayerSettings & S);
+		void hideUnavailableButtons();
+	};
+
+	std::shared_ptr<CSlider> sliderTurnDuration;
+	std::map<PlayerColor, std::shared_ptr<PlayerOptionsEntry>> entries;
+
+	OptionsTab();
+	void recreate();
+};

+ 300 - 0
client/lobby/RandomMapTab.cpp

@@ -0,0 +1,300 @@
+/*
+ * RandomMapTab.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 "RandomMapTab.h"
+#include "CSelectionBase.h"
+
+#include "../CGameInfo.h"
+#include "../CServerHandler.h"
+#include "../gui/CAnimation.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+#include "../windows/GUIClasses.h"
+#include "../windows/InfoWindows.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/mapping/CMapInfo.h"
+#include "../../lib/rmg/CMapGenOptions.h"
+
+RandomMapTab::RandomMapTab()
+{
+	recActions = 0;
+	mapGenOptions = std::make_shared<CMapGenOptions>();
+	OBJ_CONSTRUCTION;
+	background = std::make_shared<CPicture>("RANMAPBK", 0, 6);
+
+	labelHeadlineBig = std::make_shared<CLabel>(222, 36, FONT_BIG, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[738]);
+	labelHeadlineSmall = std::make_shared<CLabel>(222, 56, FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[739]);
+
+	labelMapSize = std::make_shared<CLabel>(104, 97, FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[752]);
+	groupMapSize = std::make_shared<CToggleGroup>(0);
+	groupMapSize->pos.y += 81;
+	groupMapSize->pos.x += 158;
+	const std::vector<std::string> mapSizeBtns = {"RANSIZS", "RANSIZM", "RANSIZL", "RANSIZX"};
+	addButtonsToGroup(groupMapSize.get(), mapSizeBtns, 0, 3, 47, 198);
+	groupMapSize->setSelected(1);
+	groupMapSize->addCallback([&](int btnId)
+	{
+		auto mapSizeVal = getPossibleMapSizes();
+		mapGenOptions->setWidth(mapSizeVal[btnId]);
+		mapGenOptions->setHeight(mapSizeVal[btnId]);
+		updateMapInfoByHost();
+	});
+
+	buttonTwoLevels = std::make_shared<CToggleButton>(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]);
+	buttonTwoLevels->setSelected(true);
+	buttonTwoLevels->addCallback([&](bool on)
+	{
+		mapGenOptions->setHasTwoLevels(on);
+		updateMapInfoByHost();
+	});
+
+	labelGroupForOptions = std::make_shared<CLabelGroup>(FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE);
+	// Create number defs list
+	std::vector<std::string> numberDefs;
+	for(int i = 0; i <= 8; ++i)
+	{
+		numberDefs.push_back("RANNUM" + boost::lexical_cast<std::string>(i));
+	}
+
+	const int NUMBERS_WIDTH = 32;
+	const int BTNS_GROUP_LEFT_MARGIN = 67;
+	labelGroupForOptions->add(68, 133, CGI->generaltexth->allTexts[753]);
+	groupMaxPlayers = std::make_shared<CToggleGroup>(0);
+	groupMaxPlayers->pos.y += 153;
+	groupMaxPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN;
+	addButtonsWithRandToGroup(groupMaxPlayers.get(), numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212);
+	groupMaxPlayers->addCallback([&](int btnId)
+	{
+		mapGenOptions->setPlayerCount(btnId);
+		deactivateButtonsFrom(groupMaxTeams.get(), btnId);
+		deactivateButtonsFrom(groupCompOnlyPlayers.get(), btnId);
+		validatePlayersCnt(btnId);
+		updateMapInfoByHost();
+	});
+
+	labelGroupForOptions->add(68, 199, CGI->generaltexth->allTexts[754]);
+	groupMaxTeams = std::make_shared<CToggleGroup>(0);
+	groupMaxTeams->pos.y += 219;
+	groupMaxTeams->pos.x += BTNS_GROUP_LEFT_MARGIN;
+	addButtonsWithRandToGroup(groupMaxTeams.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222);
+	groupMaxTeams->addCallback([&](int btnId)
+	{
+		mapGenOptions->setTeamCount(btnId);
+		updateMapInfoByHost();
+	});
+
+	labelGroupForOptions->add(68, 265, CGI->generaltexth->allTexts[755]);
+	groupCompOnlyPlayers = std::make_shared<CToggleGroup>(0);
+	groupCompOnlyPlayers->pos.y += 285;
+	groupCompOnlyPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN;
+	addButtonsWithRandToGroup(groupCompOnlyPlayers.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232);
+	groupCompOnlyPlayers->addCallback([&](int btnId)
+	{
+		mapGenOptions->setCompOnlyPlayerCount(btnId);
+		deactivateButtonsFrom(groupCompOnlyTeams.get(), (btnId == 0 ? 1 : btnId));
+		validateCompOnlyPlayersCnt(btnId);
+		updateMapInfoByHost();
+	});
+
+	labelGroupForOptions->add(68, 331, CGI->generaltexth->allTexts[756]);
+	groupCompOnlyTeams = std::make_shared<CToggleGroup>(0);
+	groupCompOnlyTeams->pos.y += 351;
+	groupCompOnlyTeams->pos.x += BTNS_GROUP_LEFT_MARGIN;
+	addButtonsWithRandToGroup(groupCompOnlyTeams.get(), numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241);
+	deactivateButtonsFrom(groupCompOnlyTeams.get(), 1);
+	groupCompOnlyTeams->addCallback([&](int btnId)
+	{
+		mapGenOptions->setCompOnlyTeamCount(btnId);
+		updateMapInfoByHost();
+	});
+
+	labelGroupForOptions->add(68, 398, CGI->generaltexth->allTexts[757]);
+	const int WIDE_BTN_WIDTH = 85;
+	groupWaterContent = std::make_shared<CToggleGroup>(0);
+	groupWaterContent->pos.y += 419;
+	groupWaterContent->pos.x += BTNS_GROUP_LEFT_MARGIN;
+	const std::vector<std::string> waterContentBtns = {"RANNONE", "RANNORM", "RANISLD"};
+	addButtonsWithRandToGroup(groupWaterContent.get(), waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246);
+	groupWaterContent->addCallback([&](int btnId)
+	{
+		mapGenOptions->setWaterContent(static_cast<EWaterContent::EWaterContent>(btnId));
+		updateMapInfoByHost();
+	});
+
+	labelGroupForOptions->add(68, 465, CGI->generaltexth->allTexts[758]);
+	groupMonsterStrength = std::make_shared<CToggleGroup>(0);
+	groupMonsterStrength->pos.y += 485;
+	groupMonsterStrength->pos.x += BTNS_GROUP_LEFT_MARGIN;
+	const std::vector<std::string> monsterStrengthBtns = {"RANWEAK", "RANNORM", "RANSTRG"};
+	addButtonsWithRandToGroup(groupMonsterStrength.get(), monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251);
+	groupMonsterStrength->addCallback([&](int btnId)
+	{
+		if(btnId < 0)
+			mapGenOptions->setMonsterStrength(EMonsterStrength::RANDOM);
+		else
+			mapGenOptions->setMonsterStrength(static_cast<EMonsterStrength::EMonsterStrength>(btnId + EMonsterStrength::GLOBAL_WEAK)); //value 2 to 4
+		updateMapInfoByHost();
+	});
+
+	buttonShowRandomMaps = std::make_shared<CButton>(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]);
+
+	updateMapInfoByHost();
+}
+
+void RandomMapTab::updateMapInfoByHost()
+{
+	if(CSH->isGuest())
+		return;
+
+	// Generate header info
+	mapInfo = std::make_shared<CMapInfo>();
+	mapInfo->isRandomMap = true;
+	mapInfo->mapHeader = make_unique<CMapHeader>();
+	mapInfo->mapHeader->version = EMapFormat::SOD;
+	mapInfo->mapHeader->name = CGI->generaltexth->allTexts[740];
+	mapInfo->mapHeader->description = CGI->generaltexth->allTexts[741];
+	mapInfo->mapHeader->difficulty = 1; // Normal
+	mapInfo->mapHeader->height = mapGenOptions->getHeight();
+	mapInfo->mapHeader->width = mapGenOptions->getWidth();
+	mapInfo->mapHeader->twoLevel = mapGenOptions->getHasTwoLevels();
+
+	// Generate player information
+	mapInfo->mapHeader->players.clear();
+	int playersToGen = PlayerColor::PLAYER_LIMIT_I;
+	if(mapGenOptions->getPlayerCount() != CMapGenOptions::RANDOM_SIZE)
+		playersToGen = mapGenOptions->getPlayerCount();
+	mapInfo->mapHeader->howManyTeams = playersToGen;
+
+	for(int i = 0; i < playersToGen; ++i)
+	{
+		PlayerInfo player;
+		player.isFactionRandom = true;
+		player.canComputerPlay = true;
+		if(mapGenOptions->getCompOnlyPlayerCount() != CMapGenOptions::RANDOM_SIZE && i >= mapGenOptions->getHumanOnlyPlayerCount())
+		{
+			player.canHumanPlay = false;
+		}
+		else
+		{
+			player.canHumanPlay = true;
+		}
+		player.team = TeamID(i);
+		player.hasMainTown = true;
+		player.generateHeroAtMainTown = true;
+		mapInfo->mapHeader->players.push_back(player);
+	}
+
+	mapInfoChanged(mapInfo, mapGenOptions);
+}
+
+void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
+{
+	groupMapSize->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
+	buttonTwoLevels->setSelected(opts->getHasTwoLevels());
+	groupMaxPlayers->setSelected(opts->getPlayerCount());
+	groupMaxTeams->setSelected(opts->getTeamCount());
+	groupCompOnlyPlayers->setSelected(opts->getCompOnlyPlayerCount());
+	groupCompOnlyTeams->setSelected(opts->getCompOnlyTeamCount());
+	groupWaterContent->setSelected(opts->getWaterContent());
+	groupMonsterStrength->setSelected(opts->getMonsterStrength());
+}
+
+void RandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex) const
+{
+	addButtonsToGroup(group, defs, nStart, nEnd, btnWidth, helpStartIndex);
+
+	// Buttons are relative to button group, TODO better solution?
+	SObjectConstruction obj__i(group);
+	const std::string RANDOM_DEF = "RANRAND";
+	group->addToggle(CMapGenOptions::RANDOM_SIZE, new CToggleButton(Point(256, 0), RANDOM_DEF, CGI->generaltexth->zelp[helpRandIndex]));
+	group->setSelected(CMapGenOptions::RANDOM_SIZE);
+}
+
+void RandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex) const
+{
+	// Buttons are relative to button group, TODO better solution?
+	SObjectConstruction obj__i(group);
+	int cnt = nEnd - nStart + 1;
+	for(int i = 0; i < cnt; ++i)
+	{
+		auto button = new CToggleButton(Point(i * btnWidth, 0), defs[i + nStart], CGI->generaltexth->zelp[helpStartIndex + i]);
+		// For blocked state we should use pressed image actually
+		button->setImageOrder(0, 1, 1, 3);
+
+		group->addToggle(i + nStart, button);
+	}
+}
+
+void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
+{
+	logGlobal->debug("Blocking buttons from %d", startId);
+	for(auto toggle : group->buttons)
+	{
+		if(auto button = dynamic_cast<CToggleButton *>(toggle.second))
+		{
+			if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId)
+			{
+				button->block(false);
+			}
+			else
+			{
+				button->block(true);
+			}
+		}
+	}
+}
+
+void RandomMapTab::validatePlayersCnt(int playersCnt)
+{
+	if(playersCnt == CMapGenOptions::RANDOM_SIZE)
+	{
+		return;
+	}
+
+	if(mapGenOptions->getTeamCount() >= playersCnt)
+	{
+		mapGenOptions->setTeamCount(playersCnt - 1);
+		groupMaxTeams->setSelected(mapGenOptions->getTeamCount());
+	}
+	if(mapGenOptions->getCompOnlyPlayerCount() >= playersCnt)
+	{
+		mapGenOptions->setCompOnlyPlayerCount(playersCnt - 1);
+		groupCompOnlyPlayers->setSelected(mapGenOptions->getCompOnlyPlayerCount());
+	}
+
+	validateCompOnlyPlayersCnt(mapGenOptions->getCompOnlyPlayerCount());
+}
+
+void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
+{
+	if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE)
+	{
+		return;
+	}
+
+	if(mapGenOptions->getCompOnlyTeamCount() >= compOnlyPlayersCnt)
+	{
+		int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
+		mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount);
+		updateMapInfoByHost();
+		groupCompOnlyTeams->setSelected(compOnlyTeamCount);
+	}
+}
+
+std::vector<int> RandomMapTab::getPossibleMapSizes()
+{
+	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE};
+}

+ 58 - 0
client/lobby/RandomMapTab.h

@@ -0,0 +1,58 @@
+/*
+ * RandomMapTab.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 "CSelectionBase.h"
+
+#include "../../lib/FunctionList.h"
+
+class CMapGenOptions;
+class CToggleButton;
+class CLabel;
+class CLabelGroup;
+
+class RandomMapTab : public CIntObject
+{
+public:
+	RandomMapTab();
+
+	void updateMapInfoByHost();
+	void setMapGenOptions(std::shared_ptr<CMapGenOptions> opts);
+
+	CFunctionList<void(std::shared_ptr<CMapInfo>, std::shared_ptr<CMapGenOptions>)> mapInfoChanged;
+
+private:
+	void addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex) const;
+	void addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex) const;
+	void deactivateButtonsFrom(CToggleGroup * group, int startId);
+	void validatePlayersCnt(int playersCnt);
+	void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt);
+	std::vector<int> getPossibleMapSizes();
+
+
+	std::shared_ptr<CPicture> background;
+	std::shared_ptr<CLabel> labelHeadlineBig;
+	std::shared_ptr<CLabel> labelHeadlineSmall;
+
+	std::shared_ptr<CLabel> labelMapSize;
+	std::shared_ptr<CToggleGroup> groupMapSize;
+	std::shared_ptr<CToggleButton> buttonTwoLevels;
+
+	std::shared_ptr<CLabelGroup> labelGroupForOptions;
+	std::shared_ptr<CToggleGroup> groupMaxPlayers;
+	std::shared_ptr<CToggleGroup> groupMaxTeams;
+	std::shared_ptr<CToggleGroup> groupCompOnlyPlayers;
+	std::shared_ptr<CToggleGroup> groupCompOnlyTeams;
+	std::shared_ptr<CToggleGroup> groupWaterContent;
+	std::shared_ptr<CToggleGroup> groupMonsterStrength;
+	std::shared_ptr<CButton> buttonShowRandomMaps;
+	std::shared_ptr<CMapGenOptions> mapGenOptions;
+	std::shared_ptr<CMapInfo> mapInfo;
+};

+ 664 - 0
client/lobby/SelectionTab.cpp

@@ -0,0 +1,664 @@
+/*
+ * SelectionTab.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 "SelectionTab.h"
+#include "CSelectionBase.h"
+#include "CLobbyScreen.h"
+
+#include "../CGameInfo.h"
+#include "../CMessage.h"
+#include "../CBitmapHandler.h"
+#include "../CPlayerInterface.h"
+#include "../CServerHandler.h"
+#include "../gui/CAnimation.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+#include "../windows/GUIClasses.h"
+#include "../windows/InfoWindows.h"
+
+#include "../../CCallback.h"
+
+#include "../../lib/NetPacksLobby.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CModHandler.h"
+#include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/mapping/CMapInfo.h"
+#include "../../lib/serializer/Connection.h"
+
+
+bool mapSorter::operator()(const std::shared_ptr<CMapInfo> aaa, const std::shared_ptr<CMapInfo> bbb)
+{
+	auto a = aaa->mapHeader.get();
+	auto b = bbb->mapHeader.get();
+	if(a && b) //if we are sorting scenarios
+	{
+		switch(sortBy)
+		{
+		case _format: //by map format (RoE, WoG, etc)
+			return (a->version < b->version);
+			break;
+		case _loscon: //by loss conditions
+			return (a->defeatMessage < b->defeatMessage);
+			break;
+		case _playerAm: //by player amount
+			int playerAmntB, humenPlayersB, playerAmntA, humenPlayersA;
+			playerAmntB = humenPlayersB = playerAmntA = humenPlayersA = 0;
+			for(int i = 0; i < 8; i++)
+			{
+				if(a->players[i].canHumanPlay)
+				{
+					playerAmntA++;
+					humenPlayersA++;
+				}
+				else if(a->players[i].canComputerPlay)
+				{
+					playerAmntA++;
+				}
+				if(b->players[i].canHumanPlay)
+				{
+					playerAmntB++;
+					humenPlayersB++;
+				}
+				else if(b->players[i].canComputerPlay)
+				{
+					playerAmntB++;
+				}
+			}
+			if(playerAmntB != playerAmntA)
+				return (playerAmntA < playerAmntB);
+			else
+				return (humenPlayersA < humenPlayersB);
+			break;
+		case _size: //by size of map
+			return (a->width < b->width);
+			break;
+		case _viccon: //by victory conditions
+			return (a->victoryMessage < b->victoryMessage);
+			break;
+		case _name: //by name
+			return boost::ilexicographical_compare(a->name, b->name);
+		case _fileName: //by filename
+			return boost::ilexicographical_compare(aaa->fileURI, bbb->fileURI);
+		default:
+			return boost::ilexicographical_compare(a->name, b->name);
+		}
+	}
+	else //if we are sorting campaigns
+	{
+		switch(sortBy)
+		{
+		case _numOfMaps: //by number of maps in campaign
+			return CGI->generaltexth->campaignRegionNames[aaa->campaignHeader->mapVersion].size() <
+			       CGI->generaltexth->campaignRegionNames[bbb->campaignHeader->mapVersion].size();
+			break;
+		case _name: //by name
+			return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
+		default:
+			return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
+		}
+	}
+}
+
+SelectionTab::SelectionTab(ESelectionScreen Type)
+	: CIntObject(LCLICK | WHEEL | KEYBOARD | DOUBLECLICK), callOnSelect(nullptr), tabType(Type), selectionPos(0), sortModeAscending(true)
+{
+	OBJ_CONSTRUCTION;
+	if(tabType != ESelectionScreen::campaignList)
+	{
+		sortingBy = _format;
+		background = std::make_shared<CPicture>("SCSELBCK.bmp", 0, 6);
+		pos = background->pos;
+		inputName = std::make_shared<CTextInput>(Rect(32, 539, 350, 20), Point(-32, -25), "GSSTRIP.bmp", 0);
+		inputName->filters += CTextInput::filenameFilter;
+		labelMapSizes = std::make_shared<CLabel>(87, 62, FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]);
+
+		int sizes[] = {36, 72, 108, 144, 0};
+		const char * filterIconNmes[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
+		for(int i = 0; i < 5; i++)
+			buttonsSortBy.push_back(std::make_shared<CButton>(Point(158 + 47 * i, 46), filterIconNmes[i], CGI->generaltexth->zelp[54 + i], std::bind(&SelectionTab::filter, this, sizes[i], true)));
+
+		int xpos[] = {23, 55, 88, 121, 306, 339};
+		const char * sortIconNames[] = {"SCBUTT1.DEF", "SCBUTT2.DEF", "SCBUTCP.DEF", "SCBUTT3.DEF", "SCBUTT4.DEF", "SCBUTT5.DEF"};
+		for(int i = 0; i < 6; i++)
+		{
+			ESortBy criteria = (ESortBy)i;
+			if(criteria == _name)
+				criteria = generalSortingBy;
+
+			buttonsSortBy.push_back(std::make_shared<CButton>(Point(xpos[i], 86), sortIconNames[i], CGI->generaltexth->zelp[107 + i], std::bind(&SelectionTab::sortBy, this, criteria)));
+		}
+	}
+
+	int positionsToShow = 18;
+	std::string tabTitle;
+	switch(tabType)
+	{
+	case ESelectionScreen::newGame:
+		generalSortingBy = ESortBy::_name;
+		tabTitle = CGI->generaltexth->arraytxt[229];
+		break;
+	case ESelectionScreen::loadGame:
+		generalSortingBy = ESortBy::_fileName;
+		tabTitle = CGI->generaltexth->arraytxt[230];
+		break;
+	case ESelectionScreen::saveGame:
+		positionsToShow = 16;
+		generalSortingBy = ESortBy::_fileName;
+		tabTitle = CGI->generaltexth->arraytxt[231];
+		break;
+	case ESelectionScreen::campaignList:
+		generalSortingBy = ESortBy::_name;
+		tabTitle = CGI->generaltexth->allTexts[726];
+		type |= REDRAW_PARENT; // we use parent background so we need to make sure it's will be redrawn too
+		pos.w = parent->pos.w;
+		pos.h = parent->pos.h;
+		pos.x += 3;
+		pos.y += 6;
+
+		buttonsSortBy.push_back(std::make_shared<CButton>(Point(23, 86), "CamCusM.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)));
+		buttonsSortBy.push_back(std::make_shared<CButton>(Point(55, 86), "CamCusL.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)));
+		break;
+	default:
+		assert(0);
+		break;
+	}
+
+	iconsMapFormats = std::make_shared<CAnimation>("SCSELC.DEF");
+	iconsMapFormats->preload();
+	iconsVictoryCondition = std::make_shared<CAnimation>("SCNRVICT.DEF");
+	iconsVictoryCondition->preload();
+	iconsLossCondition = std::make_shared<CAnimation>("SCNRLOSS.DEF");
+	iconsLossCondition->preload();
+	for(int i = 0; i < positionsToShow; i++)
+		listItems.push_back(std::make_shared<ListItem>(Point(30, 129 + i * 25), iconsMapFormats, iconsVictoryCondition, iconsLossCondition));
+
+	labelTabTitle = std::make_shared<CLabel>(205, 28, FONT_MEDIUM, EAlignment::CENTER, Colors::YELLOW, tabTitle);
+	slider = std::make_shared<CSlider>(Point(372, 86), tabType != ESelectionScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positionsToShow, curItems.size(), 0, false, CSlider::BLUE);
+	filter(0);
+}
+
+void SelectionTab::toggleMode()
+{
+	if(CSH->isGuest())
+	{
+		allItems.clear();
+		curItems.clear();
+		if(slider)
+			slider->block(true);
+	}
+	else
+	{
+		switch(tabType)
+		{
+		case ESelectionScreen::newGame:
+			inputName->disable();
+			parseMaps(getFiles("Maps/", EResType::MAP));
+			break;
+
+		case ESelectionScreen::loadGame:
+			inputName->disable();
+			parseSaves(getFiles("Saves/", EResType::CLIENT_SAVEGAME));
+			break;
+
+		case ESelectionScreen::saveGame:
+			parseSaves(getFiles("Saves/", EResType::CLIENT_SAVEGAME));
+			inputName->enable();
+			restoreLastSelection();
+			break;
+
+		case ESelectionScreen::campaignList:
+			parseCampaigns(getFiles("Maps/", EResType::CAMPAIGN));
+			break;
+
+		default:
+			assert(0);
+			break;
+		}
+		if(slider)
+		{
+			slider->block(false);
+			filter(0);
+		}
+
+		if(CSH->campaignStateToSend)
+		{
+			CSH->setCampaignState(CSH->campaignStateToSend);
+			CSH->campaignStateToSend.reset();
+		}
+		else
+		{
+			restoreLastSelection();
+		}
+	}
+	slider->setAmount(curItems.size());
+	updateListItems();
+	redraw();
+}
+
+void SelectionTab::clickLeft(tribool down, bool previousState)
+{
+	if(down)
+	{
+		int line = getLine();
+		if(line != -1)
+			select(line);
+	}
+}
+void SelectionTab::keyPressed(const SDL_KeyboardEvent & key)
+{
+	if(key.state != SDL_PRESSED)
+		return;
+
+	int moveBy = 0;
+	switch(key.keysym.sym)
+	{
+	case SDLK_UP:
+		moveBy = -1;
+		break;
+	case SDLK_DOWN:
+		moveBy = +1;
+		break;
+	case SDLK_PAGEUP:
+		moveBy = -listItems.size() + 1;
+		break;
+	case SDLK_PAGEDOWN:
+		moveBy = +listItems.size() - 1;
+		break;
+	case SDLK_HOME:
+		select(-slider->getValue());
+		return;
+	case SDLK_END:
+		select(curItems.size() - slider->getValue());
+		return;
+	default:
+		return;
+	}
+	select(selectionPos - slider->getValue() + moveBy);
+}
+
+void SelectionTab::onDoubleClick()
+{
+	if(getLine() != -1) //double clicked scenarios list
+	{
+		(static_cast<CLobbyScreen *>(parent))->buttonStart->clickLeft(false, true);
+	}
+}
+
+// A new size filter (Small, Medium, ...) has been selected. Populate
+// selMaps with the relevant data.
+void SelectionTab::filter(int size, bool selectFirst)
+{
+	curItems.clear();
+
+	if(tabType == ESelectionScreen::campaignList)
+	{
+		for(auto elem : allItems)
+			curItems.push_back(elem);
+	}
+	else
+	{
+		for(auto elem : allItems)
+		{
+			if(elem->mapHeader && elem->mapHeader->version && (!size || elem->mapHeader->width == size))
+				curItems.push_back(elem);
+		}
+	}
+
+	if(curItems.size())
+	{
+		slider->block(false);
+		slider->setAmount(curItems.size());
+		sort();
+		if(selectFirst)
+		{
+			slider->moveTo(0);
+			callOnSelect(curItems[0]);
+			selectAbs(0);
+		}
+	}
+	else
+	{
+		slider->block(true);
+		if(callOnSelect)
+			callOnSelect(nullptr);
+	}
+}
+
+void SelectionTab::sortBy(int criteria)
+{
+	if(criteria == sortingBy)
+	{
+		sortModeAscending = !sortModeAscending;
+	}
+	else
+	{
+		sortingBy = (ESortBy)criteria;
+		sortModeAscending = true;
+	}
+	sort();
+
+	selectAbs(0);
+}
+
+void SelectionTab::sort()
+{
+	if(sortingBy != generalSortingBy)
+		std::stable_sort(curItems.begin(), curItems.end(), mapSorter(generalSortingBy));
+	std::stable_sort(curItems.begin(), curItems.end(), mapSorter(sortingBy));
+
+	if(!sortModeAscending)
+		std::reverse(curItems.begin(), curItems.end());
+
+	updateListItems();
+	redraw();
+}
+
+void SelectionTab::select(int position)
+{
+	if(!curItems.size())
+		return;
+
+	// New selection. py is the index in curItems.
+	int py = position + slider->getValue();
+	vstd::amax(py, 0);
+	vstd::amin(py, curItems.size() - 1);
+
+	selectionPos = py;
+
+	if(position < 0)
+		slider->moveBy(position);
+	else if(position >= listItems.size())
+		slider->moveBy(position - listItems.size() + 1);
+
+	rememberCurrentSelection();
+
+	if(inputName && inputName->active)
+	{
+		auto filename = *CResourceHandler::get("local")->getResourceName(ResourceID(curItems[py]->fileURI, EResType::CLIENT_SAVEGAME));
+		inputName->setText(filename.stem().string());
+	}
+	updateListItems();
+	if(callOnSelect)
+		callOnSelect(curItems[py]);
+}
+
+void SelectionTab::selectAbs(int position)
+{
+	select(position - slider->getValue());
+}
+
+void SelectionTab::sliderMove(int slidPos)
+{
+	if(!slider)
+		return; // ignore spurious call when slider is being created
+	updateListItems();
+	redraw();
+}
+
+void SelectionTab::updateListItems()
+{
+	// elemIdx is the index of the maps or saved game to display on line 0
+	// slider->capacity contains the number of available screen lines
+	// slider->positionsAmnt is the number of elements after filtering
+	int elemIdx = slider->getValue();
+	for(auto item : listItems)
+	{
+		if(elemIdx < curItems.size())
+		{
+			item->updateItem(curItems[elemIdx], elemIdx == selectionPos);
+			elemIdx++;
+		}
+		else
+		{
+			item->updateItem();
+		}
+	}
+}
+
+int SelectionTab::getLine()
+{
+	int line = -1;
+	Point clickPos(GH.current->button.x, GH.current->button.y);
+	clickPos = clickPos - pos.topLeft();
+
+	// Ignore clicks on save name area
+	int maxPosY;
+	if(tabType == ESelectionScreen::saveGame)
+		maxPosY = 516;
+	else
+		maxPosY = 564;
+
+	if(clickPos.y > 115 && clickPos.y < maxPosY && clickPos.x > 22 && clickPos.x < 371)
+	{
+		line = (clickPos.y - 115) / 25; //which line
+	}
+
+	return line;
+}
+
+void SelectionTab::selectFileName(std::string fname)
+{
+	boost::to_upper(fname);
+	for(int i = curItems.size() - 1; i >= 0; i--)
+	{
+		if(curItems[i]->fileURI == fname)
+		{
+			slider->moveTo(i);
+			selectAbs(i);
+			return;
+		}
+	}
+
+	selectAbs(0);
+}
+
+std::shared_ptr<CMapInfo> SelectionTab::getSelectedMapInfo() const
+{
+	return curItems.empty() ? nullptr : curItems[selectionPos];
+}
+
+void SelectionTab::rememberCurrentSelection()
+{
+	// TODO: this can be more elegant
+	if(tabType == ESelectionScreen::newGame)
+	{
+		Settings lastMap = settings.write["general"]["lastMap"];
+		lastMap->String() = getSelectedMapInfo()->fileURI;
+	}
+	else if(tabType == ESelectionScreen::loadGame)
+	{
+		Settings lastSave = settings.write["general"]["lastSave"];
+		lastSave->String() = getSelectedMapInfo()->fileURI;
+	}
+	else if(tabType == ESelectionScreen::campaignList)
+	{
+		Settings lastCampaign = settings.write["general"]["lastCampaign"];
+		lastCampaign->String() = getSelectedMapInfo()->fileURI;
+	}
+}
+
+void SelectionTab::restoreLastSelection()
+{
+	switch(tabType)
+	{
+	case ESelectionScreen::newGame:
+		selectFileName(settings["general"]["lastMap"].String());
+		break;
+	case ESelectionScreen::campaignList:
+		selectFileName(settings["general"]["lastCampaign"].String());
+		break;
+	case ESelectionScreen::loadGame:
+	case ESelectionScreen::saveGame:
+		selectFileName(settings["general"]["lastSave"].String());
+	}
+}
+
+void SelectionTab::parseMaps(const std::unordered_set<ResourceID> & files)
+{
+	logGlobal->debug("Parsing %d maps", files.size());
+	allItems.clear();
+	for(auto & file : files)
+	{
+		try
+		{
+			auto mapInfo = std::make_shared<CMapInfo>();
+			mapInfo->mapInit(file.getName());
+
+			// ignore unsupported map versions (e.g. WoG maps without WoG)
+			// but accept VCMI maps
+			if((mapInfo->mapHeader->version >= EMapFormat::VCMI) || (mapInfo->mapHeader->version <= CGI->modh->settings.data["textData"]["mapVersion"].Float()))
+				allItems.push_back(mapInfo);
+		}
+		catch(std::exception & e)
+		{
+			logGlobal->error("Map %s is invalid. Message: %s", file.getName(), e.what());
+		}
+	}
+}
+
+void SelectionTab::parseSaves(const std::unordered_set<ResourceID> & files)
+{
+	for(auto & file : files)
+	{
+		try
+		{
+			auto mapInfo = std::make_shared<CMapInfo>();
+			mapInfo->saveInit(file);
+
+			// Filter out other game modes
+			bool isCampaign = mapInfo->scenarioOptionsOfSave->mode == StartInfo::CAMPAIGN;
+			bool isMultiplayer = mapInfo->amountOfHumanPlayersInSave > 1;
+			switch(CSH->getLoadMode())
+			{
+			case ELoadMode::SINGLE:
+				if(isMultiplayer || isCampaign)
+					mapInfo->mapHeader.reset();
+				break;
+			case ELoadMode::CAMPAIGN:
+				if(!isCampaign)
+					mapInfo->mapHeader.reset();
+				break;
+			default:
+				if(!isMultiplayer)
+					mapInfo->mapHeader.reset();
+				break;
+			}
+
+			allItems.push_back(mapInfo);
+		}
+		catch(const std::exception & e)
+		{
+			logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what());
+		}
+	}
+}
+
+void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> & files)
+{
+	allItems.reserve(files.size());
+	for(auto & file : files)
+	{
+		auto info = std::make_shared<CMapInfo>();
+		//allItems[i].date = std::asctime(std::localtime(&files[i].date));
+		info->fileURI = file.getName();
+		info->campaignInit();
+		allItems.push_back(info);
+	}
+}
+
+std::unordered_set<ResourceID> SelectionTab::getFiles(std::string dirURI, int resType)
+{
+	boost::to_upper(dirURI);
+	CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount)
+	{
+		return boost::algorithm::starts_with(mount, dirURI);
+	});
+
+	std::unordered_set<ResourceID> ret = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
+	{
+		return ident.getType() == resType && boost::algorithm::starts_with(ident.getName(), dirURI);
+	});
+
+	return ret;
+}
+
+SelectionTab::ListItem::ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss)
+	: CIntObject(LCLICK, position)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	labelName = std::make_shared<CLabel>(184, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
+	labelName->setAutoRedraw(false);
+	labelAmountOfPlayers = std::make_shared<CLabel>(8, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
+	labelAmountOfPlayers->setAutoRedraw(false);
+	labelNumberOfCampaignMaps = std::make_shared<CLabel>(8, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
+	labelNumberOfCampaignMaps->setAutoRedraw(false);
+	labelMapSizeLetter = std::make_shared<CLabel>(41, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
+	labelMapSizeLetter->setAutoRedraw(false);
+	// FIXME: This -12 should not be needed, but for some reason CAnimImage displaced otherwise
+	iconFormat = std::make_shared<CAnimImage>(iconsFormats, 0, 0, 59, -12);
+	iconVictoryCondition = std::make_shared<CAnimImage>(iconsVictory, 0, 0, 277, -12);
+	iconLossCondition = std::make_shared<CAnimImage>(iconsLoss, 0, 0, 310, -12);
+}
+
+void SelectionTab::ListItem::updateItem(std::shared_ptr<CMapInfo> info, bool selected)
+{
+	if(!info)
+	{
+		labelAmountOfPlayers->disable();
+		labelMapSizeLetter->disable();
+		iconFormat->disable();
+		iconVictoryCondition->disable();
+		iconLossCondition->disable();
+		labelNumberOfCampaignMaps->disable();
+		labelName->disable();
+		return;
+	}
+
+	auto color = selected ? Colors::YELLOW : Colors::WHITE;
+	if(info->campaignHeader)
+	{
+		labelAmountOfPlayers->disable();
+		labelMapSizeLetter->disable();
+		iconFormat->disable();
+		iconVictoryCondition->disable();
+		iconLossCondition->disable();
+		labelNumberOfCampaignMaps->enable();
+		std::ostringstream ostr(std::ostringstream::out);
+		ostr << CGI->generaltexth->campaignRegionNames[info->campaignHeader->mapVersion].size();
+		labelNumberOfCampaignMaps->setText(ostr.str());
+		labelNumberOfCampaignMaps->setColor(color);
+	}
+	else
+	{
+		labelNumberOfCampaignMaps->disable();
+		std::ostringstream ostr(std::ostringstream::out);
+		ostr << info->amountOfPlayersOnMap << "/" << info->amountOfHumanControllablePlayers;
+		labelAmountOfPlayers->enable();
+		labelAmountOfPlayers->setText(ostr.str());
+		labelAmountOfPlayers->setColor(color);
+		labelMapSizeLetter->enable();
+		labelMapSizeLetter->setText(info->getMapSizeName());
+		labelMapSizeLetter->setColor(color);
+		iconFormat->enable();
+		iconFormat->setFrame(info->getMapSizeFormatIconId().first, info->getMapSizeFormatIconId().second);
+		iconVictoryCondition->enable();
+		iconVictoryCondition->setFrame(info->mapHeader->victoryIconIndex, 0);
+		iconLossCondition->enable();
+		iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0);
+	}
+	labelName->enable();
+	labelName->setText(info->getNameForList());
+	labelName->setColor(color);
+}

+ 98 - 0
client/lobby/SelectionTab.h

@@ -0,0 +1,98 @@
+/*
+ * SelectionTab.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 "CSelectionBase.h"
+
+class CSlider;
+class CLabel;
+
+enum ESortBy
+{
+	_playerAm, _size, _format, _name, _viccon, _loscon, _numOfMaps, _fileName
+}; //_numOfMaps is for campaigns
+
+/// Class which handles map sorting by different criteria
+class mapSorter
+{
+public:
+	ESortBy sortBy;
+	bool operator()(const std::shared_ptr<CMapInfo> aaa, const std::shared_ptr<CMapInfo> bbb);
+	mapSorter(ESortBy es) : sortBy(es){};
+};
+
+class SelectionTab : public CIntObject
+{
+	struct ListItem : public CIntObject
+	{
+		std::shared_ptr<CLabel> labelAmountOfPlayers;
+		std::shared_ptr<CLabel> labelNumberOfCampaignMaps;
+		std::shared_ptr<CLabel> labelMapSizeLetter;
+		std::shared_ptr<CAnimImage> iconFormat;
+		std::shared_ptr<CAnimImage> iconVictoryCondition;
+		std::shared_ptr<CAnimImage> iconLossCondition;
+		std::shared_ptr<CLabel> labelName;
+
+		ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss);
+		void updateItem(std::shared_ptr<CMapInfo> info = {}, bool selected = false);
+	};
+	std::vector<std::shared_ptr<ListItem>> listItems;
+
+	std::shared_ptr<CAnimation> iconsMapFormats;
+	// FIXME: CSelectionBase use them too!
+	std::shared_ptr<CAnimation> iconsVictoryCondition;
+	std::shared_ptr<CAnimation> iconsLossCondition;
+
+public:
+	std::vector<std::shared_ptr<CMapInfo>> allItems;
+	std::vector<std::shared_ptr<CMapInfo>> curItems;
+	size_t selectionPos;
+	std::function<void(std::shared_ptr<CMapInfo>)> callOnSelect;
+
+	ESortBy sortingBy;
+	ESortBy generalSortingBy;
+	bool sortModeAscending;
+
+	std::shared_ptr<CTextInput> inputName;
+
+	SelectionTab(ESelectionScreen Type);
+	void toggleMode();
+
+	void clickLeft(tribool down, bool previousState) override;
+	void keyPressed(const SDL_KeyboardEvent & key) override;
+	void onDoubleClick() override;
+
+	void filter(int size, bool selectFirst = false); //0 - all
+	void sortBy(int criteria);
+	void sort();
+	void select(int position); //position: <0 - positions>  position on the screen
+	void selectAbs(int position); //position: absolute position in curItems vector
+	void sliderMove(int slidPos);
+	void updateListItems();
+	int getLine();
+	void selectFileName(std::string fname);
+	std::shared_ptr<CMapInfo> getSelectedMapInfo() const;
+	void rememberCurrentSelection();
+	void restoreLastSelection();
+
+private:
+
+	std::shared_ptr<CPicture> background;
+	std::shared_ptr<CSlider> slider;
+	std::vector<std::shared_ptr<CButton>> buttonsSortBy;
+	std::shared_ptr<CLabel> labelTabTitle;
+	std::shared_ptr<CLabel> labelMapSizes;
+	ESelectionScreen tabType;
+
+	void parseMaps(const std::unordered_set<ResourceID> & files);
+	void parseSaves(const std::unordered_set<ResourceID> & files);
+	void parseCampaigns(const std::unordered_set<ResourceID> & files);
+	std::unordered_set<ResourceID> getFiles(std::string dirURI, int resType);
+};

+ 156 - 0
client/mainmenu/CCampaignScreen.cpp

@@ -0,0 +1,156 @@
+/*
+ * CCampaignScreen.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 "../mainmenu/CMainMenu.h"
+#include "CCampaignScreen.h"
+
+#include "../CGameInfo.h"
+#include "../CMessage.h"
+#include "../CBitmapHandler.h"
+#include "../CMusicHandler.h"
+#include "../CVideoHandler.h"
+#include "../CPlayerInterface.h"
+#include "../CServerHandler.h"
+#include "../gui/CAnimation.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+#include "../windows/GUIClasses.h"
+#include "../windows/InfoWindows.h"
+#include "../windows/CWindowObject.h"
+
+#include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/CGeneralTextHandler.h"
+
+#include "../../lib/CArtHandler.h"
+#include "../../lib/CBuildingHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
+
+#include "../../lib/CSkillHandler.h"
+#include "../../lib/CTownHandler.h"
+#include "../../lib/CHeroHandler.h"
+#include "../../lib/CCreatureHandler.h"
+
+#include "../../lib/mapping/CCampaignHandler.h"
+#include "../../lib/mapping/CMapService.h"
+
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+CCampaignScreen::CCampaignScreen(const JsonNode & config)
+	: CWindowObject(BORDERED)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	for(const JsonNode & node : config["images"].Vector())
+		images.push_back(CMainMenu::createPicture(node));
+
+	if(!images.empty())
+	{
+		images[0]->center(); // move background to center
+		moveTo(images[0]->pos.topLeft()); // move everything else to center
+		images[0]->moveTo(pos.topLeft()); // restore moved twice background
+		pos = images[0]->pos; // fix height\width of this window
+	}
+
+	if(!config["exitbutton"].isNull())
+	{
+		buttonBack = createExitButton(config["exitbutton"]);
+		buttonBack->hoverable = true;
+	}
+
+	for(const JsonNode & node : config["items"].Vector())
+		campButtons.push_back(std::make_shared<CCampaignButton>(node));
+}
+
+std::shared_ptr<CButton> CCampaignScreen::createExitButton(const JsonNode & button)
+{
+	std::pair<std::string, std::string> help;
+	if(!button["help"].isNull() && button["help"].Float() > 0)
+		help = CGI->generaltexth->zelp[button["help"].Float()];
+
+	std::function<void()> close = std::bind(&CGuiHandler::popIntTotally, &GH, this);
+	return std::make_shared<CButton>(Point(button["x"].Float(), button["y"].Float()), button["name"].String(), help, close, button["hotkey"].Float());
+}
+
+CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	pos.x += config["x"].Float();
+	pos.y += config["y"].Float();
+	pos.w = 200;
+	pos.h = 116;
+
+	campFile = config["file"].String();
+	video = config["video"].String();
+
+	status = config["open"].Bool() ? CCampaignScreen::ENABLED : CCampaignScreen::DISABLED;
+
+	CCampaignHeader header = CCampaignHandler::getHeader(campFile);
+	hoverText = header.name;
+
+	if(status != CCampaignScreen::DISABLED)
+	{
+		addUsedEvents(LCLICK | HOVER);
+		graphicsImage = std::make_shared<CPicture>(config["image"].String());
+
+		hoverLabel = std::make_shared<CLabel>(pos.w / 2, pos.h + 20, FONT_MEDIUM, CENTER, Colors::YELLOW, "");
+		parent->addChild(hoverLabel.get());
+	}
+
+	if(status == CCampaignScreen::COMPLETED)
+		graphicsCompleted = std::make_shared<CPicture>("CAMPCHK");
+}
+
+void CCampaignScreen::CCampaignButton::show(SDL_Surface * to)
+{
+	if(status == CCampaignScreen::DISABLED)
+		return;
+
+	CIntObject::show(to);
+
+	// Play the campaign button video when the mouse cursor is placed over the button
+	if(hovered)
+	{
+		if(CCS->videoh->fname != video)
+			CCS->videoh->open(video);
+
+		CCS->videoh->update(pos.x, pos.y, to, true, false); // plays sequentially frame by frame, starts at the beginning when the video is over
+	}
+	else if(CCS->videoh->fname == video) // When you got out of the bounds of the button then close the video
+	{
+		CCS->videoh->close();
+		redraw();
+	}
+}
+
+void CCampaignScreen::CCampaignButton::clickLeft(tribool down, bool previousState)
+{
+	if(down)
+	{
+		CCS->videoh->close();
+		CMainMenu::openCampaignLobby(campFile);
+	}
+}
+
+void CCampaignScreen::CCampaignButton::hover(bool on)
+{
+	if(hoverLabel)
+	{
+		if(on)
+			hoverLabel->setText(hoverText); // Shows the name of the campaign when you get into the bounds of the button
+		else
+			hoverLabel->setText(" ");
+	}
+}

+ 55 - 0
client/mainmenu/CCampaignScreen.h

@@ -0,0 +1,55 @@
+/*
+ * CCampaignScreen.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
+
+class CLabel;
+class CPicture;
+class CButton;
+class SDL_Surface;
+class JsonNode;
+
+class CCampaignScreen : public CWindowObject
+{
+public:
+	enum CampaignStatus {DEFAULT = 0, ENABLED, DISABLED, COMPLETED}; // the status of the campaign
+
+private:
+	/// A button which plays a video when you move the mouse cursor over it
+	class CCampaignButton : public CIntObject
+	{
+	private:
+		std::shared_ptr<CLabel> hoverLabel;
+		std::shared_ptr<CPicture> graphicsImage;
+		std::shared_ptr<CPicture> graphicsCompleted;
+		CampaignStatus status;
+
+		std::string campFile; // the filename/resourcename of the campaign
+		std::string video; // the resource name of the video
+		std::string hoverText;
+
+		void clickLeft(tribool down, bool previousState) override;
+		void hover(bool on) override;
+
+	public:
+		CCampaignButton(const JsonNode & config);
+		void show(SDL_Surface * to) override;
+	};
+
+	std::vector<std::shared_ptr<CCampaignButton>> campButtons;
+	std::vector<std::shared_ptr<CPicture>> images;
+	std::shared_ptr<CButton> buttonBack;
+
+	std::shared_ptr<CButton> createExitButton(const JsonNode & button);
+
+public:
+	enum CampaignSet {ROE, AB, SOD, WOG};
+
+	CCampaignScreen(const JsonNode & config);
+};

+ 566 - 0
client/mainmenu/CMainMenu.cpp

@@ -0,0 +1,566 @@
+/*
+ * CMainMenu.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 "CMainMenu.h"
+
+#include "CCampaignScreen.h"
+#include "CreditsScreen.h"
+
+#include "../lobby/CBonusSelection.h"
+#include "../lobby/CSelectionBase.h"
+#include "../lobby/CLobbyScreen.h"
+
+#include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/filesystem/CCompressedStream.h"
+
+#include "../gui/SDL_Extensions.h"
+#include "../gui/CCursorHandler.h"
+
+#include "../CGameInfo.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/JsonNode.h"
+#include "../CMusicHandler.h"
+#include "../CVideoHandler.h"
+#include "../Graphics.h"
+#include "../../lib/serializer/Connection.h"
+#include "../../lib/serializer/CTypeList.h"
+#include "../../lib/VCMIDirs.h"
+#include "../../lib/mapping/CMap.h"
+#include "../windows/GUIClasses.h"
+#include "../CPlayerInterface.h"
+#include "../../CCallback.h"
+#include "../CMessage.h"
+#include "../CBitmapHandler.h"
+#include "../Client.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/CAnimation.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+#include "../windows/InfoWindows.h"
+#include "../CServerHandler.h"
+#include "../../lib/CStopWatch.h"
+#include "../../lib/NetPacksLobby.h"
+#include "../../lib/CThreadHelper.h"
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/GameConstants.h"
+#include "../../lib/CRandomGenerator.h"
+#include "../../lib/CondSh.h"
+#include "../../lib/mapping/CCampaignHandler.h"
+
+
+namespace fs = boost::filesystem;
+
+CMainMenu * CMM = nullptr;
+ISelectionScreenInfo * SEL;
+
+static void do_quit()
+{
+	SDL_Event event;
+	event.quit.type = SDL_QUIT;
+	SDL_PushEvent(&event);
+}
+
+CMenuScreen::CMenuScreen(const JsonNode & configNode)
+	: CWindowObject(BORDERED), config(configNode)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	background = std::make_shared<CPicture>(config["background"].String());
+	if(config["scalable"].Bool())
+	{
+		if(background->bg->format->palette)
+			background->convertToScreenBPP();
+		background->scaleTo(Point(screen->w, screen->h));
+	}
+
+	pos = background->center();
+
+	for(const JsonNode & node : config["items"].Vector())
+		menuNameToEntry.push_back(node["name"].String());
+
+	for(const JsonNode & node : config["images"].Vector())
+		images.push_back(CMainMenu::createPicture(node));
+
+	//Hardcoded entry
+	menuNameToEntry.push_back("credits");
+
+	tabs = std::make_shared<CTabbedInt>(std::bind(&CMenuScreen::createTab, this, _1), CTabbedInt::DestroyFunc());
+	tabs->type |= REDRAW_PARENT;
+}
+
+CIntObject * CMenuScreen::createTab(size_t index)
+{
+	if(config["items"].Vector().size() == index)
+		return new CreditsScreen();
+
+	return new CMenuEntry(this, config["items"].Vector()[index]);
+}
+
+void CMenuScreen::show(SDL_Surface * to)
+{
+	if(!config["video"].isNull())
+		CCS->videoh->update(config["video"]["x"].Float() + pos.x, config["video"]["y"].Float() + pos.y, to, true, false);
+	CIntObject::show(to);
+}
+
+void CMenuScreen::activate()
+{
+	CCS->musich->playMusic("Music/MainMenu", true);
+	if(!config["video"].isNull())
+		CCS->videoh->open(config["video"]["name"].String());
+	CIntObject::activate();
+}
+
+void CMenuScreen::deactivate()
+{
+	if(!config["video"].isNull())
+		CCS->videoh->close();
+
+	CIntObject::deactivate();
+}
+
+void CMenuScreen::switchToTab(size_t index)
+{
+	tabs->setActive(index);
+}
+
+//funciton for std::string -> std::function conversion for main menu
+static std::function<void()> genCommand(CMenuScreen * menu, std::vector<std::string> menuType, const std::string & string)
+{
+	static const std::vector<std::string> commandType = {"to", "campaigns", "start", "load", "exit", "highscores"};
+
+	static const std::vector<std::string> gameType = {"single", "multi", "campaign", "tutorial"};
+
+	std::list<std::string> commands;
+	boost::split(commands, string, boost::is_any_of("\t "));
+
+	if(!commands.empty())
+	{
+		size_t index = std::find(commandType.begin(), commandType.end(), commands.front()) - commandType.begin();
+		commands.pop_front();
+		if(index > 3 || !commands.empty())
+		{
+			switch(index)
+			{
+			case 0: //to - switch to another tab, if such tab exists
+			{
+				size_t index2 = std::find(menuType.begin(), menuType.end(), commands.front()) - menuType.begin();
+				if(index2 != menuType.size())
+					return std::bind(&CMenuScreen::switchToTab, menu, index2);
+				break;
+			}
+			case 1: //open campaign selection window
+			{
+				return std::bind(&CMainMenu::openCampaignScreen, CMM, commands.front());
+				break;
+			}
+			case 2: //start
+			{
+				switch(std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
+				{
+				case 0:
+					return std::bind(CMainMenu::openLobby, ESelectionScreen::newGame, true, nullptr, ELoadMode::NONE);
+				case 1:
+					return []() { GH.pushInt(new CMultiMode(ESelectionScreen::newGame)); };
+				case 2:
+					return std::bind(CMainMenu::openLobby, ESelectionScreen::campaignList, true, nullptr, ELoadMode::NONE);
+				case 3:
+					return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector<CComponent *> *)nullptr, false, PlayerColor(1));
+				}
+				break;
+			}
+			case 3: //load
+			{
+				switch(std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
+				{
+				case 0:
+					return std::bind(CMainMenu::openLobby, ESelectionScreen::loadGame, true, nullptr, ELoadMode::SINGLE);
+				case 1:
+					return []()     { GH.pushInt(new CMultiMode(ESelectionScreen::loadGame)); };
+				case 2:
+					return std::bind(CMainMenu::openLobby, ESelectionScreen::loadGame, true, nullptr, ELoadMode::CAMPAIGN);
+				case 3:
+					return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector<CComponent *> *)nullptr, false, PlayerColor(1));
+				}
+			}
+			break;
+			case 4: //exit
+			{
+				return std::bind(CInfoWindow::showYesNoDialog, std::ref(CGI->generaltexth->allTexts[69]), (const std::vector<CComponent *> *)nullptr, do_quit, 0, false, PlayerColor(1));
+			}
+			break;
+			case 5: //highscores
+			{
+				return std::bind(CInfoWindow::showInfoDialog, "Sorry, high scores menu is not implemented yet\n", (const std::vector<CComponent *> *)nullptr, false, PlayerColor(1));
+			}
+			}
+		}
+	}
+	logGlobal->error("Failed to parse command: %s", string);
+	return std::function<void()>();
+}
+
+std::shared_ptr<CButton> CMenuEntry::createButton(CMenuScreen * parent, const JsonNode & button)
+{
+	std::function<void()> command = genCommand(parent, parent->menuNameToEntry, button["command"].String());
+
+	std::pair<std::string, std::string> help;
+	if(!button["help"].isNull() && button["help"].Float() > 0)
+		help = CGI->generaltexth->zelp[button["help"].Float()];
+
+	int posx = button["x"].Float();
+	if(posx < 0)
+		posx = pos.w + posx;
+
+	int posy = button["y"].Float();
+	if(posy < 0)
+		posy = pos.h + posy;
+
+	return std::make_shared<CButton>(Point(posx, posy), button["name"].String(), help, command, button["hotkey"].Float());
+}
+
+CMenuEntry::CMenuEntry(CMenuScreen * parent, const JsonNode & config)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	type |= REDRAW_PARENT;
+	pos = parent->pos;
+
+	for(const JsonNode & node : config["images"].Vector())
+		images.push_back(CMainMenu::createPicture(node));
+
+	for(const JsonNode & node : config["buttons"].Vector())
+	{
+		buttons.push_back(createButton(parent, node));
+		buttons.back()->hoverable = true;
+		buttons.back()->type |= REDRAW_PARENT;
+	}
+}
+
+CMainMenuConfig::CMainMenuConfig()
+	: campaignSets(JsonNode(ResourceID("config/campaignSets.json"))), config(JsonNode(ResourceID("config/mainmenu.json")))
+{
+
+}
+
+CMainMenuConfig & CMainMenuConfig::get()
+{
+	static CMainMenuConfig config;
+	return config;
+}
+
+const JsonNode & CMainMenuConfig::getConfig() const
+{
+	return config;
+}
+
+const JsonNode & CMainMenuConfig::getCampaigns() const
+{
+	return campaignSets;
+}
+
+CMainMenu::CMainMenu()
+{
+	pos.w = screen->w;
+	pos.h = screen->h;
+
+	GH.defActionsDef = 63;
+	CMM = this;
+	menu = new CMenuScreen(CMainMenuConfig::get().getConfig()["window"]);
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	backgroundAroundMenu = std::make_shared<CFilledTexture>("DIBOXBCK", pos);
+}
+
+CMainMenu::~CMainMenu()
+{
+	boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+	if(CMM == this)
+		CMM = nullptr;
+
+	if(GH.curInt == this)
+		GH.curInt = nullptr;
+}
+
+void CMainMenu::update()
+{
+	if(CMM != this) //don't update if you are not a main interface
+		return;
+
+	if(GH.listInt.empty())
+	{
+		GH.pushInt(this);
+		GH.pushInt(menu);
+		menu->switchToTab(0);
+	}
+
+	// Handles mouse and key input
+	GH.updateTime();
+	GH.handleEvents();
+
+	// check for null othervice crash on finishing a campaign
+	// /FIXME: find out why GH.listInt is empty to begin with
+	if(GH.topInt() != nullptr)
+		GH.topInt()->show(screen);
+}
+
+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->loadMode = loadMode;
+
+	GH.pushInt(new CSimpleJoinScreen(host));
+}
+
+void CMainMenu::openCampaignLobby(const std::string & campaignFileName)
+{
+	auto ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(campaignFileName));
+	openCampaignLobby(ourCampaign);
+}
+
+void CMainMenu::openCampaignLobby(std::shared_ptr<CCampaignState> campaign)
+{
+	CSH->resetStateForLobby(StartInfo::CAMPAIGN);
+	CSH->screenType = ESelectionScreen::campaignList;
+	CSH->campaignStateToSend = campaign;
+	GH.pushInt(new CSimpleJoinScreen());
+}
+
+void CMainMenu::openCampaignScreen(std::string name)
+{
+	if(vstd::contains(CMainMenuConfig::get().getCampaigns().Struct(), name))
+	{
+		GH.pushInt(new CCampaignScreen(CMainMenuConfig::get().getCampaigns()[name]));
+		return;
+	}
+	logGlobal->error("Unknown campaign set: %s", name);
+}
+
+CMainMenu * CMainMenu::create()
+{
+	if(!CMM)
+		CMM = new CMainMenu();
+
+	GH.terminate_cond->set(false);
+	return CMM;
+}
+
+void CMainMenu::removeFromGui()
+{
+	//remove everything but main menu and background
+	GH.popInts(GH.listInt.size() - 2);
+	GH.popInt(GH.topInt()); //remove main menu
+	GH.popInt(GH.topInt()); //remove background
+}
+
+void CMainMenu::showLoadingScreen(std::function<void()> loader)
+{
+	if(GH.listInt.size() && GH.listInt.front() == CMM)
+		CMM->removeFromGui();
+	GH.pushInt(new CLoadingScreen(loader));
+}
+
+std::shared_ptr<CPicture> CMainMenu::createPicture(const JsonNode & config)
+{
+	return std::make_shared<CPicture>(config["name"].String(), config["x"].Float(), config["y"].Float());
+}
+
+CMultiMode::CMultiMode(ESelectionScreen ScreenType)
+	: screenType(ScreenType)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	background = std::make_shared<CPicture>("MUPOPUP.bmp");
+	background->convertToScreenBPP(); //so we could draw without problems
+	blitAt(CPicture("MUMAP.bmp"), 16, 77, *background);
+	pos = background->center(); //center, window has size of bg graphic
+
+	statusBar = std::make_shared<CGStatusBar>(new CPicture(Rect(7, 465, 440, 18), 0)); //226, 472
+	playerName = std::make_shared<CTextInput>(Rect(19, 436, 334, 16), *background);
+	playerName->setText(settings["general"]["playerName"].String());
+	playerName->cb += std::bind(&CMultiMode::onNameChange, this, _1);
+
+	buttonHotseat = std::make_shared<CButton>(Point(373, 78), "MUBHOT.DEF", CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this));
+	buttonHost = std::make_shared<CButton>(Point(373, 78 + 57 * 1), "MUBHOST.DEF", CButton::tooltip("Host TCP/IP game", ""), std::bind(&CMultiMode::hostTCP, this));
+	buttonJoin = std::make_shared<CButton>(Point(373, 78 + 57 * 2), "MUBJOIN.DEF", CButton::tooltip("Join TCP/IP game", ""), std::bind(&CMultiMode::joinTCP, this));
+	buttonCancel = std::make_shared<CButton>(Point(373, 424), "MUBCANC.DEF", CGI->generaltexth->zelp[288], [&]() { GH.popIntTotally(this);}, SDLK_ESCAPE);
+}
+
+void CMultiMode::hostTCP()
+{
+	GH.popIntTotally(this);
+	GH.pushInt(new CMultiPlayers(settings["general"]["playerName"].String(), screenType, true, ELoadMode::MULTI));
+}
+
+void CMultiMode::joinTCP()
+{
+	GH.popIntTotally(this);
+	GH.pushInt(new CMultiPlayers(settings["general"]["playerName"].String(), screenType, false, ELoadMode::MULTI));
+}
+
+void CMultiMode::onNameChange(std::string newText)
+{
+	Settings name = settings.write["general"]["playerName"];
+	name->String() = newText;
+}
+
+CMultiPlayers::CMultiPlayers(const std::string & firstPlayer, ESelectionScreen ScreenType, bool Host, ELoadMode LoadMode)
+	: loadMode(LoadMode), screenType(ScreenType), host(Host)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	background = std::make_shared<CPicture>("MUHOTSEA.bmp");
+	pos = background->center(); //center, window has size of bg graphic
+
+	std::string text = CGI->generaltexth->allTexts[446];
+	boost::replace_all(text, "\t", "\n");
+	textTitle = std::make_shared<CTextBox>(text, Rect(25, 20, 315, 50), 0, FONT_BIG, CENTER, Colors::WHITE); //HOTSEAT	Please enter names
+
+	for(int i = 0; i < inputNames.size(); i++)
+	{
+		inputNames[i] = std::make_shared<CTextInput>(Rect(60, 85 + i * 30, 280, 16), *background);
+		inputNames[i]->cb += std::bind(&CMultiPlayers::onChange, this, _1);
+	}
+
+	buttonOk = std::make_shared<CButton>(Point(95, 338), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CMultiPlayers::enterSelectionScreen, this), SDLK_RETURN);
+	buttonCancel = std::make_shared<CButton>(Point(205, 338), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE);
+	statusBar = std::make_shared<CGStatusBar>(new CPicture(Rect(7, 381, 348, 18), 0)); //226, 472
+
+	inputNames[0]->setText(firstPlayer, true);
+	inputNames[0]->giveFocus();
+}
+
+void CMultiPlayers::onChange(std::string newText)
+{
+	size_t namesCount = 0;
+
+	for(auto & elem : inputNames)
+		if(!elem->text.empty())
+			namesCount++;
+}
+
+void CMultiPlayers::enterSelectionScreen()
+{
+	std::vector<std::string> names;
+	for(auto name : inputNames)
+	{
+		if(name->text.length())
+			names.push_back(name->text);
+	}
+
+	Settings name = settings.write["general"]["playerName"];
+	name->String() = names[0];
+
+	CMainMenu::openLobby(screenType, host, &names, loadMode);
+}
+
+CSimpleJoinScreen::CSimpleJoinScreen(bool host)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	background = std::make_shared<CPicture>("MUDIALOG.bmp"); // address background
+	pos = background->center(); //center, window has size of bg graphic (x,y = 396,278 w=232 h=212)
+
+	textTitle = std::make_shared<CTextBox>("", Rect(20, 20, 205, 50), 0, FONT_BIG, CENTER, Colors::WHITE);
+	inputAddress = std::make_shared<CTextInput>(Rect(25, 68, 175, 16), *background.get());
+	inputPort = std::make_shared<CTextInput>(Rect(25, 115, 175, 16), *background.get());
+	if(host && !settings["session"]["donotstartserver"].Bool())
+	{
+		textTitle->setText("Connecting...");
+		boost::thread(&CSimpleJoinScreen::connectThread, this, "", 0);
+	}
+	else
+	{
+		textTitle->setText("Enter address:");
+		inputAddress->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
+		inputPort->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
+		inputPort->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535);
+		buttonOk = std::make_shared<CButton>(Point(26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::connectToServer, this), SDLK_RETURN);
+
+		inputAddress->giveFocus();
+	}
+	inputAddress->setText(settings["server"]["server"].String(), true);
+	inputPort->setText(CServerHandler::getDefaultPortStr(), true);
+
+	buttonCancel = std::make_shared<CButton>(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CSimpleJoinScreen::leaveScreen, this), SDLK_ESCAPE);
+	statusBar = std::make_shared<CGStatusBar>(new CPicture(Rect(7, 186, 218, 18), 0));
+}
+
+void CSimpleJoinScreen::connectToServer()
+{
+	textTitle->setText("Connecting...");
+	buttonOk->block(true);
+
+	boost::thread(&CSimpleJoinScreen::connectThread, this, inputAddress->text, boost::lexical_cast<ui16>(inputPort->text));
+}
+
+void CSimpleJoinScreen::leaveScreen()
+{
+	if(CSH->state == EClientState::CONNECTING)
+	{
+		textTitle->setText("Closing...");
+		CSH->state = EClientState::CONNECTION_CANCELLED;
+	}
+	else if(GH.listInt.size() && GH.listInt.front() == this)
+	{
+		GH.popIntTotally(this);
+	}
+}
+
+void CSimpleJoinScreen::onChange(const std::string & newText)
+{
+	buttonOk->block(inputAddress->text.empty() || inputPort->text.empty());
+}
+
+void CSimpleJoinScreen::connectThread(const std::string addr, const ui16 port)
+{
+	setThreadName("CSimpleJoinScreen::connectThread");
+	if(!addr.length())
+		CSH->startLocalServerAndConnect();
+	else
+		CSH->justConnectToServer(addr, port);
+
+	if(GH.listInt.size() && GH.listInt.front() == this)
+	{
+		GH.popIntTotally(this);
+	}
+}
+
+CLoadingScreen::CLoadingScreen(std::function<void()> loader)
+	: CWindowObject(BORDERED, getBackground()), loadingThread(loader)
+{
+	CCS->musich->stopMusic(5000);
+}
+
+CLoadingScreen::~CLoadingScreen()
+{
+	loadingThread.join();
+}
+
+void CLoadingScreen::showAll(SDL_Surface * to)
+{
+	Rect rect(0, 0, to->w, to->h);
+	SDL_FillRect(to, &rect, 0);
+
+	CWindowObject::showAll(to);
+}
+
+std::string CLoadingScreen::getBackground()
+{
+	const auto & conf = CMainMenuConfig::get().getConfig()["loading"].Vector();
+
+	if(conf.empty())
+	{
+		return "loadbar";
+	}
+	else
+	{
+		return RandomGeneratorUtil::nextItem(conf, CRandomGenerator::getDefault())->String();
+	}
+}

+ 183 - 0
client/mainmenu/CMainMenu.h

@@ -0,0 +1,183 @@
+/*
+ * CMainMenu.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 "../windows/CWindowObject.h"
+#include "../../lib/JsonNode.h"
+
+class CCampaignState;
+class CTextInput;
+class CGStatusBar;
+class CTextBox;
+class CTabbedInt;
+class CAnimation;
+class CButton;
+class CFilledTexture;
+
+
+// TODO: Find new location for these enums
+enum ESelectionScreen : ui8 {
+	unknown = 0, newGame, loadGame, saveGame, scenarioInfo, campaignList
+};
+
+enum ELoadMode : ui8
+{
+	NONE = 0, SINGLE, MULTI, CAMPAIGN
+};
+
+/// The main menu screens listed in the EState enum
+class CMenuScreen : public CWindowObject
+{
+	const JsonNode & config;
+
+	std::shared_ptr<CTabbedInt> tabs;
+
+	std::shared_ptr<CPicture> background;
+	std::vector<std::shared_ptr<CPicture>> images;
+
+	CIntObject * createTab(size_t index);
+
+public:
+	std::vector<std::string> menuNameToEntry;
+
+	CMenuScreen(const JsonNode & configNode);
+
+	void show(SDL_Surface * to) override;
+	void activate() override;
+	void deactivate() override;
+
+	void switchToTab(size_t index);
+};
+
+class CMenuEntry : public CIntObject
+{
+	std::vector<std::shared_ptr<CPicture>> images;
+	std::vector<std::shared_ptr<CButton>> buttons;
+
+	std::shared_ptr<CButton> createButton(CMenuScreen * parent, const JsonNode & button);
+
+public:
+	CMenuEntry(CMenuScreen * parent, const JsonNode & config);
+};
+
+/// Multiplayer mode
+class CMultiMode : public CIntObject
+{
+public:
+	ESelectionScreen screenType;
+	std::shared_ptr<CPicture> background;
+	std::shared_ptr<CTextInput> playerName;
+	std::shared_ptr<CButton> buttonHotseat;
+	std::shared_ptr<CButton> buttonHost;
+	std::shared_ptr<CButton> buttonJoin;
+	std::shared_ptr<CButton> buttonCancel;
+	std::shared_ptr<CGStatusBar> statusBar;
+
+	CMultiMode(ESelectionScreen ScreenType);
+	void hostTCP();
+	void joinTCP();
+
+	void onNameChange(std::string newText);
+};
+
+/// Hot seat player window
+class CMultiPlayers : public CIntObject
+{
+	bool host;
+	ELoadMode loadMode;
+	ESelectionScreen screenType;
+	std::shared_ptr<CPicture> background;
+	std::shared_ptr<CTextBox> textTitle;
+	std::array<std::shared_ptr<CTextInput>, 8> inputNames;
+	std::shared_ptr<CButton> buttonOk;
+	std::shared_ptr<CButton> buttonCancel;
+	std::shared_ptr<CGStatusBar> statusBar;
+
+	void onChange(std::string newText);
+	void enterSelectionScreen();
+
+public:
+	CMultiPlayers(const std::string & firstPlayer, ESelectionScreen ScreenType, bool Host, ELoadMode LoadMode);
+};
+
+/// Manages the configuration of pregame GUI elements like campaign screen, main menu, loading screen,...
+class CMainMenuConfig
+{
+public:
+	static CMainMenuConfig & get();
+	const JsonNode & getConfig() const;
+	const JsonNode & getCampaigns() const;
+
+private:
+	CMainMenuConfig();
+
+	const JsonNode campaignSets;
+	const JsonNode config;
+};
+
+/// Handles background screen, loads graphics for victory/loss condition and random town or hero selection
+class CMainMenu : public CIntObject, public IUpdateable
+{
+	std::shared_ptr<CFilledTexture> backgroundAroundMenu;
+
+	CMainMenu(); //Use CMainMenu::create
+
+public:
+	CMenuScreen * menu;
+
+	~CMainMenu();
+	void update() override;
+	static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> * names, ELoadMode loadMode);
+	static void openCampaignLobby(const std::string & campaignFileName);
+	static void openCampaignLobby(std::shared_ptr<CCampaignState> campaign);
+	void openCampaignScreen(std::string name);
+
+	static CMainMenu * create();
+	void removeFromGui();
+	static void showLoadingScreen(std::function<void()> loader);
+
+	static std::shared_ptr<CPicture> createPicture(const JsonNode & config);
+
+};
+
+/// Simple window to enter the server's address.
+class CSimpleJoinScreen : public CIntObject
+{
+	std::shared_ptr<CPicture> background;
+	std::shared_ptr<CTextBox> textTitle;
+	std::shared_ptr<CButton> buttonOk;
+	std::shared_ptr<CButton> buttonCancel;
+	std::shared_ptr<CGStatusBar> statusBar;
+	std::shared_ptr<CTextInput> inputAddress;
+	std::shared_ptr<CTextInput> inputPort;
+
+	void connectToServer();
+	void leaveScreen();
+	void onChange(const std::string & newText);
+	void connectThread(const std::string addr = "", const ui16 inputPort = 0);
+
+public:
+	CSimpleJoinScreen(bool host = true);
+};
+
+class CLoadingScreen : public CWindowObject
+{
+	boost::thread loadingThread;
+
+	std::string getBackground();
+
+public:
+	CLoadingScreen(std::function<void()> loader);
+	~CLoadingScreen();
+
+	void showAll(SDL_Surface * to) override;
+};
+
+extern CMainMenu * CMM;

+ 64 - 0
client/mainmenu/CPrologEpilogVideo.cpp

@@ -0,0 +1,64 @@
+/*
+ * CPrologEpilogVideo.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 "CPrologEpilogVideo.h"
+#include "../CGameInfo.h"
+#include "../CMusicHandler.h"
+#include "../CVideoHandler.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/TextControls.h"
+
+#include "../../lib/mapping/CCampaignHandler.h"
+
+
+CPrologEpilogVideo::CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback)
+	: CWindowObject(BORDERED), spe(_spe), positionCounter(0), voiceSoundHandle(-1), exitCb(callback)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	addUsedEvents(LCLICK);
+	pos = center(Rect(0, 0, 800, 600));
+	updateShadow();
+
+	CCS->videoh->open(CCampaignHandler::prologVideoName(spe.prologVideo));
+	CCS->musich->playMusic("Music/" + CCampaignHandler::prologMusicName(spe.prologMusic), true);
+	// MPTODO: Custom campaign crashing on this?
+//	voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo));
+
+	text = std::make_shared<CMultiLineLabel>(Rect(100, 500, 600, 100), EFonts::FONT_BIG, CENTER, Colors::METALLIC_GOLD, spe.prologText);
+	text->scrollTextTo(-100);
+}
+
+void CPrologEpilogVideo::show(SDL_Surface * to)
+{
+	CSDL_Ext::fillRectBlack(to, &pos);
+	//BUG: some videos are 800x600 in size while some are 800x400
+	//VCMI should center them in the middle of the screen. Possible but needs modification
+	//of video player API which I'd like to avoid until we'll get rid of Windows-specific player
+	CCS->videoh->update(pos.x, pos.y, to, true, false);
+
+	//move text every 5 calls/frames; seems to be good enough
+	++positionCounter;
+	if(positionCounter % 5 == 0)
+		text->scrollTextBy(1);
+	else
+		text->showAll(to); // blit text over video, if needed
+
+	if(text->textSize.y + 100 < positionCounter / 5)
+		clickLeft(false, false);
+}
+
+void CPrologEpilogVideo::clickLeft(tribool down, bool previousState)
+{
+	GH.popInt(this);
+	CCS->soundh->stopSound(voiceSoundHandle);
+	exitCb();
+}

+ 31 - 0
client/mainmenu/CPrologEpilogVideo.h

@@ -0,0 +1,31 @@
+/*
+ * CPrologEpilogVideo.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 "../windows/CWindowObject.h"
+#include "../../lib/mapping/CCampaignHandler.h"
+
+class CMultiLineLabel;
+class SDL_Surface;
+
+class CPrologEpilogVideo : public CWindowObject
+{
+	CCampaignScenario::SScenarioPrologEpilog spe;
+	int positionCounter;
+	int voiceSoundHandle;
+	std::function<void()> exitCb;
+
+	std::shared_ptr<CMultiLineLabel> text;
+
+public:
+	CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback);
+
+	void clickLeft(tribool down, bool previousState) override;
+	void show(SDL_Surface * to) override;
+};

+ 59 - 0
client/mainmenu/CreditsScreen.cpp

@@ -0,0 +1,59 @@
+/*
+ * CreditsScreen.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 "CreditsScreen.h"
+#include "../mainmenu/CMainMenu.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/TextControls.h"
+#include "../widgets/ObjectLists.h"
+
+#include "../../lib/filesystem/Filesystem.h"
+
+CreditsScreen::CreditsScreen()
+	: positionCounter(0)
+{
+	addUsedEvents(LCLICK | RCLICK);
+	type |= REDRAW_PARENT;
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	pos.w = CMM->menu->pos.w;
+	pos.h = CMM->menu->pos.h;
+	auto textFile = CResourceHandler::get()->load(ResourceID("DATA/CREDITS.TXT"))->readAll();
+	std::string text((char *)textFile.first.get(), textFile.second);
+	size_t firstQuote = text.find('\"') + 1;
+	text = text.substr(firstQuote, text.find('\"', firstQuote) - firstQuote);
+	credits = std::make_shared<CMultiLineLabel>(Rect(pos.w - 350, 0, 350, 600), FONT_CREDITS, CENTER, Colors::WHITE, text);
+	credits->scrollTextTo(-600); // move all text below the screen
+}
+
+void CreditsScreen::show(SDL_Surface * to)
+{
+	CIntObject::show(to);
+	positionCounter++;
+	if(positionCounter % 2 == 0)
+		credits->scrollTextBy(1);
+
+	//end of credits, close this screen
+	if(credits->textSize.y + 600 < positionCounter / 2)
+		clickRight(false, false);
+}
+
+void CreditsScreen::clickLeft(tribool down, bool previousState)
+{
+	clickRight(down, previousState);
+}
+
+void CreditsScreen::clickRight(tribool down, bool previousState)
+{
+	CTabbedInt * menu = dynamic_cast<CTabbedInt *>(parent);
+	assert(menu);
+	menu->setActive(0);
+}

+ 27 - 0
client/mainmenu/CreditsScreen.h

@@ -0,0 +1,27 @@
+/*
+ * CreditsScreen.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 "../windows/CWindowObject.h"
+
+class CMultiLineLabel;
+class SDL_Surface;
+
+class CreditsScreen : public CIntObject
+{
+	int positionCounter;
+	std::shared_ptr<CMultiLineLabel> credits;
+
+public:
+	CreditsScreen();
+	void show(SDL_Surface * to) override;
+	void clickLeft(tribool down, bool previousState) override;
+	void clickRight(tribool down, bool previousState) override;
+};

+ 1 - 1
client/widgets/AdventureMapClasses.cpp

@@ -18,7 +18,7 @@
 #include "../CGameInfo.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
-#include "../CPreGame.h"
+#include "../mainmenu/CMainMenu.h"
 #include "../Graphics.h"
 #include "../CMessage.h"
 

+ 12 - 8
client/windows/CAdvmapInterface.cpp

@@ -22,7 +22,10 @@
 #include "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
-#include "../CPreGame.h"
+#include "../mainmenu/CMainMenu.h"
+#include "../lobby/CBonusSelection.h"
+#include "../lobby/CSavingScreen.h"
+#include "../lobby/CScenarioInfoScreen.h"
 #include "../Graphics.h"
 #include "../mapHandler.h"
 
@@ -47,6 +50,7 @@
 #include "../../lib/mapping/CMap.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/VCMI_Lib.h"
+#include "../../lib/StartInfo.h"
 
 #ifdef _MSC_VER
 #pragma warning (disable : 4355)
@@ -935,7 +939,8 @@ void CAdvMapInt::activate()
 		}
 		minimap.activate();
 		terrain.activate();
-		LOCPLINT->cingconsole->activate();
+		if(LOCPLINT)
+			LOCPLINT->cingconsole->activate();
 
 		GH.fakeMouseMove(); //to restore the cursor
 	}
@@ -1215,7 +1220,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 		return;
 	case SDLK_s:
 		if(isActive() && key.type == SDL_KEYUP)
-			GH.pushInt(new CSavingScreen(CPlayerInterface::howManyPeople > 1));
+			GH.pushInt(new CSavingScreen());
 		return;
 	case SDLK_d:
 		{
@@ -1235,7 +1240,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 		if(isActive() && LOCPLINT->ctrlPressed())
 		{
 			LOCPLINT->showYesNoDialog("Are you sure you want to restart game?",
-				[](){ LOCPLINT->sendCustomEvent(RESTART_GAME); },
+				[](){ LOCPLINT->sendCustomEvent(EUserEvent::RESTART_GAME); },
 				[](){}, true);
 		}
 		return;
@@ -1947,14 +1952,13 @@ CAdventureOptions::CAdventureOptions():
 
 void CAdventureOptions::showScenarioInfo()
 {
-	auto campState = LOCPLINT->cb->getStartInfo()->campState;
-	if(campState)
+	if(LOCPLINT->cb->getStartInfo()->campState)
 	{
-		GH.pushInt(new CBonusSelection(campState));
+		GH.pushInt(new CBonusSelection());
 	}
 	else
 	{
-		GH.pushInt(new CScenarioInfo(LOCPLINT->cb->getMapHeader(), LOCPLINT->cb->getStartInfo()));
+		GH.pushInt(new CScenarioInfoScreen());
 	}
 }
 

+ 14 - 5
client/windows/GUIClasses.cpp

@@ -21,10 +21,10 @@
 #include "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
-#include "../CPreGame.h"
 #include "../CVideoHandler.h"
 #include "../Graphics.h"
 #include "../mapHandler.h"
+#include "../CServerHandler.h"
 
 #include "../battle/CBattleInterfaceClasses.h"
 #include "../battle/CBattleInterface.h"
@@ -38,6 +38,8 @@
 #include "../widgets/MiscWidgets.h"
 #include "../windows/InfoWindows.h"
 
+#include "../lobby/CSavingScreen.h"
+
 #include "../../CCallback.h"
 
 #include "../lib/mapObjects/CGHeroInstance.h"
@@ -487,6 +489,13 @@ CSystemOptionsWindow::CSystemOptionsWindow():
 	restart = new CButton (Point(246, 357), "SORSTRT", CGI->generaltexth->zelp[323], [&](){ brestartf(); }, SDLK_r);
 	restart->setImageOrder(1, 0, 2, 3);
 
+	if(CSH->isGuest())
+	{
+		load->block(true);
+		save->block(true);
+		restart->block(true);
+	}
+
 	mainMenu = new CButton (Point(357, 357), "SOMAIN.DEF", CGI->generaltexth->zelp[320], [&](){ bmainmenuf(); }, SDLK_m);
 	mainMenu->setImageOrder(1, 0, 2, 3);
 
@@ -593,7 +602,7 @@ void CSystemOptionsWindow::setGameRes(int index)
 
 void CSystemOptionsWindow::bquitf()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(SDL_USEREVENT, FORCE_QUIT); }, 0);
+	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(SDL_USEREVENT, EUserEvent::FORCE_QUIT); }, 0);
 }
 
 void CSystemOptionsWindow::breturnf()
@@ -603,7 +612,7 @@ void CSystemOptionsWindow::breturnf()
 
 void CSystemOptionsWindow::bmainmenuf()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(SDL_USEREVENT, RETURN_TO_MAIN_MENU); }, 0);
+	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(SDL_USEREVENT, EUserEvent::RETURN_TO_MAIN_MENU); }, 0);
 }
 
 void CSystemOptionsWindow::bloadf()
@@ -615,12 +624,12 @@ void CSystemOptionsWindow::bloadf()
 void CSystemOptionsWindow::bsavef()
 {
 	GH.popIntTotally(this);
-	GH.pushInt(new CSavingScreen(CPlayerInterface::howManyPeople > 1));
+	GH.pushInt(new CSavingScreen());
 }
 
 void CSystemOptionsWindow::brestartf()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [this](){ closeAndPushEvent(SDL_USEREVENT, RESTART_GAME); }, 0);
+	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [this](){ closeAndPushEvent(SDL_USEREVENT, EUserEvent::RESTART_GAME); }, 0);
 }
 
 void CSystemOptionsWindow::closeAndPushEvent(int eventType, int code)

+ 44 - 34
lib/CGameState.cpp

@@ -64,8 +64,6 @@ public:
 	}
 };
 
-static CApplier<CBaseForGSApply> *applierGs = nullptr;
-
 void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst) const
 {
 	int type = txt.first, ser = txt.second;
@@ -673,9 +671,9 @@ int CGameState::getDate(Date::EDateType mode) const
 CGameState::CGameState()
 {
 	gs = this;
-	applierGs = new CApplier<CBaseForGSApply>();
-	registerTypesClientPacks1(*applierGs);
-	registerTypesClientPacks2(*applierGs);
+	applier = std::make_shared<CApplier<CBaseForGSApply>>();
+	registerTypesClientPacks1(*applier);
+	registerTypesClientPacks2(*applier);
 	//objCaller = new CObjectCallersHandler();
 	globalEffects.setDescription("Global effects");
 	globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
@@ -686,10 +684,6 @@ CGameState::~CGameState()
 {
 	map.dellNull();
 	curB.dellNull();
-	//delete scenarioOps; //TODO: fix for loading ind delete
-	//delete initialOpts;
-	delete applierGs;
-	//delete objCaller;
 
 	for(auto ptr : hpool.heroesPool) // clean hero pool
 		ptr.second.dellNull();
@@ -709,7 +703,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
 		initNewGame(mapService, allowSavingRandomMap);
 		break;
 	case StartInfo::CAMPAIGN:
-		initCampaign(mapService);
+		initCampaign();
 		break;
 	default:
 		logGlobal->error("Wrong mode: %d", static_cast<int>(scenarioOps->mode));
@@ -765,6 +759,13 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
 	}
 }
 
+void CGameState::updateOnLoad(StartInfo * si)
+{
+	scenarioOps->playerInfos = si->playerInfos;
+	for(auto & i : si->playerInfos)
+		gs->players[i.first].human = i.second.isControlledByHuman();
+}
+
 void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap)
 {
 	if(scenarioOps->createRandomMap())
@@ -815,7 +816,7 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
 				playerSettings.compOnly = !playerInfo.canHumanPlay;
 				playerSettings.team = playerInfo.team;
 				playerSettings.castle = playerInfo.defaultCastle();
-				if(playerSettings.playerID == PlayerSettings::PLAYER_AI && playerSettings.name.empty())
+				if(playerSettings.isControlledByAI() && playerSettings.name.empty())
 				{
 					playerSettings.name = VLC->generaltexth->allTexts[468];
 				}
@@ -837,19 +838,10 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
 	}
 }
 
-void CGameState::initCampaign(const IMapService * mapService)
+void CGameState::initCampaign()
 {
 	logGlobal->info("Open campaign map file: %d", scenarioOps->campState->currentMap.get());
-	auto campaign = scenarioOps->campState;
-	assert(vstd::contains(campaign->camp->mapPieces, *scenarioOps->campState->currentMap));
-
-	std::string scenarioName = scenarioOps->mapname.substr(0, scenarioOps->mapname.find('.'));
-	boost::to_lower(scenarioName);
-	scenarioName += ':' + boost::lexical_cast<std::string>(*campaign->currentMap);
-
-	std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap];
-	auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
-	map = mapService->loadMap(buffer, mapContent.size(), scenarioName).release();
+	map = scenarioOps->campState->getMap();
 }
 
 void CGameState::checkMapChecksum()
@@ -962,13 +954,11 @@ void CGameState::initPlayerStates()
 	for(auto & elem : scenarioOps->playerInfos)
 	{
 		PlayerState & p = players[elem.first];
-		//std::pair<PlayerColor, PlayerState> ins(elem.first,PlayerState());
 		p.color=elem.first;
-		p.human = elem.second.playerID;
+		p.human = elem.second.isControlledByHuman();
 		p.team = map->players[elem.first.getNum()].team;
 		teams[p.team].id = p.team;//init team
 		teams[p.team].players.insert(elem.first);//add player to team
-		//players.insert(ins);
 	}
 }
 
@@ -1091,13 +1081,26 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
 	auto bonus = campaignState->getBonusForCurrentMap();
 	if (bonus && bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO)
 	{
-		crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[bonus->info2].crossoverHeroes;
+		std::vector<CGHeroInstance *> heroes;
+		for(auto & node : campaignState->camp->scenarios[bonus->info2].crossoverHeroes)
+		{
+			auto h = CCampaignState::crossoverDeserialize(node);
+			heroes.push_back(h);
+		}
+		crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = heroes;
 	}
 	else
 	{
 		if(!campaignState->mapsConquered.empty())
 		{
-			crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[campaignState->mapsConquered.back()].crossoverHeroes;
+			std::vector<CGHeroInstance *> heroes;
+			for(auto & node : campaignState->camp->scenarios[campaignState->mapsConquered.back()].crossoverHeroes)
+			{
+				auto h = CCampaignState::crossoverDeserialize(node);
+				heroes.push_back(h);
+			}
+			crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = heroes;
+			crossoverHeroes.heroesFromPreviousScenario = heroes;
 
 			for(auto mapNr : campaignState->mapsConquered)
 			{
@@ -1108,15 +1111,22 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
 				// remove heroes which didn't reached the end of the scenario, but were available at the start
 				for(auto hero : lostCrossoverHeroes)
 				{
-					vstd::erase_if(crossoverHeroes.heroesFromAnyPreviousScenarios,
-					               CGObjectInstanceBySubIdFinder(hero));
+//					auto hero = CCampaignState::crossoverDeserialize(node);
+					vstd::erase_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
+					{
+						return hero->subID == h->subID;
+					});
 				}
 
 				// now add heroes which completed the scenario
-				for(auto hero : scenario.crossoverHeroes)
+				for(auto node : scenario.crossoverHeroes)
 				{
+					auto hero = CCampaignState::crossoverDeserialize(node);
 					// add new heroes and replace old heroes with newer ones
-					auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, CGObjectInstanceBySubIdFinder(hero));
+					auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios,  [hero](CGHeroInstance * h)
+					{
+						return hero->subID == h->subID;
+					});
 					if (it != crossoverHeroes.heroesFromAnyPreviousScenarios.end())
 					{
 						// replace old hero with newer one
@@ -1306,7 +1316,7 @@ void CGameState::initStartingResources()
 		for(auto it = scenarioOps->playerInfos.cbegin();
 			it != scenarioOps->playerInfos.cend(); ++it)
 		{
-			if(it->second.playerID != PlayerSettings::PLAYER_AI)
+			if(it->second.isControlledByHuman())
 				ret.push_back(&it->second);
 		}
 
@@ -1955,7 +1965,7 @@ PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor col
 void CGameState::apply(CPack *pack)
 {
 	ui16 typ = typeList.getTypeID(pack);
-	applierGs->getApplier(typ)->applyOnGS(this,pack);
+	applier->getApplier(typ)->applyOnGS(this, pack);
 }
 
 void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
@@ -2806,7 +2816,7 @@ void CGameState::replaceHeroesPlaceholders(const std::vector<CGameState::Campaig
 		map->objects[heroToPlace->id.getNum()] = heroToPlace;
 		map->addBlockVisTiles(heroToPlace);
 
-		scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(heroToPlace);
+		scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(CCampaignState::crossoverSerialize(heroToPlace));
 	}
 }
 

+ 6 - 1
lib/CGameState.h

@@ -60,6 +60,9 @@ namespace boost
 	class shared_mutex;
 }
 
+template<typename T> class CApplier;
+class CBaseForGSApply;
+
 struct DLL_LINKAGE SThievesGuildInfo
 {
 	std::vector<PlayerColor> playerColors; //colors of players that are in-game
@@ -153,6 +156,7 @@ public:
 	virtual ~CGameState();
 
 	void init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false);
+	void updateOnLoad(StartInfo * si);
 
 	ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
 	PlayerColor currentPlayer; //ID of player currently having turn
@@ -246,7 +250,7 @@ private:
 	// ----- initialization -----
 
 	void initNewGame(const IMapService * mapService, bool allowSavingRandomMap);
-	void initCampaign(const IMapService * mapService);
+	void initCampaign();
 	void checkMapChecksum();
 	void initGrailPosition();
 	void initRandomFactionsForPlayers();
@@ -291,6 +295,7 @@ private:
 	int pickNextHeroType(PlayerColor owner); // picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
 
 	// ---- data -----
+	std::shared_ptr<CApplier<CBaseForGSApply>> applier;
 	CRandomGenerator rand;
 
 	friend class CCallback;

+ 3 - 1
lib/CMakeLists.txt

@@ -74,7 +74,7 @@ set(lib_SRCS
 		registerTypes/TypesMapObjects1.cpp
 		registerTypes/TypesMapObjects2.cpp
 		registerTypes/TypesMapObjects3.cpp
-		registerTypes/TypesPregamePacks.cpp
+		registerTypes/TypesLobbyPacks.cpp
 		registerTypes/TypesServerPacks.cpp
 
 		rmg/CMapGenerator.cpp
@@ -152,6 +152,7 @@ set(lib_SRCS
 		JsonNode.cpp
 		LogicalExpression.cpp
 		NetPacksLib.cpp
+		StartInfo.cpp
 		ResourceSet.cpp
 		VCMIDirs.cpp
 		VCMI_Lib.cpp
@@ -336,6 +337,7 @@ set(lib_HEADERS
 		LogicalExpression.h
 		NetPacksBase.h
 		NetPacks.h
+		NetPacksLobby.h
 		ResourceSet.h
 		ScopeGuard.h
 		StartInfo.h

+ 2 - 0
lib/GameConstants.h

@@ -51,6 +51,8 @@ namespace GameConstants
 	const ui32 BASE_MOVEMENT_COST = 100; //default cost for non-diagonal movement
 
 	const int HERO_PORTRAIT_SHIFT = 30;// 2 special frames + some extra portraits
+
+	const std::array<int, 11> POSSIBLE_TURNTIME = {1, 2, 4, 6, 8, 10, 15, 20, 25, 30, 0};
 }
 
 class CArtifact;

+ 3 - 0
lib/IGameCallback.cpp

@@ -28,6 +28,9 @@
 #include "CGameState.h"
 #include "mapping/CMap.h"
 #include "CPlayerState.h"
+#include "CSkillHandler.h"
+
+#include "serializer/Connection.h"
 
 void CPrivilegedInfoCallback::getFreeTiles(std::vector<int3> & tiles) const
 {

+ 64 - 204
lib/NetPacks.h

@@ -22,16 +22,15 @@
 
 #include "spells/ViewSpellInt.h"
 
-class CCampaignState;
+class CClient;
+class CGameState;
+class CGameHandler;
 class CArtifact;
-class CSelectionScreen;
 class CGObjectInstance;
 class CArtifactInstance;
 struct StackLocation;
 struct ArtSlotInfo;
 struct QuestInfo;
-class CMapInfo;
-struct StartInfo;
 class IBattleState;
 
 struct Query : public CPackForClient
@@ -438,18 +437,6 @@ struct RemoveBonus :  public CPackForClient
 	}
 };
 
-struct UpdateCampaignState : public CPackForClient
-{
-	UpdateCampaignState(){}
-	std::shared_ptr<CCampaignState> camp;
-	void applyCl(CClient *cl);
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & camp;
-	}
-};
-
 struct SetCommanderProperty : public CPackForClient
 {
 	enum ECommanderProperty {ALIVE, BONUS, SECONDARY_SKILL, EXPERIENCE, SPECIAL_SKILL};
@@ -495,16 +482,6 @@ struct AddQuest : public CPackForClient
 	}
 };
 
-struct PrepareForAdvancingCampaign : public CPackForClient
-{
-	PrepareForAdvancingCampaign(){}
-
-	void applyCl(CClient *cl);
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-	}
-};
-
 struct UpdateArtHandlerLists : public CPackForClient
 {
 	UpdateArtHandlerLists(){}
@@ -1889,30 +1866,18 @@ struct CommitPackage : public CPackForServer
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & packToCommit;
 	}
 };
 
-
-struct CloseServer : public CPackForServer
-{
-	bool applyGh(CGameHandler *gh);
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{}
-};
-
-struct LeaveGame : public CPackForServer
-{
-	bool applyGh(CGameHandler *gh);
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{}
-};
-
 struct EndTurn : public CPackForServer
 {
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
-	{}
+	{
+		h & static_cast<CPackForServer &>(*this);
+	}
 };
 
 struct DismissHero : public CPackForServer
@@ -1924,6 +1889,7 @@ struct DismissHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 	}
 };
@@ -1939,6 +1905,7 @@ struct MoveHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & dest;
 		h & hid;
 		h & transit;
@@ -1956,6 +1923,7 @@ struct CastleTeleportHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & dest;
 		h & hid;
 	}
@@ -1974,6 +1942,7 @@ struct ArrangeStacks : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & what;
 		h & p1;
 		h & p2;
@@ -1993,6 +1962,7 @@ struct DisbandCreature : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & pos;
 		h & id;
 	}
@@ -2008,6 +1978,7 @@ struct BuildStructure : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & tid;
 		h & bid;
 	}
@@ -2033,6 +2004,7 @@ struct RecruitCreatures : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & tid;
 		h & dst;
 		h & crid;
@@ -2052,6 +2024,7 @@ struct UpgradeCreature : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & pos;
 		h & id;
 		h & cid;
@@ -2067,6 +2040,7 @@ struct GarrisonHeroSwap : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & tid;
 	}
 };
@@ -2079,6 +2053,7 @@ struct ExchangeArtifacts : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & src;
 		h & dst;
 	}
@@ -2097,6 +2072,7 @@ struct AssembleArtifacts : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & heroID;
 		h & artifactSlot;
 		h & assemble;
@@ -2114,6 +2090,7 @@ struct BuyArtifact : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 		h & aid;
 	}
@@ -2135,6 +2112,7 @@ struct TradeOnMarketplace : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & marketId;
 		h & heroId;
 		h & mode;
@@ -2154,6 +2132,7 @@ struct SetFormation : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 		h & formation;
 	}
@@ -2170,6 +2149,7 @@ struct HireHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 		h & tid;
 		h & player;
@@ -2184,6 +2164,7 @@ struct BuildBoat : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & objid;
 	}
 
@@ -2200,6 +2181,7 @@ struct QueryReply : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & qid;
 		h & player;
 		h & reply;
@@ -2215,6 +2197,7 @@ struct MakeAction : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & ba;
 	}
 };
@@ -2228,6 +2211,7 @@ struct MakeCustomAction : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & ba;
 	}
 };
@@ -2240,6 +2224,7 @@ struct DigWithHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & id;
 	}
 };
@@ -2254,6 +2239,7 @@ struct CastAdvSpell : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 		h & sid;
 		h & pos;
@@ -2262,212 +2248,86 @@ struct CastAdvSpell : public CPackForServer
 
 /***********************************************************************************************************/
 
-struct SaveGame : public CPackForClient, public CPackForServer
+struct SaveGame : public CPackForServer
 {
 	SaveGame(){};
 	SaveGame(const std::string &Fname) :fname(Fname){};
 	std::string fname;
 
-	void applyCl(CClient *cl);
 	void applyGs(CGameState *gs){};
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CPackForServer &>(*this);
+		h & fname;
+	}
+};
+
+// TODO: Eventually we should re-merge both SaveGame and PlayerMessage
+struct SaveGameClient : public CPackForClient
+{
+	SaveGameClient(){};
+	SaveGameClient(const std::string &Fname) :fname(Fname){};
+	std::string fname;
+
+	void applyCl(CClient *cl);
+	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & fname;
 	}
 };
 
-struct PlayerMessage : public CPackForClient, public CPackForServer
+struct PlayerMessage : public CPackForServer
 {
 	PlayerMessage(){};
-	PlayerMessage(PlayerColor Player, const std::string &Text, ObjectInstanceID obj)
-		:player(Player),text(Text), currObj(obj)
+	PlayerMessage(const std::string &Text, ObjectInstanceID obj)
+		: text(Text), currObj(obj)
 	{};
-	void applyCl(CClient *cl);
 	void applyGs(CGameState *gs){};
 	bool applyGh(CGameHandler *gh);
 
-	PlayerColor player;
 	std::string text;
 	ObjectInstanceID currObj; // optional parameter that specifies current object. For cheats :)
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & text;
-		h & player;
 		h & currObj;
 	}
 };
 
-struct CenterView : public CPackForClient
+struct PlayerMessageClient : public CPackForClient
 {
-	CenterView():focusTime(0){};
+	PlayerMessageClient(){};
+	PlayerMessageClient(PlayerColor Player, const std::string &Text)
+		: player(Player), text(Text)
+	{}
 	void applyCl(CClient *cl);
 
 	PlayerColor player;
-	int3 pos;
-	ui32 focusTime; //ms
+	std::string text;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & pos;
 		h & player;
-		h & focusTime;
-	}
-};
-
-/***********************************************************************************************************/
-
-struct CPackForSelectionScreen : public CPack
-{
-	void apply(CSelectionScreen *selScreen) {} // implemented in CPreGame.cpp
-};
-
-class CPregamePackToPropagate  : public CPackForSelectionScreen
-{};
-
-class CPregamePackToHost  : public CPackForSelectionScreen
-{};
-
-struct ChatMessage : public CPregamePackToPropagate
-{
-	std::string playerName, message;
-
-	void apply(CSelectionScreen *selScreen);
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & playerName;
-		h & message;
-	}
-};
-
-struct QuitMenuWithoutStarting : public CPregamePackToPropagate
-{
-	void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{}
-};
-
-struct PlayerJoined : public CPregamePackToHost
-{
-	std::string playerName;
-	ui8 connectionID;
-
-	void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & playerName;
-		h & connectionID;
-	}
-};
-
-struct ELF_VISIBILITY SelectMap : public CPregamePackToPropagate
-{
-	const CMapInfo *mapInfo;
-	bool free;//local flag, do not serialize
-
-	DLL_LINKAGE SelectMap(const CMapInfo &src);
-	DLL_LINKAGE SelectMap();
-	DLL_LINKAGE ~SelectMap();
-
-	void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & mapInfo;
-	}
-
-};
-
-struct ELF_VISIBILITY UpdateStartOptions : public CPregamePackToPropagate
-{
-	StartInfo *options;
-	bool free;//local flag, do not serialize
-
-	void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
-
-	DLL_LINKAGE UpdateStartOptions(StartInfo &src);
-	DLL_LINKAGE UpdateStartOptions();
-	DLL_LINKAGE ~UpdateStartOptions();
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & options;
-	}
-
-};
-
-struct PregameGuiAction : public CPregamePackToPropagate
-{
-	enum {NO_TAB, OPEN_OPTIONS, OPEN_SCENARIO_LIST, OPEN_RANDOM_MAP_OPTIONS}
-		action;
-
-	void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & action;
-	}
-};
-
-struct RequestOptionsChange : public CPregamePackToHost
-{
-	enum EWhat {TOWN, HERO, BONUS};
-	ui8 what;
-	si8 direction; //-1 or +1
-	ui8 playerID;
-
-	RequestOptionsChange(ui8 What, si8 Dir, ui8 Player)
-		:what(What), direction(Dir), playerID(Player)
-	{}
-	RequestOptionsChange()
-		:what(0), direction(0), playerID(0)
-	{}
-
-	void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & what;
-		h & direction;
-		h & playerID;
-	}
-};
-
-struct PlayerLeft : public CPregamePackToPropagate
-{
-	ui8 playerID;
-
-	void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & playerID;
+		h & text;
 	}
 };
 
-struct PlayersNames : public CPregamePackToPropagate
+struct CenterView : public CPackForClient
 {
-public:
-	std::map<ui8, std::string> playerNames;
-
-	void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & playerNames;
-	}
-};
+	CenterView():focusTime(0){};
+	void applyCl(CClient *cl);
 
-struct StartWithCurrentSettings : public CPregamePackToPropagate
-{
-public:
-	void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
+	PlayerColor player;
+	int3 pos;
+	ui32 focusTime; //ms
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		//h & playerNames;
+		h & pos;
+		h & player;
+		h & focusTime;
 	}
 };

+ 12 - 6
lib/NetPacksBase.h

@@ -20,7 +20,6 @@ class CArmedInstance;
 class CArtifactSet;
 class CBonusSystemNode;
 struct ArtSlotInfo;
-class BattleInfo;
 
 #include "ConstTransitivePtr.h"
 #include "GameConstants.h"
@@ -28,7 +27,9 @@ class BattleInfo;
 
 struct DLL_LINKAGE CPack
 {
-	CPack() {};
+	std::shared_ptr<CConnection> c; // Pointer to connection that pack received from
+
+	CPack() : c(nullptr) {};
 	virtual ~CPack() {};
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -54,12 +55,11 @@ struct CPackForClient : public CPack
 
 struct CPackForServer : public CPack
 {
-	PlayerColor player;
-	CConnection *c;
+	mutable PlayerColor player;
+	mutable si32 requestID;
 	CGameState* GS(CGameHandler *gh);
 	CPackForServer():
-		player(PlayerColor::NEUTRAL),
-		c(nullptr)
+		player(PlayerColor::NEUTRAL)
 	{
 	}
 
@@ -69,6 +69,12 @@ struct CPackForServer : public CPack
 		return false;
 	}
 
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & player;
+		h & requestID;
+	}
+
 protected:
 	void throwNotAllowedAction();
 	void throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id);

+ 37 - 36
lib/NetPacksLib.cpp

@@ -56,40 +56,6 @@ DLL_LINKAGE void SetSecSkill::applyGs(CGameState *gs)
 	hero->setSecSkillLevel(which, val, abs);
 }
 
-DLL_LINKAGE SelectMap::SelectMap(const CMapInfo &src)
-{
-	mapInfo = &src;
-	free = false;
-}
-DLL_LINKAGE SelectMap::SelectMap()
-{
-	mapInfo = nullptr;
-	free = true;
-}
-
-DLL_LINKAGE SelectMap::~SelectMap()
-{
-	if(free)
-		delete mapInfo;
-}
-
-DLL_LINKAGE  UpdateStartOptions::UpdateStartOptions(StartInfo &src)
-{
-	options = &src;
-	free = false;
-}
-DLL_LINKAGE  UpdateStartOptions::UpdateStartOptions()
-{
-	options = nullptr;
-	free = true;
-}
-
-DLL_LINKAGE UpdateStartOptions::~UpdateStartOptions()
-{
-	if(free)
-		delete options;
-}
-
 DLL_LINKAGE void SetCommanderProperty::applyGs(CGameState *gs)
 {
 	CCommanderInstance * commander = gs->getHero(heroid)->commander;
@@ -346,8 +312,43 @@ DLL_LINKAGE void ChangeObjectVisitors::applyGs(CGameState *gs)
 DLL_LINKAGE void PlayerEndsGame::applyGs(CGameState *gs)
 {
 	PlayerState *p = gs->getPlayer(player);
-	if(victoryLossCheckResult.victory()) p->status = EPlayerStatus::WINNER;
-	else p->status = EPlayerStatus::LOSER;
+	if(victoryLossCheckResult.victory())
+	{
+		p->status = EPlayerStatus::WINNER;
+
+		// TODO: Campaign-specific code might as well go somewhere else
+		if(p->human && gs->scenarioOps->campState)
+		{
+			std::vector<CGHeroInstance *> crossoverHeroes;
+			for (CGHeroInstance * hero : gs->map->heroesOnMap)
+			{
+				if (hero->tempOwner == player)
+				{
+					// keep all heroes from the winning player
+					crossoverHeroes.push_back(hero);
+				}
+				else if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(hero->subID)))
+				{
+					// keep hero whether lost or won (like Xeron in AB campaign)
+					crossoverHeroes.push_back(hero);
+				}
+			}
+			// keep lost heroes which are in heroes pool
+			for (auto & heroPair : gs->hpool.heroesPool)
+			{
+				if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(heroPair.first)))
+				{
+					crossoverHeroes.push_back(heroPair.second.get());
+				}
+			}
+
+			gs->scenarioOps->campState->setCurrentMapAsConquered(crossoverHeroes);
+		}
+	}
+	else
+	{
+		p->status = EPlayerStatus::LOSER;
+	}
 }
 
 DLL_LINKAGE void RemoveBonus::applyGs(CGameState *gs)

+ 311 - 0
lib/NetPacksLobby.h

@@ -0,0 +1,311 @@
+/*
+ * NetPacksLobby.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 "NetPacksBase.h"
+
+#include "StartInfo.h"
+
+class CCampaignState;
+class CLobbyScreen;
+class CServerHandler;
+class CMapInfo;
+struct StartInfo;
+class CMapGenOptions;
+struct ClientPlayer;
+class CVCMIServer;
+
+struct CPackForLobby : public CPack
+{
+	bool checkClientPermissions(CVCMIServer * srv) const
+	{
+		return false;
+	}
+
+	bool applyOnServer(CVCMIServer * srv)
+	{
+		return true;
+	}
+
+	void applyOnServerAfterAnnounce(CVCMIServer * srv) {}
+
+	bool applyOnLobbyHandler(CServerHandler * handler)
+	{
+		return true;
+	}
+
+	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler) {}
+};
+
+struct CLobbyPackToPropagate : public CPackForLobby
+{
+
+};
+
+struct CLobbyPackToServer : public CPackForLobby
+{
+	bool checkClientPermissions(CVCMIServer * srv) const;
+	void applyOnServerAfterAnnounce(CVCMIServer * srv);
+};
+
+struct LobbyClientConnected : public CLobbyPackToPropagate
+{
+	// Set by client before sending pack to server
+	std::string uuid;
+	std::vector<std::string> names;
+	StartInfo::EMode mode;
+	// Changed by server before announcing pack
+	int clientId;
+	int hostClientId;
+
+	LobbyClientConnected()
+		: mode(StartInfo::INVALID), clientId(-1), hostClientId(-1)
+	{}
+
+	bool checkClientPermissions(CVCMIServer * srv) const;
+	bool applyOnLobbyHandler(CServerHandler * handler);
+	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
+	bool applyOnServer(CVCMIServer * srv);
+	void applyOnServerAfterAnnounce(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & uuid;
+		h & names;
+		h & mode;
+
+		h & clientId;
+		h & hostClientId;
+	}
+};
+
+struct LobbyClientDisconnected : public CLobbyPackToPropagate
+{
+	int clientId;
+	bool shutdownServer;
+
+	LobbyClientDisconnected() : shutdownServer(false) {}
+	bool checkClientPermissions(CVCMIServer * srv) const;
+	bool applyOnServer(CVCMIServer * srv);
+	void applyOnServerAfterAnnounce(CVCMIServer * srv);
+	bool applyOnLobbyHandler(CServerHandler * handler);
+	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & clientId;
+		h & shutdownServer;
+	}
+};
+
+struct LobbyChatMessage : public CLobbyPackToPropagate
+{
+	std::string playerName, message;
+
+	bool checkClientPermissions(CVCMIServer * srv) const;
+	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & playerName;
+		h & message;
+	}
+};
+
+struct LobbyGuiAction : public CLobbyPackToPropagate
+{
+	enum EAction : ui8 {
+		NONE, NO_TAB, OPEN_OPTIONS, OPEN_SCENARIO_LIST, OPEN_RANDOM_MAP_OPTIONS
+	} action;
+
+	LobbyGuiAction() : action(NONE) {}
+	bool checkClientPermissions(CVCMIServer * srv) const;
+	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & action;
+	}
+};
+
+struct LobbyStartGame : public CLobbyPackToPropagate
+{
+	// Set by server
+	std::shared_ptr<StartInfo> initializedStartInfo;
+
+	LobbyStartGame() : initializedStartInfo(nullptr) {}
+	bool checkClientPermissions(CVCMIServer * srv) const;
+	bool applyOnServer(CVCMIServer * srv);
+	void applyOnServerAfterAnnounce(CVCMIServer * srv);
+	bool applyOnLobbyHandler(CServerHandler * handler);
+	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & initializedStartInfo;
+	}
+};
+
+struct LobbyChangeHost : public CLobbyPackToPropagate
+{
+	int newHostConnectionId;
+
+	LobbyChangeHost() : newHostConnectionId(-1) {}
+	bool checkClientPermissions(CVCMIServer * srv) const;
+	bool applyOnServer(CVCMIServer * srv);
+	bool applyOnServerAfterAnnounce(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & newHostConnectionId;
+	}
+};
+
+struct LobbyUpdateState : public CLobbyPackToPropagate
+{
+	LobbyState state;
+	bool hostChanged; // Used on client-side only
+
+	LobbyUpdateState() : hostChanged(false) {}
+	bool applyOnLobbyHandler(CServerHandler * handler);
+	void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & state;
+	}
+};
+
+struct LobbySetMap : public CLobbyPackToServer
+{
+	std::shared_ptr<CMapInfo> mapInfo;
+	std::shared_ptr<CMapGenOptions> mapGenOpts;
+
+	LobbySetMap() : mapInfo(nullptr), mapGenOpts(nullptr) {}
+	bool applyOnServer(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & mapInfo;
+		h & mapGenOpts;
+	}
+};
+
+struct LobbySetCampaign : public CLobbyPackToServer
+{
+	std::shared_ptr<CCampaignState> ourCampaign;
+
+	LobbySetCampaign() {}
+	bool applyOnServer(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & ourCampaign;
+	}
+};
+
+struct LobbySetCampaignMap : public CLobbyPackToServer
+{
+	int mapId;
+
+	LobbySetCampaignMap() : mapId(-1) {}
+	bool applyOnServer(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & mapId;
+	}
+};
+
+struct LobbySetCampaignBonus : public CLobbyPackToServer
+{
+	int bonusId;
+
+	LobbySetCampaignBonus() : bonusId(-1) {}
+	bool applyOnServer(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & bonusId;
+	}
+};
+
+struct LobbyChangePlayerOption : public CLobbyPackToServer
+{
+	enum EWhat : ui8 {UNKNOWN, TOWN, HERO, BONUS};
+	ui8 what;
+	si8 direction; //-1 or +1
+	PlayerColor color;
+
+	LobbyChangePlayerOption() : what(UNKNOWN), direction(0), color(PlayerColor::CANNOT_DETERMINE) {}
+	bool checkClientPermissions(CVCMIServer * srv) const;
+	bool applyOnServer(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & what;
+		h & direction;
+		h & color;
+	}
+};
+
+struct LobbySetPlayer : public CLobbyPackToServer
+{
+	PlayerColor clickedColor;
+
+	LobbySetPlayer() : clickedColor(PlayerColor::CANNOT_DETERMINE){}
+	bool applyOnServer(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & clickedColor;
+	}
+};
+
+struct LobbySetTurnTime : public CLobbyPackToServer
+{
+	ui8 turnTime;
+
+	LobbySetTurnTime() : turnTime(0) {}
+	bool applyOnServer(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & turnTime;
+	}
+};
+
+struct LobbySetDifficulty : public CLobbyPackToServer
+{
+	ui8 difficulty;
+
+	LobbySetDifficulty() : difficulty(0) {}
+	bool applyOnServer(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & difficulty;
+	}
+};
+
+struct LobbyForceSetPlayer : public CLobbyPackToServer
+{
+	ui8 targetConnectedPlayer;
+	PlayerColor targetPlayerColor;
+
+	LobbyForceSetPlayer() : targetConnectedPlayer(-1), targetPlayerColor(PlayerColor::CANNOT_DETERMINE) {}
+	bool applyOnServer(CVCMIServer * srv);
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & targetConnectedPlayer;
+		h & targetPlayerColor;
+	}
+};

+ 195 - 0
lib/StartInfo.cpp

@@ -0,0 +1,195 @@
+/*
+ * StartInfo.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 "StartInfo.h"
+
+#include "CGeneralTextHandler.h"
+#include "rmg/CMapGenOptions.h"
+#include "mapping/CMapInfo.h"
+
+PlayerSettings::PlayerSettings()
+	: bonus(RANDOM), castle(NONE), hero(RANDOM), heroPortrait(RANDOM), color(0), handicap(NO_HANDICAP), team(0), compOnly(false)
+{
+
+}
+
+bool PlayerSettings::isControlledByAI() const
+{
+	return !connectedPlayerIDs.size();
+}
+
+bool PlayerSettings::isControlledByHuman() const
+{
+	return connectedPlayerIDs.size();
+}
+
+PlayerSettings & StartInfo::getIthPlayersSettings(PlayerColor no)
+{
+	if(playerInfos.find(no) != playerInfos.end())
+		return playerInfos[no];
+	logGlobal->error("Cannot find info about player %s. Throwing...", no.getStr());
+	throw std::runtime_error("Cannot find info about player");
+}
+const PlayerSettings & StartInfo::getIthPlayersSettings(PlayerColor no) const
+{
+	return const_cast<StartInfo &>(*this).getIthPlayersSettings(no);
+}
+
+PlayerSettings * StartInfo::getPlayersSettings(const ui8 connectedPlayerId)
+{
+	for(auto & elem : playerInfos)
+	{
+		if(vstd::contains(elem.second.connectedPlayerIDs, connectedPlayerId))
+			return &elem.second;
+	}
+
+	return nullptr;
+}
+
+std::string StartInfo::getCampaignName() const
+{
+	if(campState->camp->header.name.length())
+		return campState->camp->header.name;
+	else
+		return VLC->generaltexth->allTexts[508];
+}
+
+void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const
+{
+	if(!mi)
+		throw ExceptionMapMissing();
+
+	//there must be at least one human player before game can be started
+	std::map<PlayerColor, PlayerSettings>::const_iterator i;
+	for(i = si->playerInfos.cbegin(); i != si->playerInfos.cend(); i++)
+		if(i->second.isControlledByHuman())
+			break;
+
+	if(i == si->playerInfos.cend() && !ignoreNoHuman)
+		throw ExceptionNoHuman();
+
+	if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
+	{
+		if(!si->mapGenOptions->checkOptions())
+			throw ExceptionNoTemplate();
+	}
+}
+
+bool LobbyInfo::isClientHost(int clientId) const
+{
+	return clientId == hostClientId;
+}
+
+std::set<PlayerColor> LobbyInfo::getAllClientPlayers(int clientId)
+{
+	std::set<PlayerColor> players;
+	for(auto & elem : si->playerInfos)
+	{
+		if(isClientHost(clientId) && elem.second.isControlledByAI())
+			players.insert(elem.first);
+
+		for(ui8 id : elem.second.connectedPlayerIDs)
+		{
+			if(vstd::contains(getConnectedPlayerIdsForClient(clientId), id))
+				players.insert(elem.first);
+		}
+	}
+	if(isClientHost(clientId))
+		players.insert(PlayerColor::NEUTRAL);
+
+	return players;
+}
+
+std::vector<ui8> LobbyInfo::getConnectedPlayerIdsForClient(int clientId) const
+{
+	std::vector<ui8> ids;
+
+	for(auto & pair : playerNames)
+	{
+		if(pair.second.connection == clientId)
+		{
+			for(auto & elem : si->playerInfos)
+			{
+				if(vstd::contains(elem.second.connectedPlayerIDs, pair.first))
+					ids.push_back(pair.first);
+			}
+		}
+	}
+	return ids;
+}
+
+std::set<PlayerColor> LobbyInfo::clientHumanColors(int clientId)
+{
+	std::set<PlayerColor> players;
+	for(auto & elem : si->playerInfos)
+	{
+		for(ui8 id : elem.second.connectedPlayerIDs)
+		{
+			if(vstd::contains(getConnectedPlayerIdsForClient(clientId), id))
+			{
+				players.insert(elem.first);
+			}
+		}
+	}
+
+	return players;
+}
+
+
+PlayerColor LobbyInfo::clientFirstColor(int clientId) const
+{
+	for(auto & pair : si->playerInfos)
+	{
+		if(isClientColor(clientId, pair.first))
+			return pair.first;
+	}
+
+	return PlayerColor::CANNOT_DETERMINE;
+}
+
+bool LobbyInfo::isClientColor(int clientId, PlayerColor color) const
+{
+	if(si->playerInfos.find(color) != si->playerInfos.end())
+	{
+		for(ui8 id : si->playerInfos.find(color)->second.connectedPlayerIDs)
+		{
+			if(playerNames.find(id) != playerNames.end())
+			{
+				if(playerNames.find(id)->second.connection == clientId)
+					return true;
+			}
+		}
+	}
+	return false;
+}
+
+ui8 LobbyInfo::clientFirstId(int clientId) const
+{
+	for(auto & pair : playerNames)
+	{
+		if(pair.second.connection == clientId)
+			return pair.first;
+	}
+
+	return 0;
+}
+
+PlayerInfo & LobbyInfo::getPlayerInfo(int color)
+{
+	return mi->mapHeader->players[color];
+}
+
+TeamID LobbyInfo::getPlayerTeamId(PlayerColor color)
+{
+	if(color < PlayerColor::PLAYER_LIMIT)
+		return getPlayerInfo(color.getNum()).team;
+	else
+		return TeamID::NO_TEAM;
+}

+ 91 - 28
lib/StartInfo.h

@@ -13,9 +13,13 @@
 
 class CMapGenOptions;
 class CCampaignState;
+class CMapInfo;
+struct PlayerInfo;
+class PlayerColor;
+class SharedMemory;
 
 /// Struct which describes the name, the color, the starting bonus of a player
-struct PlayerSettings
+struct DLL_LINKAGE PlayerSettings
 {
 	enum { PLAYER_AI = 0 }; // for use in playerID
 
@@ -39,7 +43,7 @@ struct PlayerSettings
 	TeamID team;
 
 	std::string name;
-	ui8 playerID; //0 - AI, non-0 serves as player id
+	std::set<ui8> connectedPlayerIDs; //Empty - AI, or connectrd player ids
 	bool compOnly; //true if this player is a computer only player; required for RMG
 	template <typename Handler>
 	void serialize(Handler &h, const int version)
@@ -52,20 +56,30 @@ struct PlayerSettings
 		h & color;
 		h & handicap;
 		h & name;
-		h & playerID;
+		if(version < 787)
+		{
+			ui8 oldConnectedId = 0;
+			h & oldConnectedId;
+			if(oldConnectedId)
+			{
+				connectedPlayerIDs.insert(oldConnectedId);
+			}
+		}
+		else
+		{
+			h & connectedPlayerIDs;
+		}
 		h & team;
 		h & compOnly;
 	}
 
-	PlayerSettings() : bonus(RANDOM), castle(NONE), hero(RANDOM), heroPortrait(RANDOM),
-		color(0), handicap(NO_HANDICAP), team(0), playerID(PLAYER_AI), compOnly(false)
-	{
-
-	}
+	PlayerSettings();
+	bool isControlledByAI() const;
+	bool isControlledByHuman() const;
 };
 
 /// Struct which describes the difficulty, the turn time,.. of a heroes match.
-struct StartInfo
+struct DLL_LINKAGE StartInfo
 {
 	enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, INVALID = 255};
 
@@ -85,26 +99,12 @@ struct StartInfo
 
 	std::shared_ptr<CCampaignState> campState;
 
-	PlayerSettings & getIthPlayersSettings(PlayerColor no)
-	{
-		if(playerInfos.find(no) != playerInfos.end())
-			return playerInfos[no];
-		logGlobal->error("Cannot find info about player %s. Throwing...", no.getStr());
-		throw std::runtime_error("Cannot find info about player");
-	}
-	const PlayerSettings & getIthPlayersSettings(PlayerColor no) const
-	{
-		return const_cast<StartInfo&>(*this).getIthPlayersSettings(no);
-	}
-
-	PlayerSettings *getPlayersSettings(const ui8 nameID)
-	{
-		for(auto it=playerInfos.begin(); it != playerInfos.end(); ++it)
-			if(it->second.playerID == nameID)
-				return &it->second;
+	PlayerSettings & getIthPlayersSettings(PlayerColor no);
+	const PlayerSettings & getIthPlayersSettings(PlayerColor no) const;
+	PlayerSettings * getPlayersSettings(const ui8 connectedPlayerId);
 
-		return nullptr;
-	}
+	// TODO: Must be client-side
+	std::string getCampaignName() const;
 
 	template <typename Handler>
 	void serialize(Handler &h, const int version)
@@ -127,3 +127,66 @@ struct StartInfo
 
 	}
 };
+
+struct ClientPlayer
+{
+	int connection;
+	std::string name;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & connection;
+		h & name;
+	}
+};
+
+struct LobbyState
+{
+	std::shared_ptr<StartInfo> si;
+	std::shared_ptr<CMapInfo> mi;
+	std::map<ui8, ClientPlayer> playerNames; // id of player <-> player name; 0 is reserved as ID of AI "players"
+	int hostClientId;
+	// TODO: Campaign-only and we don't really need either of them.
+	// Before start both go into CCampaignState that is part of StartInfo
+	int campaignMap;
+	int campaignBonus;
+
+	LobbyState() : si(new StartInfo()), hostClientId(-1), campaignMap(-1), campaignBonus(-1) {}
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & si;
+		h & mi;
+		h & playerNames;
+		h & hostClientId;
+		h & campaignMap;
+		h & campaignBonus;
+	}
+};
+
+struct DLL_LINKAGE LobbyInfo : public LobbyState
+{
+	boost::mutex stateMutex;
+	std::string uuid;
+	std::shared_ptr<SharedMemory> shm;
+
+	LobbyInfo() {}
+
+	void verifyStateBeforeStart(bool ignoreNoHuman = false) const;
+
+	bool isClientHost(int clientId) const;
+	std::set<PlayerColor> getAllClientPlayers(int clientId);
+	std::vector<ui8> getConnectedPlayerIdsForClient(int clientId) const;
+
+	// Helpers for lobby state access
+	std::set<PlayerColor> clientHumanColors(int clientId);
+	PlayerColor clientFirstColor(int clientId) const;
+	bool isClientColor(int clientId, PlayerColor color) const;
+	ui8 clientFirstId(int clientId) const; // Used by chat only!
+	PlayerInfo & getPlayerInfo(int color);
+	TeamID getPlayerTeamId(PlayerColor color);
+};
+
+class ExceptionMapMissing : public std::exception {};
+class ExceptionNoHuman : public std::exception {};
+class ExceptionNoTemplate : public std::exception {};

+ 2 - 0
lib/mapObjects/CGHeroInstance.h

@@ -39,6 +39,8 @@ public:
 
 class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public spells::Caster
 {
+	// We serialize heroes into JSON for crossover
+	friend class CCampaignState;
 public:
 	//////////////////////////////////////////////////////////////////////////
 

+ 1 - 1
lib/mapObjects/CGPandoraBox.cpp

@@ -468,7 +468,7 @@ void CGEvent::onHeroVisit( const CGHeroInstance * h ) const
 {
 	if(!(availableFor & (1 << h->tempOwner.getNum())))
 		return;
-	if(cb->getPlayerSettings(h->tempOwner)->playerID)
+	if(cb->getPlayerSettings(h->tempOwner)->isControlledByHuman())
 	{
 		if(humanActivate)
 			activated(h);

+ 6 - 0
lib/mapObjects/CGTownInstance.cpp

@@ -1350,6 +1350,12 @@ void CGTownInstance::afterAddToMap(CMap * map)
 		map->towns.push_back(this);
 }
 
+void CGTownInstance::reset()
+{
+	CGTownInstance::merchantArtifacts.clear();
+	CGTownInstance::universitySkills.clear();
+}
+
 void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 {
 	CGObjectInstance::serializeJsonOwner(handler);

+ 1 - 0
lib/mapObjects/CGTownInstance.h

@@ -291,6 +291,7 @@ public:
 	std::string getObjectName() const override;
 
 	void afterAddToMap(CMap * map) override;
+	static void reset();
 protected:
 	void setPropertyDer(ui8 what, ui32 val) override;
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;

+ 107 - 13
lib/mapping/CCampaignHandler.cpp

@@ -23,8 +23,12 @@
 #include "../CHeroHandler.h"
 #include "CMapService.h"
 #include "CMap.h"
+#include "CMapInfo.h"
 
-namespace fs = boost::filesystem;
+// For hero crossover
+#include "serializer/CSerializer.h"
+#include "serializer/JsonDeserializer.h"
+#include "serializer/JsonSerializer.h"
 
 CCampaignHeader::CCampaignHeader()
 	: version(0), mapVersion(0), difficultyChoosenByPlayer(0), music(0), filename(), loadFromLod(0)
@@ -373,25 +377,42 @@ bool CCampaignScenario::isNotVoid() const
 	return mapName.size() > 0;
 }
 
-const CGHeroInstance * CCampaignScenario::strongestHero( PlayerColor owner ) const
+const CGHeroInstance * CCampaignScenario::strongestHero(PlayerColor owner)
 {
-	using boost::adaptors::filtered;
-	std::function<bool(CGHeroInstance*)> isOwned =  [=](const CGHeroInstance *h){ return h->tempOwner == owner; };
-	auto ownedHeroes = crossoverHeroes | filtered(isOwned);
+	std::function<bool(JsonNode & node)> isOwned = [owner](JsonNode & node)
+	{
+		auto h = CCampaignState::crossoverDeserialize(node);
+		bool result = h->tempOwner == owner;
+		vstd::clear_pointer(h);
+		return result;
+	};
+	auto ownedHeroes = crossoverHeroes | boost::adaptors::filtered(isOwned);
 
-	auto i = vstd::maxElementByFun(ownedHeroes,
-									[](const CGHeroInstance * h) {return h->getHeroStrength();});
-	return i == ownedHeroes.end() ? nullptr : *i;
+	auto i = vstd::maxElementByFun(ownedHeroes, [](JsonNode & node)
+	{
+		auto h = CCampaignState::crossoverDeserialize(node);
+		double result = h->getHeroStrength();
+		vstd::clear_pointer(h);
+		return result;
+	});
+	return i == ownedHeroes.end() ? nullptr : CCampaignState::crossoverDeserialize(*i);
 }
 
-std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes() const
+std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes()
 {
 	std::vector<CGHeroInstance *> lostCrossoverHeroes;
 	if(conquered)
 	{
-		for(auto hero : placedCrossoverHeroes)
+		for(auto node2 : placedCrossoverHeroes)
 		{
-			auto it = range::find_if(crossoverHeroes, CGObjectInstanceBySubIdFinder(hero));
+			auto hero = CCampaignState::crossoverDeserialize(node2);
+			auto it = range::find_if(crossoverHeroes, [hero](JsonNode node)
+			{
+				auto h = CCampaignState::crossoverDeserialize(node);
+				bool result = hero->subID == h->subID;
+				vstd::clear_pointer(h);
+				return result;
+			});
 			if(it == crossoverHeroes.end())
 			{
 				lostCrossoverHeroes.push_back(hero);
@@ -401,9 +422,25 @@ std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes() const
 	return lostCrossoverHeroes;
 }
 
-void CCampaignState::setCurrentMapAsConquered( const std::vector<CGHeroInstance*> & heroes )
+std::vector<JsonNode> CCampaignScenario::update787(std::vector<CGHeroInstance *> & heroes)
+{
+	static_assert(MINIMAL_SERIALIZATION_VERSION < 787, "No longer needed CCampaignScenario::update787");
+	std::vector<JsonNode> heroesNew;
+	for(auto hero : heroes)
+	{
+		heroesNew.push_back(CCampaignState::crossoverSerialize(hero));
+	}
+	return heroesNew;
+}
+
+void CCampaignState::setCurrentMapAsConquered(const std::vector<CGHeroInstance *> & heroes)
 {
-	camp->scenarios[*currentMap].crossoverHeroes = heroes;
+	camp->scenarios[*currentMap].crossoverHeroes.clear();
+	for(CGHeroInstance * hero : heroes)
+	{
+		camp->scenarios[*currentMap].crossoverHeroes.push_back(crossoverSerialize(hero));
+	}
+
 	mapsConquered.push_back(*currentMap);
 	mapsRemaining -= *currentMap;
 	camp->scenarios[*currentMap].conquered = true;
@@ -449,6 +486,63 @@ CCampaignState::CCampaignState( std::unique_ptr<CCampaign> _camp ) : camp(std::m
 	}
 }
 
+CMap * CCampaignState::getMap(int scenarioId) const
+{
+	// FIXME: there is certainly better way to handle maps inside campaigns
+	if(scenarioId == -1)
+		scenarioId = currentMap.get();
+	std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.'));
+	boost::to_lower(scenarioName);
+	scenarioName += ':' + boost::lexical_cast<std::string>(scenarioId);
+	std::string & mapContent = camp->mapPieces.find(scenarioId)->second;
+	auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
+	CMapService mapService;
+	return mapService.loadMap(buffer, mapContent.size(), scenarioName).release();
+}
+
+std::unique_ptr<CMapHeader> CCampaignState::getHeader(int scenarioId) const
+{
+	if(scenarioId == -1)
+		scenarioId = currentMap.get();
+
+	std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.'));
+	boost::to_lower(scenarioName);
+	scenarioName += ':' + boost::lexical_cast<std::string>(scenarioId);
+	std::string & mapContent = camp->mapPieces.find(scenarioId)->second;
+	auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
+	CMapService mapService;
+	return mapService.loadMapHeader(buffer, mapContent.size(), scenarioName);
+}
+
+std::shared_ptr<CMapInfo> CCampaignState::getMapInfo(int scenarioId) const
+{
+	if(scenarioId == -1)
+		scenarioId = currentMap.get();
+
+	auto mapInfo = std::make_shared<CMapInfo>();
+	mapInfo->fileURI = camp->header.filename;
+	mapInfo->mapHeader = getHeader(scenarioId);
+	mapInfo->countPlayers();
+	return mapInfo;
+}
+
+JsonNode CCampaignState::crossoverSerialize(CGHeroInstance * hero)
+{
+	JsonNode node;
+	JsonSerializer handler(nullptr, node);
+	hero->serializeJsonOptions(handler);
+	return node;
+}
+
+CGHeroInstance * CCampaignState::crossoverDeserialize(JsonNode & node)
+{
+	JsonDeserializer handler(nullptr, node);
+	auto hero = new CGHeroInstance();
+	hero->ID = Obj::HERO;
+	hero->serializeJsonOptions(handler);
+	return hero;
+}
+
 std::string CCampaignHandler::prologVideoName(ui8 index)
 {
 	JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));

+ 30 - 7
lib/mapping/CCampaignHandler.h

@@ -14,6 +14,10 @@
 struct StartInfo;
 class CGHeroInstance;
 class CBinaryReader;
+class CMap;
+class CMapHeader;
+class CMapInfo;
+class JsonNode;
 
 namespace CampaignVersion
 {
@@ -134,13 +138,15 @@ public:
 
 	CScenarioTravel travelOptions;
 	std::vector<HeroTypeID> keepHeroes; // contains list of heroes which should be kept for next scenario (doesn't matter if they lost)
-	std::vector<CGHeroInstance *> crossoverHeroes; // contains all heroes with the same state when the campaign scenario was finished
-	std::vector<CGHeroInstance *> placedCrossoverHeroes; // contains all placed crossover heroes defined by hero placeholders when the scenario was started
+	std::vector<JsonNode> crossoverHeroes; // contains all heroes with the same state when the campaign scenario was finished
+	std::vector<JsonNode> placedCrossoverHeroes; // contains all placed crossover heroes defined by hero placeholders when the scenario was started
 
-	const CGHeroInstance * strongestHero(PlayerColor owner) const;
 	void loadPreconditionRegions(ui32 regions);
 	bool isNotVoid() const;
-	std::vector<CGHeroInstance *> getLostCrossoverHeroes() const; /// returns a list of crossover heroes which started the scenario, but didn't complete it
+	// FIXME: due to usage of JsonNode I can't make these methods const
+	const CGHeroInstance * strongestHero(PlayerColor owner);
+	std::vector<CGHeroInstance *> getLostCrossoverHeroes(); /// returns a list of crossover heroes which started the scenario, but didn't complete it
+	std::vector<JsonNode> update787(std::vector<CGHeroInstance *> & heroes);
 
 	CCampaignScenario();
 
@@ -157,8 +163,19 @@ public:
 		h & prolog;
 		h & epilog;
 		h & travelOptions;
-		h & crossoverHeroes;
-		h & placedCrossoverHeroes;
+		if(formatVersion < 787)
+		{
+			std::vector<CGHeroInstance *> crossoverHeroesOld, placedCrossoverHeroesOld;
+			h & crossoverHeroesOld;
+			h & placedCrossoverHeroesOld;
+			crossoverHeroes = update787(crossoverHeroesOld);
+			placedCrossoverHeroes = update787(placedCrossoverHeroesOld);
+		}
+		else
+		{
+			h & crossoverHeroes;
+			h & placedCrossoverHeroes;
+		}
 		h & keepHeroes;
 	}
 };
@@ -192,13 +209,19 @@ public:
 
 	std::map<ui8, ui8> chosenCampaignBonuses;
 
-	//void initNewCampaign(const StartInfo &si);
 	void setCurrentMapAsConquered(const std::vector<CGHeroInstance*> & heroes);
 	boost::optional<CScenarioTravel::STravelBonus> getBonusForCurrentMap() const;
 	const CCampaignScenario & getCurrentScenario() const;
 	CCampaignScenario & getCurrentScenario();
 	ui8 currentBonusID() const;
 
+	CMap * getMap(int scenarioId = -1) const;
+	std::unique_ptr<CMapHeader> getHeader(int scenarioId = -1) const;
+	std::shared_ptr<CMapInfo> getMapInfo(int scenarioId = -1) const;
+
+	static JsonNode crossoverSerialize(CGHeroInstance * hero);
+	static CGHeroInstance * crossoverDeserialize(JsonNode & node);
+
 	CCampaignState();
 	CCampaignState(std::unique_ptr<CCampaign> _camp);
 	~CCampaignState(){};

+ 10 - 5
lib/mapping/CMap.cpp

@@ -184,11 +184,6 @@ bool TerrainTile::isWater() const
 	return terType == ETerrainType::WATER;
 }
 
-const int CMapHeader::MAP_SIZE_SMALL = 36;
-const int CMapHeader::MAP_SIZE_MIDDLE = 72;
-const int CMapHeader::MAP_SIZE_LARGE = 108;
-const int CMapHeader::MAP_SIZE_XLARGE = 144;
-
 void CMapHeader::setupEvents()
 {
 	EventCondition victoryCondition(EventCondition::STANDARD_WIN);
@@ -269,6 +264,8 @@ CMap::~CMap()
 
 	for(auto quest : quests)
 		quest.dellNull();
+
+	resetStaticData();
 }
 
 void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
@@ -647,3 +644,11 @@ CMapEditManager * CMap::getEditManager()
 	if(!editManager) editManager = make_unique<CMapEditManager>(this);
 	return editManager.get();
 }
+
+void CMap::resetStaticData()
+{
+	CGKeys::reset();
+	CGMagi::reset();
+	CGObelisk::reset();
+	CGTownInstance::reset();
+}

+ 7 - 4
lib/mapping/CMap.h

@@ -288,10 +288,11 @@ class DLL_LINKAGE CMapHeader
 {
 	void setupEvents();
 public:
-	static const int MAP_SIZE_SMALL;
-	static const int MAP_SIZE_MIDDLE;
-	static const int MAP_SIZE_LARGE;
-	static const int MAP_SIZE_XLARGE;
+
+	static const int MAP_SIZE_SMALL = 36;
+	static const int MAP_SIZE_MIDDLE = 72;
+	static const int MAP_SIZE_LARGE = 108;
+	static const int MAP_SIZE_XLARGE = 144;
 
 	CMapHeader();
 	virtual ~CMapHeader();
@@ -382,6 +383,8 @@ public:
 	/// Sets the victory/loss condition objectives ??
 	void checkForObjectives();
 
+	void resetStaticData();
+
 	ui32 checksum;
 	std::vector<Rumor> rumors;
 	std::vector<DisposedHero> disposedHeroes;

+ 136 - 47
lib/mapping/CMapInfo.cpp

@@ -15,81 +15,170 @@
 #include "../GameConstants.h"
 #include "CMapService.h"
 
+#include "../filesystem/Filesystem.h"
+#include "../serializer/CMemorySerializer.h"
+#include "../CGeneralTextHandler.h"
+#include "../rmg/CMapGenOptions.h"
+#include "../CCreatureHandler.h"
+#include "../CHeroHandler.h"
+#include "../CModHandler.h"
+
+CMapInfo::CMapInfo()
+	: scenarioOptionsOfSave(nullptr), amountOfPlayersOnMap(0), amountOfHumanControllablePlayers(0),	amountOfHumanPlayersInSave(0), isRandomMap(false)
+{
+
+}
+
+CMapInfo::~CMapInfo()
+{
+	vstd::clear_pointer(scenarioOptionsOfSave);
+}
+
+void CMapInfo::mapInit(const std::string & fname)
+{
+	fileURI = fname;
+	CMapService mapService;
+	mapHeader = mapService.loadMapHeader(ResourceID(fname, EResType::MAP));
+	countPlayers();
+}
+
+void CMapInfo::saveInit(ResourceID file)
+{
+	CLoadFile lf(*CResourceHandler::get()->getResourceName(file), MINIMAL_SERIALIZATION_VERSION);
+	lf.checkMagicBytes(SAVEGAME_MAGIC);
+
+	mapHeader = make_unique<CMapHeader>();
+	lf >> *(mapHeader.get()) >> scenarioOptionsOfSave;
+	fileURI = file.getName();
+	countPlayers();
+	std::time_t time = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(file));
+	date = std::asctime(std::localtime(&time));
+	// We absolutely not need this data for lobby and server will read it from save
+	// FIXME: actually we don't want them in CMapHeader!
+	mapHeader->triggeredEvents.clear();
+}
+
+void CMapInfo::campaignInit()
+{
+	campaignHeader = std::unique_ptr<CCampaignHeader>(new CCampaignHeader(CCampaignHandler::getHeader(fileURI)));
+}
+
 void CMapInfo::countPlayers()
 {
-	actualHumanPlayers = playerAmnt = humanPlayers = 0;
 	for(int i=0; i<PlayerColor::PLAYER_LIMIT_I; i++)
 	{
 		if(mapHeader->players[i].canHumanPlay)
 		{
-			playerAmnt++;
-			humanPlayers++;
+			amountOfPlayersOnMap++;
+			amountOfHumanControllablePlayers++;
 		}
 		else if(mapHeader->players[i].canComputerPlay)
 		{
-			playerAmnt++;
+			amountOfPlayersOnMap++;
 		}
 	}
 
-	if(scenarioOpts)
-		for (auto i = scenarioOpts->playerInfos.cbegin(); i != scenarioOpts->playerInfos.cend(); i++)
-			if(i->second.playerID != PlayerSettings::PLAYER_AI)
-				actualHumanPlayers++;
+	if(scenarioOptionsOfSave)
+		for (auto i = scenarioOptionsOfSave->playerInfos.cbegin(); i != scenarioOptionsOfSave->playerInfos.cend(); i++)
+			if(i->second.isControlledByHuman())
+				amountOfHumanPlayersInSave++;
 }
 
-CMapInfo::CMapInfo() : scenarioOpts(nullptr), playerAmnt(0), humanPlayers(0),
-	actualHumanPlayers(0), isRandomMap(false)
+std::string CMapInfo::getName() const
 {
-
+	if(campaignHeader && campaignHeader->name.length())
+		return campaignHeader->name;
+	else if(mapHeader && mapHeader->name.length())
+		return mapHeader->name;
+	else
+		return VLC->generaltexth->allTexts[508];
 }
 
-#define STEAL(x) x = std::move(tmp.x)
-
-CMapInfo::CMapInfo(CMapInfo && tmp):
-	scenarioOpts(nullptr), playerAmnt(0), humanPlayers(0),
-	actualHumanPlayers(0), isRandomMap(false)
+std::string CMapInfo::getNameForList() const
 {
-	std::swap(scenarioOpts, tmp.scenarioOpts);
-	STEAL(mapHeader);
-	STEAL(campaignHeader);
-	STEAL(fileURI);
-	STEAL(date);
-	STEAL(playerAmnt);
-	STEAL(humanPlayers);
-	STEAL(actualHumanPlayers);
-	STEAL(isRandomMap);
+	if(scenarioOptionsOfSave)
+	{
+		// TODO: this could be handled differently
+		std::vector<std::string> path;
+		boost::split(path, fileURI, boost::is_any_of("\\/"));
+		return path[path.size()-1];
+	}
+	else
+	{
+		return getName();
+	}
 }
 
-CMapInfo::~CMapInfo()
+std::string CMapInfo::getDescription() const
 {
-	vstd::clear_pointer(scenarioOpts);
+	if(campaignHeader)
+		return campaignHeader->description;
+	else
+		return mapHeader->description;
 }
 
-void CMapInfo::mapInit(const std::string & fname)
+int CMapInfo::getMapSizeIconId() const
 {
-	fileURI = fname;
-	CMapService mapService;
-	mapHeader = mapService.loadMapHeader(ResourceID(fname, EResType::MAP));
-	countPlayers();
+	if(!mapHeader)
+		return 4;
+
+	switch(mapHeader->width)
+	{
+	case CMapHeader::MAP_SIZE_SMALL:
+		return 0;
+	case CMapHeader::MAP_SIZE_MIDDLE:
+		return 1;
+	case CMapHeader::MAP_SIZE_LARGE:
+		return 2;
+	case CMapHeader::MAP_SIZE_XLARGE:
+		return 3;
+	default:
+		return 4;
+	}
 }
 
-void CMapInfo::campaignInit()
+std::pair<int, int> CMapInfo::getMapSizeFormatIconId() const
 {
-	campaignHeader = std::unique_ptr<CCampaignHeader>(new CCampaignHeader(CCampaignHandler::getHeader(fileURI)));
+	int frame = -1, group = 0;
+	switch(mapHeader->version)
+	{
+	case EMapFormat::ROE:
+		frame = 0;
+		break;
+	case EMapFormat::AB:
+		frame = 1;
+		break;
+	case EMapFormat::SOD:
+		frame = 2;
+		break;
+	case EMapFormat::WOG:
+		frame = 3;
+		break;
+	case EMapFormat::VCMI:
+		frame = 0;
+		group = 1;
+		break;
+	default:
+		// Unknown version. Be safe and ignore that map
+		//logGlobal->warn("Warning: %s has wrong version!", currentItem->fileURI);
+		break;
+	}
+	return std::make_pair(frame, group);
 }
 
-CMapInfo & CMapInfo::operator=(CMapInfo &&tmp)
+std::string CMapInfo::getMapSizeName() const
 {
-	STEAL(mapHeader);
-	STEAL(campaignHeader);
-	STEAL(scenarioOpts);
-	STEAL(fileURI);
-	STEAL(date);
-	STEAL(playerAmnt);
-	STEAL(humanPlayers);
-	STEAL(actualHumanPlayers);
-	STEAL(isRandomMap);
-	return *this;
+	switch(mapHeader->width)
+	{
+	case CMapHeader::MAP_SIZE_SMALL:
+		return "S";
+	case CMapHeader::MAP_SIZE_MIDDLE:
+		return "M";
+	case CMapHeader::MAP_SIZE_LARGE:
+		return "L";
+	case CMapHeader::MAP_SIZE_XLARGE:
+		return "XL";
+	default:
+		return "C";
+	}
 }
-
-#undef STEAL

+ 17 - 10
lib/mapping/CMapInfo.h

@@ -31,34 +31,41 @@ class DLL_LINKAGE CMapInfo
 public:
 	std::unique_ptr<CMapHeader> mapHeader; //may be nullptr if campaign
 	std::unique_ptr<CCampaignHeader> campaignHeader; //may be nullptr if scenario
-	StartInfo * scenarioOpts; //options with which scenario has been started (used only with saved games)
+	StartInfo * scenarioOptionsOfSave; // Options with which scenario has been started (used only with saved games)
 	std::string fileURI;
 	std::string date;
-	int playerAmnt; //players in map
-	int humanPlayers; //players ALLOWED to be controlled by human
-	int actualHumanPlayers; // >1 if multiplayer game
-	bool isRandomMap; // true if the map will be created randomly, false if not
+	int amountOfPlayersOnMap;
+	int amountOfHumanControllablePlayers;
+	int amountOfHumanPlayersInSave;
+	bool isRandomMap;
 
 	CMapInfo();
-	CMapInfo(CMapInfo && tmp);
 	virtual ~CMapInfo();
 
 	CMapInfo &operator=(CMapInfo &&other);
 
 	void mapInit(const std::string & fname);
+	void saveInit(ResourceID file);
 	void campaignInit();
 	void countPlayers();
+	// TODO: Those must be on client-side
+	std::string getName() const;
+	std::string getNameForList() const;
+	std::string getDescription() const;
+	int getMapSizeIconId() const;
+	std::pair<int, int> getMapSizeFormatIconId() const;
+	std::string getMapSizeName() const;
 
 	template <typename Handler> void serialize(Handler &h, const int Version)
 	{
 		h & mapHeader;
 		h & campaignHeader;
-		h & scenarioOpts;
+		h & scenarioOptionsOfSave;
 		h & fileURI;
 		h & date;
-		h & playerAmnt;
-		h & humanPlayers;
-		h & actualHumanPlayers;
+		h & amountOfPlayersOnMap;
+		h & amountOfHumanControllablePlayers;
+		h & amountOfHumanPlayersInSave;
 		h & isRandomMap;
 	}
 };

+ 1 - 1
lib/registerTypes/RegisterTypes.cpp

@@ -44,7 +44,7 @@ DEFINE_EXTERNAL_METHOD(registerTypesMapObjects2)
 DEFINE_EXTERNAL_METHOD(registerTypesClientPacks1)
 DEFINE_EXTERNAL_METHOD(registerTypesClientPacks2)
 DEFINE_EXTERNAL_METHOD(registerTypesServerPacks)
-DEFINE_EXTERNAL_METHOD(registerTypesPregamePacks)
+DEFINE_EXTERNAL_METHOD(registerTypesLobbyPacks)
 
 template void registerTypes<BinaryDeserializer>(BinaryDeserializer & s);
 template void registerTypes<BinarySerializer>(BinarySerializer & s);

+ 31 - 23
lib/registerTypes/RegisterTypes.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../NetPacks.h"
+#include "../NetPacksLobby.h"
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CPlayerState.h"
@@ -234,8 +235,6 @@ void registerTypesClientPacks1(Serializer &s)
 	s.template registerType<CPackForClient, ChangeObjPos>();
 	s.template registerType<CPackForClient, PlayerEndsGame>();
 	s.template registerType<CPackForClient, RemoveBonus>();
-	s.template registerType<CPackForClient, UpdateCampaignState>();
-	s.template registerType<CPackForClient, PrepareForAdvancingCampaign>();
 	s.template registerType<CPackForClient, UpdateArtHandlerLists>();
 	s.template registerType<CPackForClient, UpdateMapEvents>();
 	s.template registerType<CPackForClient, UpdateCastleEvents>();
@@ -312,16 +311,14 @@ void registerTypesClientPacks2(Serializer &s)
 	s.template registerType<CArtifactOperationPack, AssembledArtifact>();
 	s.template registerType<CArtifactOperationPack, DisassembledArtifact>();
 
-	s.template registerType<CPackForClient, SaveGame>();
-	s.template registerType<CPackForClient, PlayerMessage>();
+	s.template registerType<CPackForClient, SaveGameClient>();
+	s.template registerType<CPackForClient, PlayerMessageClient>();
 }
 
 template<typename Serializer>
 void registerTypesServerPacks(Serializer &s)
 {
 	s.template registerType<CPack, CPackForServer>();
-	s.template registerType<CPackForServer, CloseServer>();
-	s.template registerType<CPackForServer, LeaveGame>();
 	s.template registerType<CPackForServer, EndTurn>();
 	s.template registerType<CPackForServer, DismissHero>();
 	s.template registerType<CPackForServer, MoveHero>();
@@ -351,23 +348,34 @@ void registerTypesServerPacks(Serializer &s)
 }
 
 template<typename Serializer>
-void registerTypesPregamePacks(Serializer &s)
+void registerTypesLobbyPacks(Serializer &s)
 {
-	s.template registerType<CPack, CPackForSelectionScreen>();
-	s.template registerType<CPackForSelectionScreen, CPregamePackToPropagate>();
-	s.template registerType<CPackForSelectionScreen, CPregamePackToHost>();
-
-	s.template registerType<CPregamePackToPropagate, ChatMessage>();
-	s.template registerType<CPregamePackToPropagate, QuitMenuWithoutStarting>();
-	s.template registerType<CPregamePackToPropagate, SelectMap>();
-	s.template registerType<CPregamePackToPropagate, UpdateStartOptions>();
-	s.template registerType<CPregamePackToPropagate, PregameGuiAction>();
-	s.template registerType<CPregamePackToPropagate, PlayerLeft>();
-	s.template registerType<CPregamePackToPropagate, PlayersNames>();
-	s.template registerType<CPregamePackToPropagate, StartWithCurrentSettings>();
-
-	s.template registerType<CPregamePackToHost, PlayerJoined>();
-	s.template registerType<CPregamePackToHost, RequestOptionsChange>();
+	s.template registerType<CPack, CPackForLobby>();
+	s.template registerType<CPackForLobby, CLobbyPackToPropagate>();
+	s.template registerType<CPackForLobby, CLobbyPackToServer>();
+
+	// Any client can sent
+	s.template registerType<CLobbyPackToPropagate, LobbyClientConnected>();
+	s.template registerType<CLobbyPackToPropagate, LobbyClientDisconnected>();
+	s.template registerType<CLobbyPackToPropagate, LobbyChatMessage>();
+	// Only host client send
+	s.template registerType<CLobbyPackToPropagate, LobbyGuiAction>();
+	s.template registerType<CLobbyPackToPropagate, LobbyStartGame>();
+	s.template registerType<CLobbyPackToPropagate, LobbyChangeHost>();
+	// Only server send
+	s.template registerType<CLobbyPackToPropagate, LobbyUpdateState>();
+
+	// For client with permissions
+	s.template registerType<CLobbyPackToServer, LobbyChangePlayerOption>();
+	// Only for host client
+	s.template registerType<CLobbyPackToServer, LobbySetMap>();
+	s.template registerType<CLobbyPackToServer, LobbySetCampaign>();
+	s.template registerType<CLobbyPackToServer, LobbySetCampaignMap>();
+	s.template registerType<CLobbyPackToServer, LobbySetCampaignBonus>();
+	s.template registerType<CLobbyPackToServer, LobbySetPlayer>();
+	s.template registerType<CLobbyPackToServer, LobbySetTurnTime>();
+	s.template registerType<CLobbyPackToServer, LobbySetDifficulty>();
+	s.template registerType<CLobbyPackToServer, LobbyForceSetPlayer>();
 }
 
 template<typename Serializer>
@@ -379,7 +387,7 @@ void registerTypes(Serializer &s)
 	registerTypesClientPacks1(s);
 	registerTypesClientPacks2(s);
 	registerTypesServerPacks(s);
-	registerTypesPregamePacks(s);
+	registerTypesLobbyPacks(s);
 }
 
 #ifndef INSTANTIATE_REGISTER_TYPES_HERE

+ 37 - 37
lib/registerTypes/TypesPregamePacks.cpp → lib/registerTypes/TypesLobbyPacks.cpp

@@ -1,37 +1,37 @@
-/*
- * TypesPregamePacks.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 "RegisterTypes.h"
-
-#include "../mapping/CMapInfo.h"
-#include "../StartInfo.h"
-#include "../CGameState.h"
-#include "../mapping/CMap.h"
-#include "../CModHandler.h"
-#include "../mapObjects/CObjectHandler.h"
-#include "../CCreatureHandler.h"
-#include "../VCMI_Lib.h"
-#include "../CArtHandler.h"
-#include "../CHeroHandler.h"
-#include "../spells/CSpellHandler.h"
-#include "../CTownHandler.h"
-#include "../mapping/CCampaignHandler.h"
-#include "../NetPacks.h"
-#include "../mapObjects/CObjectClassesHandler.h"
-#include "../rmg/CMapGenOptions.h"
-
-#include "../serializer/BinaryDeserializer.h"
-#include "../serializer/BinarySerializer.h"
-#include "../serializer/CTypeList.h"
-
-template void registerTypesPregamePacks<BinaryDeserializer>(BinaryDeserializer & s);
-template void registerTypesPregamePacks<BinarySerializer>(BinarySerializer & s);
-template void registerTypesPregamePacks<CTypeList>(CTypeList & s);
-
+/*
+ * TypesLobbyPacks.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 "RegisterTypes.h"
+
+#include "../mapping/CMapInfo.h"
+#include "../StartInfo.h"
+#include "../CGameState.h"
+#include "../mapping/CMap.h"
+#include "../CModHandler.h"
+#include "../mapObjects/CObjectHandler.h"
+#include "../CCreatureHandler.h"
+#include "../VCMI_Lib.h"
+#include "../CArtHandler.h"
+#include "../CHeroHandler.h"
+#include "../spells/CSpellHandler.h"
+#include "../CTownHandler.h"
+#include "../mapping/CCampaignHandler.h"
+#include "../NetPacks.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "../rmg/CMapGenOptions.h"
+
+#include "../serializer/BinaryDeserializer.h"
+#include "../serializer/BinarySerializer.h"
+#include "../serializer/CTypeList.h"
+
+template void registerTypesLobbyPacks<BinaryDeserializer>(BinaryDeserializer & s);
+template void registerTypesLobbyPacks<BinarySerializer>(BinarySerializer & s);
+template void registerTypesLobbyPacks<CTypeList>(CTypeList & s);
+

+ 2 - 1
lib/rmg/CMapGenOptions.cpp

@@ -17,7 +17,8 @@
 #include "../VCMI_Lib.h"
 #include "../CTownHandler.h"
 
-CMapGenOptions::CMapGenOptions() : width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), hasTwoLevels(false),
+CMapGenOptions::CMapGenOptions()
+	: width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), hasTwoLevels(true),
 	playerCount(RANDOM_SIZE), teamCount(RANDOM_SIZE), compOnlyPlayerCount(RANDOM_SIZE), compOnlyTeamCount(RANDOM_SIZE), humanPlayersCount(0),
 	waterContent(EWaterContent::RANDOM), monsterStrength(EMonsterStrength::RANDOM), mapTemplate(nullptr)
 {

+ 1 - 1
lib/serializer/CSerializer.h

@@ -12,7 +12,7 @@
 #include "../ConstTransitivePtr.h"
 #include "../GameConstants.h"
 
-const ui32 SERIALIZATION_VERSION = 786;
+const ui32 SERIALIZATION_VERSION = 787;
 const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
 const std::string SAVEGAME_MAGIC = "VCMISVG";
 

+ 43 - 53
lib/serializer/Connection.cpp

@@ -35,8 +35,9 @@ using namespace boost::asio::ip;
 
 void CConnection::init()
 {
-	boost::asio::ip::tcp::no_delay option(true);
-	socket->set_option(option);
+	socket->set_option(boost::asio::ip::tcp::no_delay(true));
+	socket->set_option(boost::asio::socket_base::send_buffer_size(4194304));
+	socket->set_option(boost::asio::socket_base::receive_buffer_size(4194304));
 
 	enableSmartPointerSerialization();
 	disableStackSendingByID();
@@ -50,25 +51,21 @@ void CConnection::init()
 	connected = true;
 	std::string pom;
 	//we got connection
-	oser & std::string("Aiya!\n") & name & myEndianess; //identify ourselves
-	iser & pom & pom & contactEndianess;
-	logNetwork->info("Established connection with %s", pom);
-	wmx = new boost::mutex();
-	rmx = new boost::mutex();
+	oser & std::string("Aiya!\n") & name & uuid & myEndianess; //identify ourselves
+	iser & pom & pom & contactUuid & contactEndianess;
+	logNetwork->info("Established connection with %s. UUID: %s", pom, contactUuid);
+	mutexRead = std::make_shared<boost::mutex>();
+	mutexWrite = std::make_shared<boost::mutex>();
 
-	handler = nullptr;
-	receivedStop = sendStop = false;
-	static int cid = 1;
-	connectionID = cid++;
 	iser.fileVersion = SERIALIZATION_VERSION;
 }
 
-CConnection::CConnection(std::string host, ui16 port, std::string Name)
-:iser(this), oser(this), io_service(new asio::io_service), name(Name)
+CConnection::CConnection(std::string host, ui16 port, std::string Name, std::string UUID)
+	: iser(this), oser(this), io_service(std::make_shared<asio::io_service>()), connectionID(0), name(Name), uuid(UUID)
 {
 	int i;
 	boost::system::error_code error = asio::error::host_not_found;
-	socket = new tcp::socket(*io_service);
+	socket = std::make_shared<tcp::socket>(*io_service);
 	tcp::resolver resolver(*io_service);
 	tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, std::to_string(port)),error);
 	if(error)
@@ -114,25 +111,23 @@ connerror1:
 		logNetwork->error(error.message());
 	else
 		logNetwork->error("No error info. ");
-	delete io_service;
-	//delete socket;
 	throw std::runtime_error("Can't establish connection :(");
 }
-CConnection::CConnection(TSocket * Socket, std::string Name )
-	:iser(this), oser(this), socket(Socket),io_service(&Socket->get_io_service()), name(Name)//, send(this), rec(this)
+CConnection::CConnection(std::shared_ptr<TSocket> Socket, std::string Name, std::string UUID)
+	: iser(this), oser(this), socket(Socket), io_service(&Socket->get_io_service()), connectionID(0), name(Name), uuid(UUID)
 {
 	init();
 }
-CConnection::CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name)
-: iser(this), oser(this), name(Name)//, send(this), rec(this)
+CConnection::CConnection(std::shared_ptr<TAcceptor> acceptor, std::shared_ptr<boost::asio::io_service> Io_service, std::string Name, std::string UUID)
+	: iser(this), oser(this), connectionID(0), name(Name), uuid(UUID)
 {
 	boost::system::error_code error = asio::error::host_not_found;
-	socket = new tcp::socket(*io_service);
+	socket = std::make_shared<tcp::socket>(*io_service);
 	acceptor->accept(*socket,error);
 	if (error)
 	{
 		logNetwork->error("Error on accepting: %s", error.message());
-		delete socket;
+		socket.reset();
 		throw std::runtime_error("Can't establish connection :(");
 	}
 	init();
@@ -171,12 +166,7 @@ CConnection::~CConnection()
 	if(handler)
 		handler->join();
 
-	delete handler;
-
 	close();
-	delete io_service;
-	delete wmx;
-	delete rmx;
 }
 
 template<class T>
@@ -193,7 +183,7 @@ void CConnection::close()
 	if(socket)
 	{
 		socket->close();
-		vstd::clear_pointer(socket);
+		socket.reset();
 	}
 }
 
@@ -202,11 +192,6 @@ bool CConnection::isOpen() const
 	return socket && connected;
 }
 
-bool CConnection::isHost() const
-{
-	return connectionID == 1;
-}
-
 void CConnection::reportState(vstd::CLoggerBase * out)
 {
 	out->debug("CConnection");
@@ -219,19 +204,26 @@ void CConnection::reportState(vstd::CLoggerBase * out)
 
 CPack * CConnection::retrievePack()
 {
-	CPack *ret = nullptr;
-	boost::unique_lock<boost::mutex> lock(*rmx);
-	logNetwork->trace("Listening... ");
-	iser & ret;
-	logNetwork->trace("\treceived server message of type %s", (ret? typeid(*ret).name() : "nullptr"));
-	return ret;
+	CPack * pack = nullptr;
+	boost::unique_lock<boost::mutex> lock(*mutexRead);
+	iser & pack;
+	logNetwork->trace("\treceived CPack of type %s", (pack ? typeid(*pack).name() : "nullptr"));
+	if(pack == nullptr)
+	{
+		logNetwork->error("Received a nullptr CPack! You should check whether client and server ABI matches.");
+	}
+	else
+	{
+		pack->c = this->shared_from_this();
+	}
+	return pack;
 }
 
-void CConnection::sendPackToServer(const CPack &pack, PlayerColor player, ui32 requestID)
+void CConnection::sendPack(const CPack * pack)
 {
-	boost::unique_lock<boost::mutex> lock(*wmx);
-	logNetwork->trace("Sending to server a pack of type %s", typeid(pack).name());
-	oser & player & requestID & &pack; //packs has to be sent as polymorphic pointers!
+	boost::unique_lock<boost::mutex> lock(*mutexWrite);
+	logNetwork->trace("Sending a pack of type %s", typeid(*pack).name());
+	oser & pack;
 }
 
 void CConnection::disableStackSendingByID()
@@ -254,21 +246,19 @@ void CConnection::enableSmartPointerSerialization()
 	iser.smartPointerSerialization = oser.smartPointerSerialization = true;
 }
 
-void CConnection::prepareForSendingHeroes()
+void CConnection::enterLobbyConnectionMode()
 {
 	iser.loadedPointers.clear();
 	oser.savedPointers.clear();
 	disableSmartVectorMemberSerialization();
-	enableSmartPointerSerialization();
-	disableStackSendingByID();
+	disableSmartPointerSerialization();
 }
 
-void CConnection::enterPregameConnectionMode()
+void CConnection::enterGameplayConnectionMode(CGameState * gs)
 {
-	iser.loadedPointers.clear();
-	oser.savedPointers.clear();
-	disableSmartVectorMemberSerialization();
+	enableStackSendingByID();
 	disableSmartPointerSerialization();
+	addStdVecItems(gs);
 }
 
 void CConnection::disableSmartVectorMemberSerialization()
@@ -283,7 +273,7 @@ void CConnection::enableSmartVectorMemberSerializatoin()
 
 std::string CConnection::toString() const
 {
-    boost::format fmt("Connection with %s (ID: %d)");
-    fmt % name % connectionID;
-    return fmt.str();
+	boost::format fmt("Connection with %s (ID: %d UUID: %s)");
+	fmt % name % connectionID % uuid;
+	return fmt.str();
 }

+ 15 - 15
lib/serializer/Connection.h

@@ -47,7 +47,7 @@ typedef boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::so
 /// Main class for network communication
 /// Allows establishing connection and bidirectional read-write
 class DLL_LINKAGE CConnection
-	: public IBinaryReader, public IBinaryWriter
+	: public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this<CConnection>
 {
 	CConnection();
 
@@ -60,31 +60,31 @@ public:
 	BinaryDeserializer iser;
 	BinarySerializer oser;
 
-	boost::mutex *rmx, *wmx; // read/write mutexes
-	TSocket * socket;
+	std::shared_ptr<boost::mutex> mutexRead;
+	std::shared_ptr<boost::mutex> mutexWrite;
+	std::shared_ptr<TSocket> socket;
 	bool connected;
 	bool myEndianess, contactEndianess; //true if little endian, if endianness is different we'll have to revert received multi-byte vars
-	boost::asio::io_service *io_service;
+	std::string contactUuid;
+	std::shared_ptr<boost::asio::io_service> io_service;
 	std::string name; //who uses this connection
+	std::string uuid;
 
 	int connectionID;
-	boost::thread *handler;
+	std::shared_ptr<boost::thread> handler;
 
-	bool receivedStop, sendStop;
-
-	CConnection(std::string host, ui16 port, std::string Name);
-	CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name);
-	CConnection(TSocket * Socket, std::string Name); //use immediately after accepting connection into socket
+	CConnection(std::string host, ui16 port, std::string Name, std::string UUID);
+	CConnection(std::shared_ptr<TAcceptor> acceptor, std::shared_ptr<boost::asio::io_service> Io_service, std::string Name, std::string UUID);
+	CConnection(std::shared_ptr<TSocket> Socket, std::string Name, std::string UUID); //use immediately after accepting connection into socket
 
 	void close();
 	bool isOpen() const;
-	bool isHost() const;
 	template<class T>
 	CConnection &operator&(const T&);
 	virtual ~CConnection();
 
-	CPack * retrievePack(); //gets from server next pack (allocates it with new)
-	void sendPackToServer(const CPack &pack, PlayerColor player, ui32 requestID);
+	CPack * retrievePack();
+	void sendPack(const CPack * pack);
 
 	void disableStackSendingByID();
 	void enableStackSendingByID();
@@ -93,8 +93,8 @@ public:
 	void disableSmartVectorMemberSerialization();
 	void enableSmartVectorMemberSerializatoin();
 
-	void prepareForSendingHeroes(); //disables sending vectorized, enables smart pointer serialization, clears saved/loaded ptr cache
-	void enterPregameConnectionMode();
+	void enterLobbyConnectionMode();
+	void enterGameplayConnectionMode(CGameState * gs);
 
 	std::string toString() const;
 

+ 81 - 211
server/CGameHandler.cpp

@@ -50,7 +50,6 @@
 #ifndef _MSC_VER
 #include <boost/thread/xtime.hpp>
 #endif
-extern std::atomic<bool> serverShuttingDown;
 
 #define COMPLAIN_RET_IF(cond, txt) do {if (cond){complain(txt); return;}} while(0)
 #define COMPLAIN_RET_FALSE_IF(cond, txt) do {if (cond){complain(txt); return false;}} while(0)
@@ -177,7 +176,7 @@ template <typename T> class CApplyOnGH;
 class CBaseForGHApply
 {
 public:
-	virtual bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, PlayerColor player) const =0;
+	virtual bool applyOnGH(CGameHandler * gh, void * pack) const =0;
 	virtual ~CBaseForGHApply(){}
 	template<typename U> static CBaseForGHApply *getApplier(const U * t=nullptr)
 	{
@@ -188,11 +187,9 @@ public:
 template <typename T> class CApplyOnGH : public CBaseForGHApply
 {
 public:
-	bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, PlayerColor player) const override
+	bool applyOnGH(CGameHandler * gh, void * pack) const override
 	{
 		T *ptr = static_cast<T*>(pack);
-		ptr->c = c;
-		ptr->player = player;
 		try
 		{
 			return ptr->applyGh(gh);
@@ -212,7 +209,7 @@ template <>
 class CApplyOnGH<CPack> : public CBaseForGHApply
 {
 public:
-	bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, PlayerColor player) const override
+	bool applyOnGH(CGameHandler * gh, void * pack) const override
 	{
 		logGlobal->error("Cannot apply on GH plain CPack!");
 		assert(0);
@@ -220,8 +217,6 @@ public:
 	}
 };
 
-static CApplier<CBaseForGHApply> *applier = nullptr;
-
 static inline double distance(int3 a, int3 b)
 {
 	return std::sqrt((double)(a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
@@ -1194,114 +1189,64 @@ void CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr<battle
 		fireShield.push_back(std::make_pair(def, fireShieldDamage));
 	}
 }
-void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &c)
-{
-	setThreadName("CGameHandler::handleConnection");
 
-	auto handleDisconnection = [&](const std::exception & e)
+void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
+{
+	for(auto playerConns : connections)
 	{
-		boost::unique_lock<boost::mutex> lock(*c.wmx);
-		assert(!c.connected); //make sure that connection has been marked as broken
-		logGlobal->error(e.what());
-		conns -= &c;
-		for(auto playerConn : connections)
+		for(auto conn : playerConns.second)
 		{
-			if(!serverShuttingDown && playerConn.second == &c)
+			if(lobby->state != EServerState::SHUTDOWN && conn == c)
 			{
+				vstd::erase_if_present(playerConns.second, conn);
+				if(playerConns.second.size())
+					continue;
 				PlayerCheated pc;
-				pc.player = playerConn.first;
+				pc.player = playerConns.first;
 				pc.losingCheatCode = true;
 				sendAndApply(&pc);
-				checkVictoryLossConditionsForPlayer(playerConn.first);
+				checkVictoryLossConditionsForPlayer(playerConns.first);
 			}
 		}
-	};
+	}
+}
 
-	try
+void CGameHandler::handleReceivedPack(CPackForServer * pack)
+{
+	//prepare struct informing that action was applied
+	auto sendPackageResponse = [&](bool succesfullyApplied)
 	{
-		while(1)//server should never shut connection first //was: while(!end2)
-		{
-			CPack *pack = nullptr;
-			PlayerColor player = PlayerColor::NEUTRAL;
-			si32 requestID = -999;
-			int packType = 0;
-
-			{
-				boost::unique_lock<boost::mutex> lock(*c.rmx);
-				if(!c.connected)
-					throw clientDisconnectedException();
-				c >> player >> requestID >> pack; //get the package
-
-				if (!pack)
-				{
-					logGlobal->error("Received a null package marked as request %d from player %d", requestID, player);
-				}
-				else
-				{
-					packType = typeList.getTypeID(pack); //get the id of type
-
-					logGlobal->trace("Received client message (request %d by player %d (%s)) of type with ID=%d (%s).\n",
-									 requestID, player, player.getStr(), packType, typeid(*pack).name());
-				}
-			}
-
-			//prepare struct informing that action was applied
-			auto sendPackageResponse = [&](bool succesfullyApplied)
-			{
-				//dont reply to disconnected client
-				//TODO: this must be implemented as option of CPackForServer
-				if(dynamic_cast<LeaveGame *>(pack) || dynamic_cast<CloseServer *>(pack))
-					return;
-
-				PackageApplied applied;
-				applied.player = player;
-				applied.result = succesfullyApplied;
-				applied.packType = packType;
-				applied.requestID = requestID;
-				boost::unique_lock<boost::mutex> lock(*c.wmx);
-				c << &applied;
-			};
-			CBaseForGHApply *apply = applier->getApplier(packType); //and appropriate applier object
-			if(isBlockedByQueries(pack, player))
-			{
-				sendPackageResponse(false);
-			}
-			else if (apply)
-			{
-				const bool result = apply->applyOnGH(this, &c, pack, player);
-				if (result)
-					logGlobal->trace("Message %s successfully applied!", typeid(*pack).name());
-				else
-					complain((boost::format("Got false in applying %s... that request must have been fishy!")
-						% typeid(*pack).name()).str());
-
-				sendPackageResponse(true);
-			}
-			else
-			{
-				logGlobal->error("Message cannot be applied, cannot find applier (unregistered type)!");
-				sendPackageResponse(false);
-			}
+		PackageApplied applied;
+		applied.player = pack->player;
+		applied.result = succesfullyApplied;
+		applied.packType = typeList.getTypeID(pack);
+		applied.requestID = pack->requestID;
+		pack->c->sendPack(&applied);
+	};
 
-			vstd::clear_pointer(pack);
-		}
-	}
-	catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
+	CBaseForGHApply * apply = applier->getApplier(typeList.getTypeID(pack)); //and appropriate applier object
+	if(isBlockedByQueries(pack, pack->player))
 	{
-		handleDisconnection(e);
+		sendPackageResponse(false);
 	}
-	catch(clientDisconnectedException & e)
+	else if(apply)
 	{
-		handleDisconnection(e);
+		const bool result = apply->applyOnGH(this, pack);
+		if(result)
+			logGlobal->trace("Message %s successfully applied!", typeid(*pack).name());
+		else
+			complain((boost::format("Got false in applying %s... that request must have been fishy!")
+				% typeid(*pack).name()).str());
+
+		sendPackageResponse(true);
 	}
-	catch(...)
+	else
 	{
-		serverShuttingDown = true;
-		handleException();
-		throw;
+		logGlobal->error("Message cannot be applied, cannot find applier (unregistered type)!");
+		sendPackageResponse(false);
 	}
 
-	logGlobal->error("Ended handling connection");
+	vstd::clear_pointer(pack);
 }
 
 int CGameHandler::moveStack(int stack, BattleHex dest)
@@ -1569,12 +1514,12 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 	return ret;
 }
 
-CGameHandler::CGameHandler()
+CGameHandler::CGameHandler(CVCMIServer * lobby)
+	: lobby(lobby)
 {
 	QID = 1;
-	//gs = nullptr;
 	IObjectInterface::cb = this;
-	applier = new CApplier<CBaseForGHApply>();
+	applier = std::make_shared<CApplier<CBaseForGHApply>>();
 	registerTypesServerPacks(*applier);
 	visitObjectAfterVictory = false;
 
@@ -1584,8 +1529,6 @@ CGameHandler::CGameHandler()
 CGameHandler::~CGameHandler()
 {
 	delete spellEnv;
-	delete applier;
-	applier = nullptr;
 	delete gs;
 }
 
@@ -1988,16 +1931,9 @@ void CGameHandler::run(bool resume)
 	LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
 
 	using namespace boost::posix_time;
-	for (CConnection *cc : conns)
+	for (auto cc : lobby->connections)
 	{
-		if (!resume)
-		{
-			(*cc) << gs->initialOpts; // gs->scenarioOps
-		}
-
-		std::set<PlayerColor> players;
-		(*cc) >> players; //how many players will be handled at that client
-
+		auto players = lobby->getAllClientPlayers(cc->connectionID);
 		std::stringstream sbuffer;
 		sbuffer << "Connection " << cc->connectionID << " will handle " << players.size() << " player: ";
 		for (PlayerColor color : players)
@@ -2005,30 +1941,15 @@ void CGameHandler::run(bool resume)
 			sbuffer << color << " ";
 			{
 				boost::unique_lock<boost::recursive_mutex> lock(gsm);
-				if(!color.isSpectator()) // there can be more than one spectator
-					connections[color] = cc;
+				connections[color].insert(cc);
 			}
 		}
 		logGlobal->info(sbuffer.str());
-
-		cc->addStdVecItems(gs);
-		cc->enableStackSendingByID();
-		cc->disableSmartPointerSerialization();
-	}
-
-	for (auto & elem : conns)
-	{
-		std::set<PlayerColor> pom;
-		for (auto j = connections.cbegin(); j!=connections.cend();j++)
-			if (j->second == elem)
-				pom.insert(j->first);
-
-		boost::thread(std::bind(&CGameHandler::handleConnection,this,pom,std::ref(*elem)));
 	}
 
 	auto playerTurnOrder = generatePlayerTurnOrder();
 
-	while(!serverShuttingDown)
+	while(lobby->state == EServerState::GAMEPLAY)
 	{
 		if (!resume) newTurn();
 
@@ -2069,12 +1990,14 @@ void CGameHandler::run(bool resume)
 
 					//wait till turn is done
 					boost::unique_lock<boost::mutex> lock(states.mx);
-					while(states.players.at(playerColor).makingTurn && !serverShuttingDown)
+					while(states.players.at(playerColor).makingTurn && lobby->state == EServerState::GAMEPLAY)
 					{
 						static time_duration p = milliseconds(100);
 						states.cv.timed_wait(lock, p);
 					}
 				}
+				if(lobby->state != EServerState::GAMEPLAY)
+					break;
 			}
 		}
 		//additional check that game is not finished
@@ -2084,11 +2007,9 @@ void CGameHandler::run(bool resume)
 			if (gs->players[player].status == EPlayerStatus::INGAME)
 					activePlayer = true;
 		}
-		if (!activePlayer)
-			serverShuttingDown = true;
+		if(!activePlayer)
+			lobby->state = EServerState::GAMEPLAY_ENDED;
 	}
-	while(conns.size() && (*conns.begin())->isOpen())
-		boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
 }
 
 std::list<PlayerColor> CGameHandler::generatePlayerTurnOrder() const
@@ -2619,12 +2540,12 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st
 	sendAndApply(&cs);
 }
 
-void CGameHandler::sendMessageTo(CConnection &c, const std::string &message)
+void CGameHandler::sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message)
 {
 	SystemMessage sm;
 	sm.text = message;
-	boost::unique_lock<boost::mutex> lock(*c.wmx);
-	c << &sm;
+	boost::unique_lock<boost::mutex> lock(*c->mutexWrite);
+	*(c.get()) << &sm;
 }
 
 void CGameHandler::giveHeroBonus(GiveBonus * bonus)
@@ -2776,13 +2697,12 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
 void CGameHandler::sendToAllClients(CPackForClient * info)
 {
 	logNetwork->trace("Sending to all clients a package of type %s", typeid(*info).name());
-	for (auto & elem : conns)
+	for (auto c : lobby->connections)
 	{
-		if(!elem->isOpen())
+		if(!c->isOpen())
 			continue;
 
-		boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
-		*elem << info;
+		c->sendPack(info);
 	}
 }
 
@@ -2818,14 +2738,14 @@ void CGameHandler::sendAndApply(NewStructures * info)
 
 void CGameHandler::save(const std::string & filename)
 {
-	logGlobal->info("Saving to %s", filename);
+	logGlobal->info("Loading from %s", filename);
 	const auto stem	= FileInfo::GetPathStem(filename);
 	const auto savefname = stem.to_string() + ".vsgm1";
 	CResourceHandler::get("local")->createResource(savefname);
 
 	{
 		logGlobal->info("Ordering clients to serialize...");
-		SaveGame sg(savefname);
+		SaveGameClient sg(savefname);
 		sendToAllClients(&sg);
 	}
 
@@ -2845,34 +2765,26 @@ void CGameHandler::save(const std::string & filename)
 	}
 }
 
-void CGameHandler::close()
+void CGameHandler::load(const std::string & filename)
 {
-	logGlobal->info("We have been requested to close.");
-	serverShuttingDown = true;
-
-	for (auto & elem : conns)
-	{
-		if(!elem->isOpen())
-			continue;
-
-		boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
-		elem->close();
-		elem->connected = false;
-	}
-}
+	logGlobal->info("Loading from %s", filename);
+	const auto stem	= FileInfo::GetPathStem(filename);
 
-void CGameHandler::playerLeftGame(int cid)
-{
-	for (auto & elem : conns)
+	try
 	{
-		if(elem->isOpen() && elem->connectionID == cid)
 		{
-			boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
-			elem->close();
-			elem->connected = false;
-			break;
+			CLoadFile lf(*CResourceHandler::get("local")->getResourceName(ResourceID(stem.to_string(), EResType::SERVER_SAVEGAME)), MINIMAL_SERIALIZATION_VERSION);
+			loadCommonState(lf);
+			logGlobal->info("Loading server state");
+			lf >> *this;
 		}
+		logGlobal->info("Game has been successfully loaded!");
+	}
+	catch(std::exception &e)
+	{
+		logGlobal->error("Failed to load game: %s", e.what());
 	}
+	gs->updateOnLoad(lobby->si.get());
 }
 
 bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player)
@@ -3014,11 +2926,11 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
 	return true;
 }
 
-PlayerColor CGameHandler::getPlayerAt(CConnection *c) const
+PlayerColor CGameHandler::getPlayerAt(std::shared_ptr<CConnection> c) const
 {
 	std::set<PlayerColor> all;
 	for (auto i=connections.cbegin(); i!=connections.cend(); i++)
-		if (i->second == c)
+		if(vstd::contains(i->second, c))
 			all.insert(i->first);
 
 	switch(all.size())
@@ -4567,7 +4479,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 void CGameHandler::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj)
 {
 	bool cheated = true;
-	PlayerMessage temp_message(player, message, ObjectInstanceID(-1)); // don't inform other client on selected object
+	PlayerMessageClient temp_message(player, message);
 	sendAndApply(&temp_message);
 
 	std::vector<std::string> cheat;
@@ -5304,51 +5216,9 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 				}
 			}
 
-			if (p->human)
+			if(p->human)
 			{
-				serverShuttingDown = true;
-
-				if (gs->scenarioOps->campState)
-				{
-					std::vector<CGHeroInstance *> crossoverHeroes;
-					for (CGHeroInstance * hero : gs->map->heroesOnMap)
-					{
-						if (hero->tempOwner == player)
-						{
-							// keep all heroes from the winning player
-							crossoverHeroes.push_back(hero);
-						}
-						else if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(hero->subID)))
-						{
-							// keep hero whether lost or won (like Xeron in AB campaign)
-							crossoverHeroes.push_back(hero);
-						}
-					}
-					// keep lost heroes which are in heroes pool
-					for (auto & heroPair : gs->hpool.heroesPool)
-					{
-						if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(heroPair.first)))
-						{
-							crossoverHeroes.push_back(heroPair.second.get());
-						}
-					}
-
-					gs->scenarioOps->campState->setCurrentMapAsConquered(crossoverHeroes);
-
-					//Request clients to change connection mode
-					PrepareForAdvancingCampaign pfac;
-					sendAndApply(&pfac);
-					//Change connection mode
-					if (getPlayer(player)->human && getStartInfo()->campState)
-					{
-						for (auto connection : conns)
-							connection->prepareForSendingHeroes();
-					}
-
-					UpdateCampaignState ucs;
-					ucs.camp = gs->scenarioOps->campState;
-					sendAndApply(&ucs);
-				}
+				lobby->state = EServerState::GAMEPLAY_ENDED;
 			}
 		}
 		else

+ 13 - 12
server/CGameHandler.h

@@ -30,6 +30,9 @@ class IMarket;
 
 class SpellCastEnvironment;
 
+template<typename T> class CApplier;
+class CBaseForGHApply;
+
 struct PlayerStatus
 {
 	bool makingTurn;
@@ -74,6 +77,8 @@ struct CasualtiesAfterBattle
 
 class CGameHandler : public IGameCallback, CBattleInfoCallback
 {
+	CVCMIServer * lobby;
+	std::shared_ptr<CApplier<CBaseForGHApply>> applier;
 public:
 	using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>;
 	//use enums as parameters, because doMove(sth, true, false, true) is not readable
@@ -81,9 +86,8 @@ public:
 	enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST};
 	enum ELEaveTile {LEAVING_TILE, REMAINING_ON_TILE};
 
-	std::map<PlayerColor, CConnection*> connections; //player color -> connection to client with interface of that player
+	std::map<PlayerColor, std::set<std::shared_ptr<CConnection>>> connections; //player color -> connection to client with interface of that player
 	PlayerStatuses states; //player color -> player state
-	std::set<CConnection*> conns;
 
 	//queries stuff
 	boost::recursive_mutex gsm;
@@ -111,7 +115,7 @@ public:
 	void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
 	void setBattleResult(BattleResult::EResult resultType, int victoriusSide);
 
-	CGameHandler();
+	CGameHandler(CVCMIServer * lobby);
 	~CGameHandler();
 
 	//////////////////////////////////////////////////////////////////////////
@@ -189,8 +193,9 @@ public:
 	void commitPackage(CPackForClient *pack) override;
 
 	void init(StartInfo *si);
-	void handleConnection(std::set<PlayerColor> players, CConnection &c);
-	PlayerColor getPlayerAt(CConnection *c) const;
+	void handleClientDisconnection(std::shared_ptr<CConnection> c);
+	void handleReceivedPack(CPackForServer * pack);
+	PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;
 
 	void playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj);
 	void updateGateState();
@@ -225,8 +230,8 @@ public:
 	bool disbandCreature( ObjectInstanceID id, SlotID pos );
 	bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player);
 	void save(const std::string &fname);
-	void close();
-	void playerLeftGame(int cid);
+	void load(const std::string &fname);
+
 	void handleTimeEvents();
 	void handleTownEvents(CGTownInstance *town, NewTurn &n);
 	bool complain(const std::string &problem); //sends message to all clients, prints on the logs and return true
@@ -248,7 +253,7 @@ public:
 	}
 
 	void sendMessageToAll(const std::string &message);
-	void sendMessageTo(CConnection &c, const std::string &message);
+	void sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message);
 	void sendToAllClients(CPackForClient * info);
 	void sendAndApply(CPackForClient * info) override;
 	void applyAndSend(CPackForClient * info);
@@ -308,10 +313,6 @@ private:
 	void checkVictoryLossConditionsForAll();
 };
 
-class clientDisconnectedException : public std::exception
-{
-
-};
 class ExceptionNotAllowedAction : public std::exception
 {
 

+ 1 - 0
server/CMakeLists.txt

@@ -8,6 +8,7 @@ set(server_SRCS
 		CQuery.cpp
 		CVCMIServer.cpp
 		NetPacksServer.cpp
+		NetPacksLobbyServer.cpp
 )
 
 set(server_HEADERS

+ 961 - 680
server/CVCMIServer.cpp

@@ -1,682 +1,963 @@
-/*
- * CVCMIServer.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"
-
+/*
+ * CVCMIServer.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"
+
 #if BOOST_VERSION >= 106600
 #define BOOST_ASIO_ENABLE_OLD_SERVICES
-#endif
-#include <boost/asio.hpp>
-
-#include "../lib/filesystem/Filesystem.h"
-#include "../lib/mapping/CCampaignHandler.h"
-#include "../lib/CThreadHelper.h"
-#include "../lib/serializer/Connection.h"
-#include "../lib/CModHandler.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 "CVCMIServer.h"
-#include "../lib/StartInfo.h"
-#include "../lib/mapping/CMap.h"
-#include "../lib/rmg/CMapGenOptions.h"
-#ifdef VCMI_ANDROID
-#include "lib/CAndroidVMHelper.h"
-#else
-#include "../lib/Interprocess.h"
-#endif
-#include "../lib/VCMI_Lib.h"
-#include "../lib/VCMIDirs.h"
-#include "CGameHandler.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/UnlockGuard.h"
-
-#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID)
-#include <execinfo.h>
-#endif
-
-std::string NAME_AFFIX = "server";
-std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
-std::atomic<bool> serverShuttingDown(false);
-
-boost::program_options::variables_map cmdLineOptions;
-
-static void vaccept(boost::asio::ip::tcp::acceptor *ac, boost::asio::ip::tcp::socket *s, boost::system::error_code *error)
-{
-	ac->accept(*s,*error);
-}
-
-
-
-CPregameServer::CPregameServer(CConnection * Host, TAcceptor * Acceptor)
-	: host(Host), listeningThreads(0), acceptor(Acceptor), upcomingConnection(nullptr),
-	  curmap(nullptr), curStartInfo(nullptr), state(RUNNING)
-{
-	initConnection(host);
-}
-
-void CPregameServer::handleConnection(CConnection *cpc)
-{
-	setThreadName("CPregameServer::handleConnection");
-	try
-	{
-		while(!cpc->receivedStop)
-		{
-			CPackForSelectionScreen *cpfs = nullptr;
-			*cpc >> cpfs;
-
-			logNetwork->info("Got package to announce %s from %s", typeid(*cpfs).name(), cpc->toString());
-
-			boost::unique_lock<boost::recursive_mutex> queueLock(mx);
-			bool quitting = dynamic_ptr_cast<QuitMenuWithoutStarting>(cpfs),
-				startingGame = dynamic_ptr_cast<StartWithCurrentSettings>(cpfs);
-			if(quitting || startingGame) //host leaves main menu or wants to start game -> we end
-			{
-				cpc->receivedStop = true;
-				if(!cpc->sendStop)
-					sendPack(cpc, *cpfs);
-
-				if(cpc == host)
-					toAnnounce.push_back(cpfs);
-			}
-			else
-				toAnnounce.push_back(cpfs);
-
-			if(startingGame)
-			{
-				//wait for sending thread to announce start
-				auto unlock = vstd::makeUnlockGuard(mx);
-				while(state == RUNNING) boost::this_thread::sleep(boost::posix_time::milliseconds(50));
-			}
-			else if(quitting) // Server must be stopped if host is leaving from lobby to avoid crash
-			{
-				serverShuttingDown = true;
-			}
-		}
-	}
-	catch (const std::exception& e)
-	{
-		boost::unique_lock<boost::recursive_mutex> queueLock(mx);
-		logNetwork->error("%s dies... \nWhat happened: %s", cpc->toString(), e.what());
-	}
-
-	boost::unique_lock<boost::recursive_mutex> queueLock(mx);
-	if(state != ENDING_AND_STARTING_GAME)
-	{
-		connections -= cpc;
-
-		//notify other players about leaving
-		auto pl = new PlayerLeft();
-		pl->playerID = cpc->connectionID;
-		announceTxt(cpc->name + " left the game");
-		toAnnounce.push_back(pl);
-
-		if(connections.empty())
-		{
-			logNetwork->error("Last connection lost, server will close itself...");
-			boost::this_thread::sleep(boost::posix_time::seconds(2)); //we should never be hasty when networking
-			state = ENDING_WITHOUT_START;
-		}
-	}
-
-	logNetwork->info("Thread listening for %s ended", cpc->toString());
-	listeningThreads--;
-	vstd::clear_pointer(cpc->handler);
-}
-
-void CPregameServer::run()
-{
-	startListeningThread(host);
-	start_async_accept();
-
-	while(state == RUNNING)
-	{
-		{
-			boost::unique_lock<boost::recursive_mutex> myLock(mx);
-			while(!toAnnounce.empty())
-			{
-				processPack(toAnnounce.front());
-				toAnnounce.pop_front();
-			}
-
-// 			//we end sending thread if we ordered all our connections to stop
-// 			ending = true;
-// 			for(CPregameConnection *pc : connections)
-// 				if(!pc->sendStop)
-// 					ending = false;
-
-			if(state != RUNNING)
-			{
-				logNetwork->info("Stopping listening for connections...");
-				if(acceptor)
-					acceptor->close();
-			}
-
-			if(acceptor)
-			{
-				acceptor->get_io_service().reset();
-				acceptor->get_io_service().poll();
-			}
-		} //frees lock
-
-		boost::this_thread::sleep(boost::posix_time::milliseconds(50));
-	}
-
-	logNetwork->info("Thread handling connections ended");
-
-	if(state == ENDING_AND_STARTING_GAME)
-	{
-		logNetwork->info("Waiting for listening thread to finish...");
-		while(listeningThreads) boost::this_thread::sleep(boost::posix_time::milliseconds(50));
-		logNetwork->info("Preparing new game");
-	}
-}
-
-CPregameServer::~CPregameServer()
-{
-	delete acceptor;
-	delete upcomingConnection;
-
-	for(CPackForSelectionScreen *pack : toAnnounce)
-		delete pack;
-
-	toAnnounce.clear();
-
-	//TODO pregameconnections
-}
-
-void CPregameServer::connectionAccepted(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! :)");
-		std::string name = NAME;
-		CConnection *pc = new CConnection(upcomingConnection, name.append(" STATE_PREGAME"));
-		initConnection(pc);
-		upcomingConnection = nullptr;
-
-		startListeningThread(pc);
-
-		*pc << (ui8)pc->connectionID << curmap;
-
-		announceTxt(pc->name + " joins the game");
-		auto pj = new PlayerJoined();
-		pj->playerName = pc->name;
-		pj->connectionID = pc->connectionID;
-		toAnnounce.push_back(pj);
-	}
-	catch(std::exception& e)
-	{
-		upcomingConnection = nullptr;
-		logNetwork->info("I guess it was just my imagination!");
-	}
-
-	start_async_accept();
-}
-
-void CPregameServer::start_async_accept()
-{
-	assert(!upcomingConnection);
-	assert(acceptor);
-
-	upcomingConnection = new TSocket(acceptor->get_io_service());
-	acceptor->async_accept(*upcomingConnection, std::bind(&CPregameServer::connectionAccepted, this, _1));
-}
-
-void CPregameServer::announceTxt(const std::string &txt, const std::string &playerName)
-{
-	logNetwork->info("%s says: %s", playerName, txt);
-	ChatMessage cm;
-	cm.playerName = playerName;
-	cm.message = txt;
-
-	boost::unique_lock<boost::recursive_mutex> queueLock(mx);
-	toAnnounce.push_front(new ChatMessage(cm));
-}
-
-void CPregameServer::announcePack(const CPackForSelectionScreen &pack)
-{
-	for(CConnection *pc : connections)
-		sendPack(pc, pack);
-}
-
-void CPregameServer::sendPack(CConnection * pc, const CPackForSelectionScreen & pack)
-{
-	if(!pc->sendStop)
-	{
-		logNetwork->info("\tSending pack of type %s to %s", typeid(pack).name(), pc->toString());
-		*pc << &pack;
-	}
-
-	if(dynamic_ptr_cast<QuitMenuWithoutStarting>(&pack))
-	{
-		pc->sendStop = true;
-	}
-	else if(dynamic_ptr_cast<StartWithCurrentSettings>(&pack))
-	{
-		pc->sendStop = true;
-	}
-}
-
-void CPregameServer::processPack(CPackForSelectionScreen * pack)
-{
-	if(dynamic_ptr_cast<CPregamePackToHost>(pack))
-	{
-		sendPack(host, *pack);
-	}
-	else if(SelectMap *sm = dynamic_ptr_cast<SelectMap>(pack))
-	{
-		vstd::clear_pointer(curmap);
-		curmap = sm->mapInfo;
-		sm->free = false;
-		announcePack(*pack);
-	}
-	else if(UpdateStartOptions *uso = dynamic_ptr_cast<UpdateStartOptions>(pack))
-	{
-		vstd::clear_pointer(curStartInfo);
-		curStartInfo = uso->options;
-		uso->free = false;
-		announcePack(*pack);
-	}
-	else if(dynamic_ptr_cast<StartWithCurrentSettings>(pack))
-	{
-		state = ENDING_AND_STARTING_GAME;
-		announcePack(*pack);
-	}
-	else
-		announcePack(*pack);
-
-	delete pack;
-}
-
-void CPregameServer::initConnection(CConnection *c)
-{
-	*c >> c->name;
-	connections.insert(c);
-	logNetwork->info("Pregame connection with player %s established!", c->name);
-}
-
-void CPregameServer::startListeningThread(CConnection * pc)
-{
-	listeningThreads++;
-	pc->enterPregameConnectionMode();
-	pc->handler = new boost::thread(&CPregameServer::handleConnection, this, pc);
-}
-
-CVCMIServer::CVCMIServer()
-	: port(3030), io(new boost::asio::io_service()), firstConnection(nullptr), shared(nullptr)
-{
-	logNetwork->trace("CVCMIServer created!");
-	if(cmdLineOptions.count("port"))
-		port = cmdLineOptions["port"].as<ui16>();
-	logNetwork->info("Port %d will be used", port);
-	try
-	{
-		acceptor = new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
-	}
-	catch(...)
-	{
-		logNetwork->info("Port %d is busy, trying to use random port instead", port);
-		if(cmdLineOptions.count("run-by-client") && !cmdLineOptions.count("enable-shm"))
-		{
-			logNetwork->error("Cant pass port number to client without shared memory!", port);
-			exit(0);
-		}
-		acceptor = new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0));
-		port = acceptor->local_endpoint().port();
-	}
-	logNetwork->info("Listening for connections at port %d", port);
-}
-CVCMIServer::~CVCMIServer()
-{
-	//delete io;
-	//delete acceptor;
-	//delete firstConnection;
-}
-
-std::shared_ptr<CGameHandler> CVCMIServer::initGhFromHostingConnection(CConnection &c)
-{
-	auto gh = std::make_shared<CGameHandler>();
-	StartInfo si;
-	c >> si; //get start options
-
-	if(!si.createRandomMap())
-	{
-		bool mapFound = CResourceHandler::get()->existsResource(ResourceID(si.mapname, EResType::MAP));
-
-		//TODO some checking for campaigns
-		if(!mapFound && si.mode == StartInfo::NEW_GAME)
-		{
-			c << ui8(1); //WRONG!
-			gh.reset();
-			return gh;
-		}
-	}
-
-	c << ui8(0); //OK!
-
-	gh->init(&si);
-	gh->conns.insert(&c);
-
-	return gh;
-}
-
-void CVCMIServer::newGame()
-{
-	CConnection &c = *firstConnection;
-	ui8 clients;
-	c >> clients; //how many clients should be connected
-	assert(clients == 1); //multi goes now by newPregame, TODO: custom lobbies
-
-	auto gh = initGhFromHostingConnection(c);
-
-	if(gh)
-		gh->run(false);
-}
-
-void CVCMIServer::newPregame()
-{
-	auto cps = new CPregameServer(firstConnection, acceptor);
-	cps->run();
-	if(cps->state == CPregameServer::ENDING_WITHOUT_START)
-	{
-		delete cps;
-		return;
-	}
-
-	if(cps->state == CPregameServer::ENDING_AND_STARTING_GAME)
-	{
-		CGameHandler gh;
-		gh.conns = cps->connections;
-		gh.init(cps->curStartInfo);
-
-		for(CConnection *c : gh.conns)
-			c->addStdVecItems(gh.gs);
-
-		gh.run(false);
-	}
-}
-
-void CVCMIServer::start()
-{
-#ifndef VCMI_ANDROID
-	if(cmdLineOptions.count("enable-shm"))
-	{
-		std::string sharedMemoryName = "vcmi_memory";
-		if(cmdLineOptions.count("enable-shm-uuid") && cmdLineOptions.count("uuid"))
-		{
-			sharedMemoryName += "_" + cmdLineOptions["uuid"].as<std::string>();
-		}
-		shared = new SharedMemory(sharedMemoryName);
-	}
-#endif
-
-	boost::system::error_code error;
-	for (;;)
-	{
-		try
-		{
-			auto s = new boost::asio::ip::tcp::socket(acceptor->get_io_service());
-			boost::thread acc(std::bind(vaccept,acceptor,s,&error));
-#ifdef VCMI_ANDROID
-			{ // in block to clean-up vm helper after use, because we don't need to keep this thread attached to vm
-				CAndroidVMHelper envHelper;
-				envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady");
-				logNetwork->info("Sending server ready message to client");
-			}
-#else
-			if(shared)
-			{
-				shared->sr->setToReadyAndNotify(port);
-			}
-#endif
-
-			acc.join();
-			if (error)
-			{
-				logNetwork->warn("Got connection but there is an error %s", error.message());
-				return;
-			}
-			logNetwork->info("We've accepted someone... ");
-			std::string name = NAME;
-			firstConnection = new CConnection(s, name.append(" STATE_WAITING"));
-			logNetwork->info("Got connection!");
-			while(!serverShuttingDown)
-			{
-				ui8 mode;
-				*firstConnection >> mode;
-				switch (mode)
-				{
-				case 0:
-					firstConnection->close();
-					exit(0);
-				case 1:
-					firstConnection->close();
-					return;
-				case 2:
-					newGame();
-					break;
-				case 3:
-					loadGame();
-					break;
-				case 4:
-					newPregame();
-					break;
-				}
-			}
-			break;
-		}
-		catch(std::exception& e)
-		{
-			vstd::clear_pointer(firstConnection);
-			logNetwork->info("I guess it was just my imagination!");
-		}
-	}
-}
-
-void CVCMIServer::loadGame()
-{
-	CConnection &c = *firstConnection;
-	std::string fname;
-	CGameHandler gh;
-	boost::system::error_code error;
-	ui8 clients;
-
-	c >> clients >> fname; //how many clients should be connected
-
-	{
-		CLoadFile lf(*CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::SERVER_SAVEGAME)), MINIMAL_SERIALIZATION_VERSION);
-		gh.loadCommonState(lf);
-		lf >> gh;
-	}
-
-	c << ui8(0);
-
-	gh.conns.insert(firstConnection);
-
-	for(int i=1; i<clients; i++)
-	{
-		auto s = make_unique<boost::asio::ip::tcp::socket>(acceptor->get_io_service());
-		acceptor->accept(*s,error);
-		if(error) //retry
-		{
-			logNetwork->warn("Cannot establish connection - retrying...");
-			i--;
-			continue;
-		}
-
-		gh.conns.insert(new CConnection(s.release(),NAME));
-	}
-
-	gh.run(true);
-}
-
-
-
-static void handleCommandOptions(int argc, char *argv[])
-{
-	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")
-		("uuid", po::value<std::string>(), "")
-		("enable-shm-uuid", "use UUID for shared memory identifier")
-		("enable-shm", "enable usage of shared memory")
-		("port", po::value<ui16>(), "port at which server will listen to connections from client");
-
-	if(argc > 1)
-	{
-		try
-		{
-			po::store(po::parse_command_line(argc, argv, opts), cmdLineOptions);
-		}
-		catch(std::exception &e)
-		{
-			std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl;
-		}
-	}
-
-	po::notify(cmdLineOptions);
-	if (cmdLineOptions.count("help"))
-	{
-		auto time = std::time(0);
-		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 (cmdLineOptions.count("version"))
-	{
-		printf("%s\n", GameConstants::VCMI_VERSION.c_str());
-		std::cout << VCMIDirs::get().genHelpString();
-		exit(0);
-	}
-}
-
-#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID)
-void handleLinuxSignal(int sig)
-{
-	const int STACKTRACE_SIZE = 100;
-	void * buffer[STACKTRACE_SIZE];
-	int ptrCount = backtrace(buffer, STACKTRACE_SIZE);
-	char ** strings;
-
-	logGlobal->error("Error: signal %d :", sig);
-	strings = backtrace_symbols(buffer, ptrCount);
-	if(strings == nullptr)
-	{
-		logGlobal->error("There are no symbols.");
-	}
-	else
-	{
-		for(int i = 0; i < ptrCount; ++i)
-		{
-			logGlobal->error(strings[i]);
-		}
-		free(strings);
-	}
-
-	_exit(EXIT_FAILURE);
-}
-#endif
-
-int main(int argc, char * argv[])
-{
-#ifndef VCMI_ANDROID
-	// 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
-	// Installs a sig sev segmentation violation handler
-	// to log stacktrace
-	#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID)
-	signal(SIGSEGV, handleLinuxSignal);
-    #endif
-
-	console = new CConsoleHandler();
-	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Server_log.txt", console);
-	logConfig.configureDefault();
-	logGlobal->info(NAME);
-
-	handleCommandOptions(argc, argv);
-	preinitDLL(console);
-	settings.init();
-	logConfig.configure();
-
-	loadDLLClasses();
-	srand ( (ui32)time(nullptr) );
-	try
-	{
-		boost::asio::io_service io_service;
-		CVCMIServer server;
-
-		try
-		{
-			while(!serverShuttingDown)
-			{
-				server.start();
-			}
-			io_service.run();
-		}
-		catch (boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
-		{
-			logNetwork->error(e.what());
-			serverShuttingDown = true;
-		}
-		catch (...)
-		{
-			handleException();
-		}
-	}
-	catch(boost::system::system_error &e)
-	{
-		logNetwork->error(e.what());
-		//catch any startup errors (e.g. can't access port) errors
-		//and return non-zero status so client can detect error
-		throw;
-	}
-#ifdef VCMI_ANDROID
-	CAndroidVMHelper envHelper;
-	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
-#endif
-	vstd::clear_pointer(VLC);
-	CResourceHandler::clear();
-	return 0;
-}
-
-#ifdef VCMI_ANDROID
-
-void CVCMIServer::create()
-{
-	const char * foo[1] = {"android-server"};
-	main(1, const_cast<char **>(foo));
-}
-
-#endif
+#endif
+#include <boost/asio.hpp>
+
+#include "../lib/filesystem/Filesystem.h"
+#include "../lib/mapping/CCampaignHandler.h"
+#include "../lib/CThreadHelper.h"
+#include "../lib/serializer/Connection.h"
+#include "../lib/CModHandler.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 "CVCMIServer.h"
+#include "../lib/StartInfo.h"
+#include "../lib/mapping/CMap.h"
+#include "../lib/rmg/CMapGenOptions.h"
+#ifdef VCMI_ANDROID
+#include "lib/CAndroidVMHelper.h"
+#else
+#include "../lib/Interprocess.h"
+#endif
+#include "../lib/VCMI_Lib.h"
+#include "../lib/VCMIDirs.h"
+#include "CGameHandler.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/UnlockGuard.h"
+
+// for applier
+#include "../lib/registerTypes/RegisterTypes.h"
+
+// UUID generation
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_io.hpp>
+#include <boost/uuid/uuid_generators.hpp>
+
+#include "../lib/CGameState.h"
+
+#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID)
+#include <execinfo.h>
+#endif
+
+template<typename T> class CApplyOnServer;
+
+class CBaseForServerApply
+{
+public:
+	virtual bool applyOnServerBefore(CVCMIServer * srv, void * pack) const =0;
+	virtual void applyOnServerAfter(CVCMIServer * srv, void * pack) const =0;
+	virtual ~CBaseForServerApply() {}
+	template<typename U> static CBaseForServerApply * getApplier(const U * t = nullptr)
+	{
+		return new CApplyOnServer<U>();
+	}
+};
+
+template <typename T> class CApplyOnServer : public CBaseForServerApply
+{
+public:
+	bool applyOnServerBefore(CVCMIServer * srv, void * pack) const override
+	{
+		T * ptr = static_cast<T *>(pack);
+		if(ptr->checkClientPermissions(srv))
+		{
+			boost::unique_lock<boost::mutex> stateLock(srv->stateMutex);
+			return ptr->applyOnServer(srv);
+		}
+		else
+			return false;
+	}
+
+	void applyOnServerAfter(CVCMIServer * srv, void * pack) const override
+	{
+		T * ptr = static_cast<T *>(pack);
+		ptr->applyOnServerAfterAnnounce(srv);
+	}
+};
+
+template <>
+class CApplyOnServer<CPack> : public CBaseForServerApply
+{
+public:
+	bool applyOnServerBefore(CVCMIServer * srv, void * pack) const override
+	{
+		logGlobal->error("Cannot apply plain CPack!");
+		assert(0);
+		return false;
+	}
+	void applyOnServerAfter(CVCMIServer * srv, void * pack) const override
+	{
+		logGlobal->error("Cannot apply plain CPack!");
+		assert(0);
+	}
+};
+
+std::string NAME_AFFIX = "server";
+std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')';
+
+CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts)
+	: port(3030), io(std::make_shared<boost::asio::io_service>()), state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false)
+{
+	uuid = boost::uuids::to_string(boost::uuids::random_generator()());
+	logNetwork->trace("CVCMIServer created! UUID: %s", uuid);
+	applier = std::make_shared<CApplier<CBaseForServerApply>>();
+	registerTypesLobbyPacks(*applier);
+
+	if(cmdLineOptions.count("port"))
+		port = cmdLineOptions["port"].as<ui16>();
+	logNetwork->info("Port %d will be used", port);
+	try
+	{
+		acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
+	}
+	catch(...)
+	{
+		logNetwork->info("Port %d is busy, trying to use random port instead", port);
+		if(cmdLineOptions.count("run-by-client") && !cmdLineOptions.count("enable-shm"))
+		{
+			logNetwork->error("Cant pass port number to client without shared memory!", port);
+			exit(0);
+		}
+		acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0));
+		port = acceptor->local_endpoint().port();
+	}
+	logNetwork->info("Listening for connections at port %d", port);
+}
+
+CVCMIServer::~CVCMIServer()
+{
+
+	for(CPackForLobby * pack : announceQueue)
+		delete pack;
+
+	announceQueue.clear();
+}
+
+void CVCMIServer::run()
+{
+	if(!restartGameplay)
+	{
+		boost::thread(&CVCMIServer::threadAnnounceLobby, this);
+#ifndef VCMI_ANDROID
+		if(cmdLineOptions.count("enable-shm"))
+		{
+			std::string sharedMemoryName = "vcmi_memory";
+			if(cmdLineOptions.count("enable-shm-uuid") && cmdLineOptions.count("uuid"))
+			{
+				sharedMemoryName += "_" + cmdLineOptions["uuid"].as<std::string>();
+			}
+			shm = std::make_shared<SharedMemory>(sharedMemoryName);
+		}
+#endif
+
+		startAsyncAccept();
+		if(shm)
+		{
+			shm->sr->setToReadyAndNotify(port);
+		}
+	}
+
+	while(state == EServerState::LOBBY)
+		boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+
+	logNetwork->info("Thread handling connections ended");
+
+	if(state == EServerState::GAMEPLAY)
+	{
+		gh->run(si->mode == StartInfo::LOAD_GAME);
+	}
+}
+
+void CVCMIServer::threadAnnounceLobby()
+{
+	while(state != EServerState::SHUTDOWN)
+	{
+		{
+			boost::unique_lock<boost::recursive_mutex> myLock(mx);
+			while(!announceQueue.empty())
+			{
+				announcePack(announceQueue.front());
+				announceQueue.pop_front();
+			}
+			if(state != EServerState::LOBBY)
+			{
+				if(acceptor)
+					acceptor->close();
+			}
+
+			if(acceptor)
+			{
+				acceptor->get_io_service().reset();
+				acceptor->get_io_service().poll();
+			}
+		}
+
+		boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+	}
+}
+
+void CVCMIServer::prepareToStartGame()
+{
+	if(state == EServerState::GAMEPLAY)
+	{
+		restartGameplay = true;
+		state = EServerState::LOBBY;
+		// FIXME: dirry hack to make sure old CGameHandler::run is finished
+		boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
+	}
+
+	gh = std::make_shared<CGameHandler>(this);
+	switch(si->mode)
+	{
+	case StartInfo::CAMPAIGN:
+		logNetwork->info("Preparing to start new campaign");
+		si->campState->currentMap = boost::make_optional(campaignMap);
+		si->campState->chosenCampaignBonuses[campaignMap] = campaignBonus;
+		gh->init(si.get());
+		break;
+
+	case StartInfo::NEW_GAME:
+		logNetwork->info("Preparing to start new game");
+		gh->init(si.get());
+		break;
+
+	case StartInfo::LOAD_GAME:
+		logNetwork->info("Preparing to start loaded game");
+		gh->load(si->mapname);
+		break;
+	default:
+		logNetwork->error("Wrong mode in StartInfo!");
+		assert(0);
+		break;
+	}
+}
+
+void CVCMIServer::startGameImmidiately()
+{
+	for(auto c : connections)
+		c->enterGameplayConnectionMode(gh->gs);
+
+	state = EServerState::GAMEPLAY;
+}
+
+void CVCMIServer::startAsyncAccept()
+{
+	assert(!upcomingConnection);
+	assert(acceptor);
+
+	upcomingConnection = std::make_shared<TSocket>(acceptor->get_io_service());
+	acceptor->async_accept(*upcomingConnection, std::bind(&CVCMIServer::connectionAccepted, this, _1));
+}
+
+void CVCMIServer::connectionAccepted(const boost::system::error_code & ec)
+{
+	if(ec)
+	{
+		if(state != EServerState::SHUTDOWN)
+			logNetwork->info("Something wrong during accepting: %s", ec.message());
+		return;
+	}
+
+	try
+	{
+		logNetwork->info("We got a new connection! :)");
+		auto c = std::make_shared<CConnection>(upcomingConnection, NAME, uuid);
+		upcomingConnection.reset();
+		connections.insert(c);
+		c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);
+	}
+	catch(std::exception & e)
+	{
+		upcomingConnection.reset();
+		logNetwork->info("I guess it was just my imagination!");
+	}
+
+	startAsyncAccept();
+}
+
+void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
+{
+	setThreadName("CVCMIServer::handleConnection");
+	c->enterLobbyConnectionMode();
+
+	try
+	{
+		while(c->connected)
+		{
+			CPack * pack = c->retrievePack();
+			if(auto lobbyPack = dynamic_ptr_cast<CPackForLobby>(pack))
+			{
+				handleReceivedPack(lobbyPack);
+			}
+			else if(auto serverPack = dynamic_ptr_cast<CPackForServer>(pack))
+			{
+				gh->handleReceivedPack(serverPack);
+			}
+		}
+	}
+	catch(boost::system::system_error & e)
+	{
+		if(state != EServerState::LOBBY)
+			gh->handleClientDisconnection(c);
+	}
+	catch(const std::exception & e)
+	{
+		boost::unique_lock<boost::recursive_mutex> queueLock(mx);
+		logNetwork->error("%s dies... \nWhat happened: %s", c->toString(), e.what());
+	}
+	catch(...)
+	{
+		state = EServerState::SHUTDOWN;
+		handleException();
+		throw;
+	}
+
+	boost::unique_lock<boost::recursive_mutex> queueLock(mx);
+//	if(state != ENDING_AND_STARTING_GAME)
+	{
+		auto lcd = new LobbyClientDisconnected();
+		lcd->c = c;
+		lcd->clientId = c->connectionID;
+		handleReceivedPack(lcd);
+	}
+
+	logNetwork->info("Thread listening for %s ended", c->toString());
+	c->handler.reset();
+}
+
+void CVCMIServer::handleReceivedPack(CPackForLobby * pack)
+{
+	CBaseForServerApply * apply = applier->getApplier(typeList.getTypeID(pack));
+	if(apply->applyOnServerBefore(this, pack))
+		addToAnnounceQueue(pack);
+	else
+		delete pack;
+}
+
+void CVCMIServer::announcePack(CPackForLobby * pack)
+{
+	for(auto c : connections)
+	{
+		// FIXME: we need to avoid senting something to client that not yet get answer for LobbyClientConnected
+		// Until UUID set we only pass LobbyClientConnected to this client
+		if(c->uuid == uuid && !dynamic_cast<LobbyClientConnected *>(pack))
+			continue;
+
+		c->sendPack(pack);
+	}
+
+	applier->getApplier(typeList.getTypeID(pack))->applyOnServerAfter(this, pack);
+
+	delete pack;
+}
+
+void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName)
+{
+	logNetwork->info("%s says: %s", playerName, txt);
+	auto cm = new LobbyChatMessage();
+	cm->playerName = playerName;
+	cm->message = txt;
+	addToAnnounceQueue(cm);
+}
+
+void CVCMIServer::addToAnnounceQueue(CPackForLobby * pack)
+{
+	boost::unique_lock<boost::recursive_mutex> queueLock(mx);
+	announceQueue.push_back(pack);
+}
+
+bool CVCMIServer::passHost(int toConnectionId)
+{
+	for(auto c : connections)
+	{
+		if(isClientHost(c->connectionID))
+			continue;
+		if(c->connectionID != toConnectionId)
+			continue;
+
+		hostClient = c;
+		hostClientId = c->connectionID;
+		announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId));
+		return true;
+	}
+	return false;
+}
+
+void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, StartInfo::EMode mode)
+{
+	c->connectionID = currentClientId++;
+
+	if(!hostClient)
+	{
+		hostClient = c;
+		hostClientId = c->connectionID;
+		si->mode = mode;
+	}
+
+	logNetwork->info("Connection with client %d established. UUID: %s", c->connectionID, c->uuid);
+	for(auto & name : names)
+	{
+		logNetwork->info("Client %d player: %s", c->connectionID, name);
+		ui8 id = currentPlayerId++;
+
+		ClientPlayer cp;
+		cp.connection = c->connectionID;
+		cp.name = name;
+		playerNames.insert(std::make_pair(id, cp));
+		announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % id % c->connectionID));
+
+		//put new player in first slot with AI
+		for(auto & elem : si->playerInfos)
+		{
+			if(elem.second.isControlledByAI() && !elem.second.compOnly)
+			{
+				setPlayerConnectedId(elem.second, id);
+				break;
+			}
+		}
+	}
+}
+
+void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
+{
+	connections -= c;
+	for(auto & pair : playerNames)
+	{
+		if(pair.second.connection != c->connectionID)
+			continue;
+
+		int id = pair.first;
+		announceTxt(boost::str(boost::format("%s (pid %d cid %d) left the game") % id % playerNames[id].name % c->connectionID));
+		playerNames.erase(id);
+
+		// Reset in-game players client used back to AI
+		if(PlayerSettings * s = si->getPlayersSettings(id))
+		{
+			setPlayerConnectedId(*s, PlayerSettings::PLAYER_AI);
+		}
+	}
+}
+
+void CVCMIServer::setPlayerConnectedId(PlayerSettings & pset, ui8 player) const
+{
+	if(vstd::contains(playerNames, player))
+		pset.name = playerNames.find(player)->second.name;
+	else
+		pset.name = VLC->generaltexth->allTexts[468]; //Computer
+
+	pset.connectedPlayerIDs.clear();
+	if(player != PlayerSettings::PLAYER_AI)
+		pset.connectedPlayerIDs.insert(player);
+}
+
+void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpts)
+{
+	mi = mapInfo;
+	if(!mi)
+		return;
+
+	auto namesIt = playerNames.cbegin();
+	si->playerInfos.clear();
+	if(mi->scenarioOptionsOfSave)
+	{
+		si = std::shared_ptr<StartInfo>(mi->scenarioOptionsOfSave);
+		si->mode = StartInfo::LOAD_GAME;
+		if(si->campState)
+			campaignMap = si->campState->currentMap.get();
+
+		for(auto & ps : si->playerInfos)
+		{
+			if(!ps.second.compOnly && ps.second.connectedPlayerIDs.size() && namesIt != playerNames.cend())
+			{
+				setPlayerConnectedId(ps.second, namesIt++->first);
+			}
+			else
+			{
+				setPlayerConnectedId(ps.second, PlayerSettings::PLAYER_AI);
+			}
+		}
+	}
+	else if(si->mode == StartInfo::NEW_GAME || si->mode == StartInfo::CAMPAIGN)
+	{
+		if(mi->campaignHeader)
+			return;
+
+		for(int i = 0; i < mi->mapHeader->players.size(); i++)
+		{
+			const PlayerInfo & pinfo = mi->mapHeader->players[i];
+
+			//neither computer nor human can play - no player
+			if(!(pinfo.canHumanPlay || pinfo.canComputerPlay))
+				continue;
+
+			PlayerSettings & pset = si->playerInfos[PlayerColor(i)];
+			pset.color = PlayerColor(i);
+			if(pinfo.canHumanPlay && namesIt != playerNames.cend())
+			{
+				setPlayerConnectedId(pset, namesIt++->first);
+			}
+			else
+			{
+				setPlayerConnectedId(pset, PlayerSettings::PLAYER_AI);
+				if(!pinfo.canHumanPlay)
+				{
+					pset.compOnly = true;
+				}
+			}
+
+			pset.castle = pinfo.defaultCastle();
+			pset.hero = pinfo.defaultHero();
+
+			if(pset.hero != PlayerSettings::RANDOM && pinfo.hasCustomMainHero())
+			{
+				pset.hero = pinfo.mainCustomHeroId;
+				pset.heroName = pinfo.mainCustomHeroName;
+				pset.heroPortrait = pinfo.mainCustomHeroPortrait;
+			}
+
+			pset.handicap = PlayerSettings::NO_HANDICAP;
+		}
+
+		if(mi->isRandomMap && mapGenOpts)
+			si->mapGenOptions = std::shared_ptr<CMapGenOptions>(mapGenOpts);
+		else
+			si->mapGenOptions.reset();
+	}
+	si->mapname = mi->fileURI;
+}
+
+void CVCMIServer::updateAndPropagateLobbyState()
+{
+	boost::unique_lock<boost::mutex> stateLock(stateMutex);
+	// Update player settings for RMG
+	// TODO: find appropriate location for this code
+	if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
+	{
+		for(const auto & psetPair : si->playerInfos)
+		{
+			const auto & pset = psetPair.second;
+			si->mapGenOptions->setStartingTownForPlayer(pset.color, pset.castle);
+			if(pset.isControlledByHuman())
+			{
+				si->mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::HUMAN);
+			}
+		}
+	}
+
+	auto lus = new LobbyUpdateState();
+	lus->state = *this;
+	addToAnnounceQueue(lus);
+}
+
+void CVCMIServer::setPlayer(PlayerColor clickedColor)
+{
+	struct PlayerToRestore
+	{
+		PlayerColor color;
+		int id;
+		void reset() { id = -1; color = PlayerColor::CANNOT_DETERMINE; }
+		PlayerToRestore(){ reset(); }
+	} playerToRestore;
+
+
+	PlayerSettings & clicked = si->playerInfos[clickedColor];
+	PlayerSettings * old = nullptr;
+
+	//identify clicked player
+	int clickedNameID = 0; //number of player - zero means AI, assume it initially
+	if(clicked.isControlledByHuman())
+		clickedNameID = *(clicked.connectedPlayerIDs.begin()); //if not AI - set appropiate ID
+
+	if(clickedNameID > 0 && playerToRestore.id == clickedNameID) //player to restore is about to being replaced -> put him back to the old place
+	{
+		PlayerSettings & restPos = si->playerInfos[playerToRestore.color];
+		setPlayerConnectedId(restPos, playerToRestore.id);
+		playerToRestore.reset();
+	}
+
+	int newPlayer; //which player will take clicked position
+
+	//who will be put here?
+	if(!clickedNameID) //AI player clicked -> if possible replace computer with unallocated player
+	{
+		newPlayer = getIdOfFirstUnallocatedPlayer();
+		if(!newPlayer) //no "free" player -> get just first one
+			newPlayer = playerNames.begin()->first;
+	}
+	else //human clicked -> take next
+	{
+		auto i = playerNames.find(clickedNameID); //clicked one
+		i++; //player AFTER clicked one
+
+		if(i != playerNames.end())
+			newPlayer = i->first;
+		else
+			newPlayer = 0; //AI if we scrolled through all players
+	}
+
+	setPlayerConnectedId(clicked, newPlayer); //put player
+
+	//if that player was somewhere else, we need to replace him with computer
+	if(newPlayer) //not AI
+	{
+		for(auto i = si->playerInfos.begin(); i != si->playerInfos.end(); i++)
+		{
+			int curNameID = *(i->second.connectedPlayerIDs.begin());
+			if(i->first != clickedColor && curNameID == newPlayer)
+			{
+				assert(i->second.connectedPlayerIDs.size());
+				playerToRestore.color = i->first;
+				playerToRestore.id = newPlayer;
+				setPlayerConnectedId(i->second, PlayerSettings::PLAYER_AI); //set computer
+				old = &i->second;
+				break;
+			}
+		}
+	}
+}
+
+void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
+{
+	PlayerSettings & s = si->playerInfos[player];
+	si16 & cur = s.castle;
+	auto & allowed = getPlayerInfo(player.getNum()).allowedFactions;
+	const bool allowRandomTown = getPlayerInfo(player.getNum()).isFactionRandom;
+
+	if(cur == PlayerSettings::NONE) //no change
+		return;
+
+	if(cur == PlayerSettings::RANDOM) //first/last available
+	{
+		if(dir > 0)
+			cur = *allowed.begin(); //id of first town
+		else
+			cur = *allowed.rbegin(); //id of last town
+
+	}
+	else // next/previous available
+	{
+		if((cur == *allowed.begin() && dir < 0) || (cur == *allowed.rbegin() && dir > 0))
+		{
+			if(allowRandomTown)
+			{
+				cur = PlayerSettings::RANDOM;
+			}
+			else
+			{
+				if(dir > 0)
+					cur = *allowed.begin();
+				else
+					cur = *allowed.rbegin();
+			}
+		}
+		else
+		{
+			assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range
+			auto iter = allowed.find(cur);
+			std::advance(iter, dir);
+			cur = *iter;
+		}
+	}
+
+	if(s.hero >= 0 && !getPlayerInfo(player.getNum()).hasCustomMainHero()) // remove hero unless it set to fixed one in map editor
+	{
+		s.hero = PlayerSettings::RANDOM;
+	}
+	if(cur < 0 && s.bonus == PlayerSettings::RESOURCE)
+		s.bonus = PlayerSettings::RANDOM;
+}
+
+void CVCMIServer::setCampaignMap(int mapId)
+{
+	campaignMap = mapId;
+	si->difficulty = si->campState->camp->scenarios[mapId].difficulty;
+	campaignBonus = -1;
+	updateStartInfoOnMapChange(si->campState->getMapInfo(mapId));
+}
+
+void CVCMIServer::setCampaignBonus(int bonusId)
+{
+	campaignBonus = bonusId;
+
+	const CCampaignScenario & scenario = si->campState->camp->scenarios[campaignMap];
+	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
+	if(bonDescs[bonusId].type == CScenarioTravel::STravelBonus::HERO)
+	{
+		for(auto & elem : si->playerInfos)
+		{
+			if(elem.first == PlayerColor(bonDescs[bonusId].info1))
+				setPlayerConnectedId(elem.second, 1);
+			else
+				setPlayerConnectedId(elem.second, PlayerSettings::PLAYER_AI);
+		}
+	}
+}
+
+void CVCMIServer::optionNextHero(PlayerColor player, int dir)
+{
+	PlayerSettings & s = si->playerInfos[player];
+	if(s.castle < 0 || s.hero == PlayerSettings::NONE)
+		return;
+
+	if(s.hero == PlayerSettings::RANDOM) // first/last available
+	{
+		int max = VLC->heroh->heroes.size(),
+			min = 0;
+		s.hero = nextAllowedHero(player, min, max, 0, dir);
+	}
+	else
+	{
+		if(dir > 0)
+			s.hero = nextAllowedHero(player, s.hero, VLC->heroh->heroes.size(), 1, dir);
+		else
+			s.hero = nextAllowedHero(player, -1, s.hero, 1, dir); // min needs to be -1 -- hero at index 0 would be skipped otherwise
+	}
+}
+
+int CVCMIServer::nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir)
+{
+	if(dir > 0)
+	{
+		for(int i = min + incl; i <= max - incl; i++)
+			if(canUseThisHero(player, i))
+				return i;
+	}
+	else
+	{
+		for(int i = max - incl; i >= min + incl; i--)
+			if(canUseThisHero(player, i))
+				return i;
+	}
+	return -1;
+}
+
+void CVCMIServer::optionNextBonus(PlayerColor player, int dir)
+{
+	PlayerSettings & s = si->playerInfos[player];
+	PlayerSettings::Ebonus & ret = s.bonus = static_cast<PlayerSettings::Ebonus>(static_cast<int>(s.bonus) + dir);
+
+	if(s.hero == PlayerSettings::NONE &&
+		!getPlayerInfo(player.getNum()).heroesNames.size() &&
+		ret == PlayerSettings::ARTIFACT) //no hero - can't be artifact
+	{
+		if(dir < 0)
+			ret = PlayerSettings::RANDOM;
+		else
+			ret = PlayerSettings::GOLD;
+	}
+
+	if(ret > PlayerSettings::RESOURCE)
+		ret = PlayerSettings::RANDOM;
+	if(ret < PlayerSettings::RANDOM)
+		ret = PlayerSettings::RESOURCE;
+
+	if(s.castle == PlayerSettings::RANDOM && ret == PlayerSettings::RESOURCE) //random castle - can't be resource
+	{
+		if(dir < 0)
+			ret = PlayerSettings::GOLD;
+		else
+			ret = PlayerSettings::RANDOM;
+	}
+}
+
+bool CVCMIServer::canUseThisHero(PlayerColor player, int ID)
+{
+	return VLC->heroh->heroes.size() > ID
+		&& si->playerInfos[player].castle == VLC->heroh->heroes[ID]->heroClass->faction
+		&& !vstd::contains(getUsedHeroes(), ID)
+		&& mi->mapHeader->allowedHeroes[ID];
+}
+
+std::vector<int> CVCMIServer::getUsedHeroes()
+{
+	std::vector<int> heroIds;
+	for(auto & p : si->playerInfos)
+	{
+		const auto & heroes = getPlayerInfo(p.first.getNum()).heroesNames;
+		for(auto & hero : heroes)
+			if(hero.heroId >= 0) //in VCMI map format heroId = -1 means random hero
+				heroIds.push_back(hero.heroId);
+
+		if(p.second.hero != PlayerSettings::RANDOM)
+			heroIds.push_back(p.second.hero);
+	}
+	return heroIds;
+}
+
+ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const
+{
+	for(auto i = playerNames.cbegin(); i != playerNames.cend(); i++)
+	{
+		if(!si->getPlayersSettings(i->first))
+			return i->first;
+	}
+
+	return 0;
+}
+
+#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID)
+void handleLinuxSignal(int sig)
+{
+	const int STACKTRACE_SIZE = 100;
+	void * buffer[STACKTRACE_SIZE];
+	int ptrCount = backtrace(buffer, STACKTRACE_SIZE);
+	char * * strings;
+
+	logGlobal->error("Error: signal %d :", sig);
+	strings = backtrace_symbols(buffer, ptrCount);
+	if(strings == nullptr)
+	{
+		logGlobal->error("There are no symbols.");
+	}
+	else
+	{
+		for(int i = 0; i < ptrCount; ++i)
+		{
+			logGlobal->error(strings[i]);
+		}
+		free(strings);
+	}
+
+	_exit(EXIT_FAILURE);
+}
+#endif
+
+static void handleCommandOptions(int argc, 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")
+	("uuid", po::value<std::string>(), "")
+	("enable-shm-uuid", "use UUID for shared memory identifier")
+	("enable-shm", "enable usage of shared memory")
+	("port", po::value<ui16>(), "port at which server will listen to connections from client");
+
+	if(argc > 1)
+	{
+		try
+		{
+			po::store(po::parse_command_line(argc, argv, opts), options);
+		}
+		catch(std::exception & e)
+		{
+			std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl;
+		}
+	}
+
+	po::notify(options);
+	if(options.count("help"))
+	{
+		auto time = std::time(0);
+		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);
+	}
+}
+
+int main(int argc, char * argv[])
+{
+#ifndef VCMI_ANDROID
+	// 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
+	// Installs a sig sev segmentation violation handler
+	// to log stacktrace
+#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID)
+	signal(SIGSEGV, handleLinuxSignal);
+#endif
+
+	console = new CConsoleHandler();
+	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Server_log.txt", console);
+	logConfig.configureDefault();
+	logGlobal->info(NAME);
+
+	boost::program_options::variables_map opts;
+	handleCommandOptions(argc, argv, opts);
+	preinitDLL(console);
+	settings.init();
+	logConfig.configure();
+
+	loadDLLClasses();
+	srand((ui32)time(nullptr));
+	try
+	{
+		boost::asio::io_service io_service;
+		CVCMIServer server(opts);
+
+		try
+		{
+			while(server.state != EServerState::SHUTDOWN)
+			{
+				server.run();
+			}
+			io_service.run();
+		}
+		catch(boost::system::system_error & e) //for boost errors just log, not crash - probably client shut down connection
+		{
+			logNetwork->error(e.what());
+			server.state = EServerState::SHUTDOWN;
+		}
+		catch(...)
+		{
+			handleException();
+		}
+	}
+	catch(boost::system::system_error & e)
+	{
+		logNetwork->error(e.what());
+		//catch any startup errors (e.g. can't access port) errors
+		//and return non-zero status so client can detect error
+		throw;
+	}
+#ifdef VCMI_ANDROID
+	CAndroidVMHelper envHelper;
+	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
+#endif
+	vstd::clear_pointer(VLC);
+	CResourceHandler::clear();
+	return 0;
+}
+
+#ifdef VCMI_ANDROID
+void CVCMIServer::create()
+{
+	const char * foo[1] = {"android-server"};
+	main(1, const_cast<char **>(foo));
+}
+#endif

+ 105 - 112
server/CVCMIServer.h

@@ -1,112 +1,105 @@
-/*
- * CVCMIServer.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/program_options.hpp>
-
-class CMapInfo;
-
-class CConnection;
-struct CPackForSelectionScreen;
-class CGameHandler;
-struct SharedMemory;
-
-namespace boost
-{
-	namespace asio
-	{
-		namespace ip
-		{
-			class tcp;
-		}
-
-#if BOOST_VERSION >= 106600  // Boost version >= 1.66
-		class io_context;
-		typedef io_context io_service;
-#else
-		class io_service;
-#endif
-
-		template <typename Protocol> class stream_socket_service;
-		template <typename Protocol,typename StreamSocketService>
-		class basic_stream_socket;
-
-		template <typename Protocol> class socket_acceptor_service;
-		template <typename Protocol,typename SocketAcceptorService>
-		class basic_socket_acceptor;
-	}
-};
-
-typedef boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::socket_acceptor_service<boost::asio::ip::tcp> > TAcceptor;
-typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::stream_socket_service<boost::asio::ip::tcp>  > TSocket;
-
-class CVCMIServer
-{
-	ui16 port;
-	boost::asio::io_service *io;
-	TAcceptor * acceptor;
-	SharedMemory * shared;
-
-	CConnection *firstConnection;
-public:
-	CVCMIServer();
-	~CVCMIServer();
-
-	void start();
-	std::shared_ptr<CGameHandler> initGhFromHostingConnection(CConnection &c);
-
-	void newGame();
-	void loadGame();
-	void newPregame();
-
-#ifdef VCMI_ANDROID
-    static void create();
-#endif
-};
-
-struct StartInfo;
-class CPregameServer
-{
-public:
-	CConnection *host;
-	int listeningThreads;
-	std::set<CConnection *> connections;
-	std::list<CPackForSelectionScreen*> toAnnounce;
-	boost::recursive_mutex mx;
-
-	TAcceptor *acceptor;
-	TSocket *upcomingConnection;
-
-	const CMapInfo *curmap;
-	StartInfo *curStartInfo;
-
-	CPregameServer(CConnection *Host, TAcceptor *Acceptor = nullptr);
-	~CPregameServer();
-
-	void run();
-
-	void processPack(CPackForSelectionScreen * pack);
-	void handleConnection(CConnection *cpc);
-	void connectionAccepted(const boost::system::error_code& ec);
-	void initConnection(CConnection *c);
-
-	void start_async_accept();
-
-	enum { INVALID, RUNNING, ENDING_WITHOUT_START, ENDING_AND_STARTING_GAME
-	} state;
-
-	void announceTxt(const std::string &txt, const std::string &playerName = "system");
-	void announcePack(const CPackForSelectionScreen &pack);
-
-	void sendPack(CConnection * pc, const CPackForSelectionScreen & pack);
-	void startListeningThread(CConnection * pc);
-};
-
-extern boost::program_options::variables_map cmdLineOptions;
+/*
+ * CVCMIServer.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/serializer/Connection.h"
+#include "../lib/StartInfo.h"
+
+#include <boost/program_options.hpp>
+
+class CMapInfo;
+
+struct CPackForLobby;
+class CGameHandler;
+struct SharedMemory;
+
+struct StartInfo;
+struct LobbyInfo;
+class PlayerSettings;
+class PlayerColor;
+
+template<typename T> class CApplier;
+class CBaseForServerApply;
+class CBaseForGHApply;
+
+enum class EServerState : ui8
+{
+	LOBBY,
+	GAMEPLAY,
+	GAMEPLAY_ENDED,
+	SHUTDOWN
+};
+
+class CVCMIServer : public LobbyInfo
+{
+	std::atomic<bool> restartGameplay; // FIXME: this is just a hack
+	std::shared_ptr<boost::asio::io_service> io;
+	std::shared_ptr<TAcceptor> acceptor;
+	std::shared_ptr<TSocket> upcomingConnection;
+	std::list<CPackForLobby *> announceQueue;
+	boost::recursive_mutex mx;
+	std::shared_ptr<CApplier<CBaseForServerApply>> applier;
+
+public:
+	std::shared_ptr<CGameHandler> gh;
+	std::atomic<EServerState> state;
+	ui16 port;
+
+	boost::program_options::variables_map cmdLineOptions;
+	std::set<std::shared_ptr<CConnection>> connections;
+	std::atomic<int> currentClientId;
+	std::atomic<ui8> currentPlayerId;
+	std::shared_ptr<CConnection> hostClient;
+
+	CVCMIServer(boost::program_options::variables_map & opts);
+	~CVCMIServer();
+	void run();
+	void prepareToStartGame();
+	void startGameImmidiately();
+
+	void startAsyncAccept();
+	void connectionAccepted(const boost::system::error_code & ec);
+	void threadHandleClient(std::shared_ptr<CConnection> c);
+	void threadAnnounceLobby();
+	void handleReceivedPack(CPackForLobby * pack);
+
+	void announcePack(CPackForLobby * pack);
+	bool passHost(int toConnectionId);
+
+	void announceTxt(const std::string & txt, const std::string & playerName = "system");
+	void addToAnnounceQueue(CPackForLobby * pack);
+
+	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 clientDisconnected(std::shared_ptr<CConnection> c);
+
+	void updateAndPropagateLobbyState();
+
+	// Work with LobbyInfo
+	void setPlayer(PlayerColor clickedColor);
+	void optionNextHero(PlayerColor player, int dir); //dir == -1 or +1
+	int nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir);
+	bool canUseThisHero(PlayerColor player, int ID);
+	std::vector<int> getUsedHeroes();
+	void optionNextBonus(PlayerColor player, int dir); //dir == -1 or +1
+	void optionNextCastle(PlayerColor player, int dir); //dir == -1 or +
+
+	// Campaigns
+	void setCampaignMap(int mapId);
+	void setCampaignBonus(int bonusId);
+
+	ui8 getIdOfFirstUnallocatedPlayer() const;
+
+#ifdef VCMI_ANDROID
+	static void create();
+#endif
+};

+ 250 - 0
server/NetPacksLobbyServer.cpp

@@ -0,0 +1,250 @@
+/*
+ * NetPacksLobbyServer.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 "CGameHandler.h"
+
+#include "../lib/NetPacksLobby.h"
+#include "../lib/serializer/Connection.h"
+#include "../lib/StartInfo.h"
+
+// Campaigns
+#include "../lib/mapping/CCampaignHandler.h"
+#include "../lib/mapping/CMapService.h"
+#include "../lib/mapping/CMapInfo.h"
+
+bool CLobbyPackToServer::checkClientPermissions(CVCMIServer * srv) const
+{
+	return srv->isClientHost(c->connectionID);
+}
+
+void CLobbyPackToServer::applyOnServerAfterAnnounce(CVCMIServer * srv)
+{
+	// Propogate options after every CLobbyPackToServer
+	srv->updateAndPropagateLobbyState();
+}
+
+bool LobbyClientConnected::checkClientPermissions(CVCMIServer * srv) const
+{
+	return true;
+}
+
+bool LobbyClientConnected::applyOnServer(CVCMIServer * srv)
+{
+	srv->clientConnected(c, names, uuid, mode);
+	// Server need to pass some data to newly connected client
+	clientId = c->connectionID;
+	mode = srv->si->mode;
+	hostClientId = srv->hostClientId;
+	return true;
+}
+
+void LobbyClientConnected::applyOnServerAfterAnnounce(CVCMIServer * srv)
+{
+	// FIXME: we need to avoid senting something to client that not yet get answer for LobbyClientConnected
+	// Until UUID set we only pass LobbyClientConnected to this client
+	c->uuid = uuid;
+	srv->updateAndPropagateLobbyState();
+}
+
+bool LobbyClientDisconnected::checkClientPermissions(CVCMIServer * srv) const
+{
+	if(clientId != c->connectionID)
+		return false;
+
+	if(shutdownServer)
+	{
+		if(!srv->cmdLineOptions.count("run-by-client"))
+			return false;
+
+		if(c->uuid != srv->cmdLineOptions["uuid"].as<std::string>())
+			return false;
+	}
+
+	return true;
+}
+
+bool LobbyClientDisconnected::applyOnServer(CVCMIServer * srv)
+{
+	srv->clientDisconnected(c);
+	return true;
+}
+
+void LobbyClientDisconnected::applyOnServerAfterAnnounce(CVCMIServer * srv)
+{
+	if(c->isOpen())
+	{
+		boost::unique_lock<boost::mutex> lock(*c->mutexWrite);
+		c->close();
+		c->connected = false;
+	}
+
+	if(shutdownServer)
+	{
+		logNetwork->info("Client requested shutdown, server will close itself...");
+		srv->state = EServerState::SHUTDOWN;
+		return;
+	}
+	else if(srv->connections.empty())
+	{
+		logNetwork->error("Last connection lost, server will close itself...");
+		srv->state = EServerState::SHUTDOWN;
+	}
+	else if(c == srv->hostClient)
+	{
+		auto ph = new LobbyChangeHost();
+		auto newHost = *RandomGeneratorUtil::nextItem(srv->connections, CRandomGenerator::getDefault());
+		ph->newHostConnectionId = newHost->connectionID;
+		srv->addToAnnounceQueue(ph);
+	}
+	srv->updateAndPropagateLobbyState();
+}
+
+bool LobbyChatMessage::checkClientPermissions(CVCMIServer * srv) const
+{
+	return true;
+}
+
+bool LobbySetMap::applyOnServer(CVCMIServer * srv)
+{
+	srv->updateStartInfoOnMapChange(mapInfo, mapGenOpts);
+	return true;
+}
+
+bool LobbySetCampaign::applyOnServer(CVCMIServer * srv)
+{
+	srv->si->mapname = ourCampaign->camp->header.filename;
+	srv->si->mode = StartInfo::CAMPAIGN;
+	srv->si->campState = ourCampaign;
+	srv->si->turnTime = 0;
+	bool isCurrentMapConquerable = ourCampaign->currentMap && ourCampaign->camp->conquerable(*ourCampaign->currentMap);
+	for(int i = 0; i < ourCampaign->camp->scenarios.size(); i++)
+	{
+		if(ourCampaign->camp->conquerable(i))
+		{
+			if(!isCurrentMapConquerable || (isCurrentMapConquerable && i == *ourCampaign->currentMap))
+			{
+				srv->setCampaignMap(i);
+			}
+		}
+	}
+	return true;
+}
+
+bool LobbySetCampaignMap::applyOnServer(CVCMIServer * srv)
+{
+	srv->setCampaignMap(mapId);
+	return true;
+}
+
+bool LobbySetCampaignBonus::applyOnServer(CVCMIServer * srv)
+{
+	srv->setCampaignBonus(bonusId);
+	return true;
+}
+
+bool LobbyGuiAction::checkClientPermissions(CVCMIServer * srv) const
+{
+	return srv->isClientHost(c->connectionID);
+}
+
+bool LobbyStartGame::checkClientPermissions(CVCMIServer * srv) const
+{
+	return srv->isClientHost(c->connectionID);
+}
+
+bool LobbyStartGame::applyOnServer(CVCMIServer * srv)
+{
+	try
+	{
+		srv->verifyStateBeforeStart(true);
+	}
+	catch(...)
+	{
+		return false;
+	}
+	// Server will prepare gamestate and we announce StartInfo to clients
+	srv->prepareToStartGame();
+	initializedStartInfo = std::make_shared<StartInfo>(*srv->gh->getStartInfo(true));
+	return true;
+}
+
+void LobbyStartGame::applyOnServerAfterAnnounce(CVCMIServer * srv)
+{
+	srv->startGameImmidiately();
+}
+
+bool LobbyChangeHost::checkClientPermissions(CVCMIServer * srv) const
+{
+	return srv->isClientHost(c->connectionID);
+}
+
+bool LobbyChangeHost::applyOnServer(CVCMIServer * srv)
+{
+	return true;
+}
+
+bool LobbyChangeHost::applyOnServerAfterAnnounce(CVCMIServer * srv)
+{
+	return srv->passHost(newHostConnectionId);
+}
+
+bool LobbyChangePlayerOption::checkClientPermissions(CVCMIServer * srv) const
+{
+	if(srv->isClientHost(c->connectionID))
+		return true;
+
+	if(vstd::contains(srv->getAllClientPlayers(c->connectionID), color))
+		return true;
+
+	return false;
+}
+
+bool LobbyChangePlayerOption::applyOnServer(CVCMIServer * srv)
+{
+	switch(what)
+	{
+	case TOWN:
+		srv->optionNextCastle(color, direction);
+		break;
+	case HERO:
+		srv->optionNextHero(color, direction);
+		break;
+	case BONUS:
+		srv->optionNextBonus(color, direction);
+		break;
+	}
+	return true;
+}
+
+bool LobbySetPlayer::applyOnServer(CVCMIServer * srv)
+{
+	srv->setPlayer(clickedColor);
+	return true;
+}
+
+bool LobbySetTurnTime::applyOnServer(CVCMIServer * srv)
+{
+	srv->si->turnTime = turnTime;
+	return true;
+}
+
+bool LobbySetDifficulty::applyOnServer(CVCMIServer * srv)
+{
+	srv->si->difficulty = vstd::abetween(difficulty, 0, 4);
+	return true;
+}
+
+bool LobbyForceSetPlayer::applyOnServer(CVCMIServer * srv)
+{
+	srv->si->playerInfos[targetPlayerColor].connectedPlayerIDs.insert(targetConnectedPlayer);
+	return true;
+}

+ 10 - 21
server/NetPacksServer.cpp

@@ -31,7 +31,7 @@ void CPackForServer::throwNotAllowedAction()
 	if(c)
 	{
 		SystemMessage temp_message("You are not allowed to perform this action!");
-		*c << &temp_message;
+		c->sendPack(&temp_message);
 	}
 	logNetwork->error("Player is not allowed to perform this action!");
 	throw ExceptionNotAllowedAction();
@@ -45,7 +45,7 @@ void CPackForServer::wrongPlayerMessage(CGameHandler * gh, PlayerColor expectedp
 	if(c)
 	{
 		SystemMessage temp_message(oss.str());
-		*c << &temp_message;
+		c->sendPack(&temp_message);
 	}
 }
 
@@ -92,18 +92,6 @@ bool CommitPackage::applyGh(CGameHandler * gh)
 	return true;
 }
 
-bool CloseServer::applyGh(CGameHandler * gh)
-{
-	gh->close();
-	return true;
-}
-
-bool LeaveGame::applyGh(CGameHandler * gh)
-{
-	gh->playerLeftGame(c->connectionID);
-	return true;
-}
-
 bool EndTurn::applyGh(CGameHandler * gh)
 {
 	PlayerColor player = GS(gh)->currentPlayer;
@@ -292,7 +280,7 @@ bool QueryReply::applyGh(CGameHandler * gh)
 	auto playerToConnection = gh->connections.find(player);
 	if(playerToConnection == gh->connections.end())
 		throwAndCompain(gh, "No such player!");
-	if(playerToConnection->second != c)
+	if(!vstd::contains(playerToConnection->second, c))
 		throwAndCompain(gh, "Message came from wrong connection!");
 	if(qid == QueryID(-1))
 		throwAndCompain(gh, "Cannot answer the query with id -1!");
@@ -312,17 +300,18 @@ bool MakeAction::applyGh(CGameHandler * gh)
 		if(ba.actionType != EActionType::WALK && ba.actionType != EActionType::END_TACTIC_PHASE
 			&& ba.actionType != EActionType::RETREAT && ba.actionType != EActionType::SURRENDER)
 			throwNotAllowedAction();
-		if(gh->connections[b->sides[b->tacticsSide].color] != c)
+		if(!vstd::contains(gh->connections[b->sides[b->tacticsSide].color], c))
 			throwNotAllowedAction();
 	}
 	else
 	{
 		auto active = b->battleActiveUnit();
-		if(!active) throwNotAllowedAction();
+		if(!active)
+			throwNotAllowedAction();
 		auto unitOwner = b->battleGetOwner(active);
-		if(gh->connections[unitOwner] != c) throwNotAllowedAction();
+		if(!vstd::contains(gh->connections[unitOwner], c))
+			throwNotAllowedAction();
 	}
-
 	return gh->makeBattleAction(ba);
 }
 
@@ -337,7 +326,7 @@ bool MakeCustomAction::applyGh(CGameHandler * gh)
 	if(!active)
 		throwNotAllowedAction();
 	auto unitOwner = b->battleGetOwner(active);
-	if(gh->connections[unitOwner] != c)
+	if(!vstd::contains(gh->connections[unitOwner], c))
 		throwNotAllowedAction();
 	if(ba.actionType != EActionType::HERO_SPELL)
 		throwNotAllowedAction();
@@ -373,7 +362,7 @@ bool PlayerMessage::applyGh(CGameHandler * gh)
 	if(!player.isSpectator()) // TODO: clearly not a great way to verify permissions
 	{
 		throwOnWrongPlayer(gh, player);
-		if(gh->getPlayerAt(c) != player)
+		if(gh->getPlayerAt(this->c) != player)
 			throwNotAllowedAction();
 	}
 	gh->playerMessage(player, text, currObj);

+ 1 - 1
test/game/CGameStateTest.cpp

@@ -108,7 +108,7 @@ public:
 
 			PlayerSettings & pset = si.playerInfos[PlayerColor(i)];
 			pset.color = PlayerColor(i);
-			pset.playerID = i;
+			pset.connectedPlayerIDs.insert(i);
 			pset.name = "Player";
 
 			pset.castle = pinfo.defaultCastle();

Vissa filer visades inte eftersom för många filer har ändrats