Переглянути джерело

Merge pull request #402 from vcmi/feature/multiplayer

Refactoring of all pre-gameplay UI and networking code.
This will break some things, but I'll be able to fix them without constant rebasing.
ArseniyShestakov 7 роки тому
батько
коміт
1a6f456ac0
93 змінених файлів з 8775 додано та 7648 видалено
  1. 4 12
      CCallback.cpp
  2. 3 4
      CCallback.h
  3. 5 0
      ChangeLog
  4. 3 0
      Global.h
  5. 3 1
      client/CGameInfo.cpp
  6. 1 1
      client/CGameInfo.h
  7. 95 207
      client/CMT.cpp
  8. 0 3
      client/CMT.h
  9. 31 2
      client/CMakeLists.txt
  10. 36 71
      client/CPlayerInterface.cpp
  11. 1 15
      client/CPlayerInterface.h
  12. 0 4374
      client/CPreGame.cpp
  13. 0 650
      client/CPreGame.h
  14. 674 0
      client/CServerHandler.cpp
  15. 145 0
      client/CServerHandler.h
  16. 229 544
      client/Client.cpp
  17. 72 125
      client/Client.h
  18. 6 14
      client/NetPacksClient.cpp
  19. 142 0
      client/NetPacksLobbyClient.cpp
  20. 3 3
      client/gui/CGuiHandler.cpp
  21. 15 5
      client/gui/CGuiHandler.h
  22. 2 0
      client/gui/SDL_Extensions.cpp
  23. 6 0
      client/gui/SDL_Extensions.h
  24. 534 0
      client/lobby/CBonusSelection.cpp
  25. 93 0
      client/lobby/CBonusSelection.h
  26. 201 0
      client/lobby/CLobbyScreen.cpp
  27. 35 0
      client/lobby/CLobbyScreen.h
  28. 97 0
      client/lobby/CSavingScreen.cpp
  29. 32 0
      client/lobby/CSavingScreen.h
  30. 59 0
      client/lobby/CScenarioInfoScreen.cpp
  31. 30 0
      client/lobby/CScenarioInfoScreen.h
  32. 407 0
      client/lobby/CSelectionBase.cpp
  33. 146 0
      client/lobby/CSelectionBase.h
  34. 537 0
      client/lobby/OptionsTab.cpp
  35. 127 0
      client/lobby/OptionsTab.h
  36. 300 0
      client/lobby/RandomMapTab.cpp
  37. 58 0
      client/lobby/RandomMapTab.h
  38. 664 0
      client/lobby/SelectionTab.cpp
  39. 98 0
      client/lobby/SelectionTab.h
  40. 156 0
      client/mainmenu/CCampaignScreen.cpp
  41. 55 0
      client/mainmenu/CCampaignScreen.h
  42. 566 0
      client/mainmenu/CMainMenu.cpp
  43. 183 0
      client/mainmenu/CMainMenu.h
  44. 64 0
      client/mainmenu/CPrologEpilogVideo.cpp
  45. 31 0
      client/mainmenu/CPrologEpilogVideo.h
  46. 59 0
      client/mainmenu/CreditsScreen.cpp
  47. 27 0
      client/mainmenu/CreditsScreen.h
  48. 1 1
      client/widgets/AdventureMapClasses.cpp
  49. 28 1
      client/widgets/TextControls.cpp
  50. 4 0
      client/widgets/TextControls.h
  51. 12 8
      client/windows/CAdvmapInterface.cpp
  52. 14 5
      client/windows/GUIClasses.cpp
  53. 12 0
      config/schemas/settings.json
  54. 44 34
      lib/CGameState.cpp
  55. 6 1
      lib/CGameState.h
  56. 3 1
      lib/CMakeLists.txt
  57. 2 0
      lib/GameConstants.h
  58. 3 0
      lib/IGameCallback.cpp
  59. 1 1
      lib/IGameCallback.h
  60. 64 204
      lib/NetPacks.h
  61. 12 6
      lib/NetPacksBase.h
  62. 37 36
      lib/NetPacksLib.cpp
  63. 311 0
      lib/NetPacksLobby.h
  64. 195 0
      lib/StartInfo.cpp
  65. 91 28
      lib/StartInfo.h
  66. 2 0
      lib/mapObjects/CGHeroInstance.h
  67. 1 1
      lib/mapObjects/CGPandoraBox.cpp
  68. 6 0
      lib/mapObjects/CGTownInstance.cpp
  69. 1 0
      lib/mapObjects/CGTownInstance.h
  70. 107 13
      lib/mapping/CCampaignHandler.cpp
  71. 30 7
      lib/mapping/CCampaignHandler.h
  72. 10 5
      lib/mapping/CMap.cpp
  73. 7 4
      lib/mapping/CMap.h
  74. 136 47
      lib/mapping/CMapInfo.cpp
  75. 17 10
      lib/mapping/CMapInfo.h
  76. 1 1
      lib/registerTypes/RegisterTypes.cpp
  77. 31 23
      lib/registerTypes/RegisterTypes.h
  78. 37 37
      lib/registerTypes/TypesLobbyPacks.cpp
  79. 2 1
      lib/rmg/CMapGenOptions.cpp
  80. 1 1
      lib/serializer/CSerializer.h
  81. 43 56
      lib/serializer/Connection.cpp
  82. 15 15
      lib/serializer/Connection.h
  83. 1 1
      lib/spells/Magic.h
  84. 101 230
      server/CGameHandler.cpp
  85. 19 18
      server/CGameHandler.h
  86. 1 0
      server/CMakeLists.txt
  87. 960 682
      server/CVCMIServer.cpp
  88. 105 112
      server/CVCMIServer.h
  89. 250 0
      server/NetPacksLobbyServer.cpp
  90. 10 21
      server/NetPacksServer.cpp
  91. 3 3
      test/game/CGameStateTest.cpp
  92. 2 2
      test/mock/mock_IGameCallback.cpp
  93. 1 1
      test/mock/mock_IGameCallback.h

+ 4 - 12
CCallback.cpp

@@ -95,7 +95,7 @@ void CCallback::endTurn()
 {
 {
 	logGlobal->trace("Player %d ended his turn.", player.get().getNum());
 	logGlobal->trace("Player %d ended his turn.", player.get().getNum());
 	EndTurn pack;
 	EndTurn pack;
-	sendRequest(&pack); //report that we ended turn
+	sendRequest(&pack);
 }
 }
 int CCallback::swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 int CCallback::swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 {
 {
@@ -174,7 +174,7 @@ int CBattleCallback::battleMakeAction(BattleAction* action)
 	return 0;
 	return 0;
 }
 }
 
 
-int CBattleCallback::sendRequest(const CPack *request)
+int CBattleCallback::sendRequest(const CPackForServer * request)
 {
 {
 	int requestID = cl->sendRequest(request, *player);
 	int requestID = cl->sendRequest(request, *player);
 	if(waitTillRealize)
 	if(waitTillRealize)
@@ -262,8 +262,8 @@ void CCallback::save( const std::string &fname )
 void CCallback::sendMessage(const std::string &mess, const CGObjectInstance * currentObject)
 void CCallback::sendMessage(const std::string &mess, const CGObjectInstance * currentObject)
 {
 {
 	ASSERT_IF_CALLED_WITH_PLAYER
 	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 )
 void CCallback::buildBoat( const IShipyard *obj )
@@ -327,14 +327,6 @@ void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int
 	sendRequest(&cas);
 	sendRequest(&cas);
 }
 }
 
 
-void CCallback::unregisterAllInterfaces()
-{
-	for (auto& pi : cl->playerint)
-		pi.second->finish();
-	cl->playerint.clear();
-	cl->battleints.clear();
-}
-
 int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
 {
 {
 	if(s1->getCreature(p1) == s2->getCreature(p2))
 	if(s1->getCreature(p1) == s2->getCreature(p2))

+ 3 - 4
CCallback.h

@@ -78,12 +78,12 @@ public:
 	virtual void buildBoat(const IShipyard *obj) = 0;
 	virtual void buildBoat(const IShipyard *obj) = 0;
 };
 };
 
 
-struct CPack;
+struct CPackForServer;
 
 
 class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback
 class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback
 {
 {
 protected:
 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;
 	CClient *cl;
 
 
 public:
 public:
@@ -95,6 +95,7 @@ public:
 	friend class CClient;
 	friend class CClient;
 };
 };
 
 
+class CPlayerInterface;
 class CCallback : public CPlayerSpecificInfoCallback, public IGameActionCallback, public CBattleCallback
 class CCallback : public CPlayerSpecificInfoCallback, public IGameActionCallback, public CBattleCallback
 {
 {
 public:
 public:
@@ -114,8 +115,6 @@ public:
 	void unregisterGameInterface(std::shared_ptr<IGameEventsReceiver> gameEvents);
 	void unregisterGameInterface(std::shared_ptr<IGameEventsReceiver> gameEvents);
 	void unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents);
 	void unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents);
 
 
-	void unregisterAllInterfaces(); //stops delivering information about game events to player interfaces -> can be called ONLY after victory/loss
-
 //commands
 //commands
 	bool moveHero(const CGHeroInstance *h, int3 dst, bool transit = false) override; //dst must be free, neighbouring tile (this function can move hero only by one tile)
 	bool moveHero(const CGHeroInstance *h, int3 dst, bool transit = false) override; //dst must be free, neighbouring tile (this function can move hero only by one tile)
 	bool teleportHero(const CGHeroInstance *who, const CGTownInstance *where);
 	bool teleportHero(const CGHeroInstance *who, const CGTownInstance *where);

+ 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
 - 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
 - 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:
 SPELLS:
 * Implemented cumulative effects for spells
 * Implemented cumulative effects for spells
 
 

+ 3 - 0
Global.h

@@ -172,6 +172,9 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #if defined(_MSC_VER) && (_MSC_VER == 1900 || _MSC_VER == 1910 || _MSC_VER == 1911)
 #if defined(_MSC_VER) && (_MSC_VER == 1900 || _MSC_VER == 1910 || _MSC_VER == 1911)
 #define BOOST_NO_CXX11_VARIADIC_TEMPLATES //Variadic templates are buggy in VS2015 and VS2017, so turn this off to avoid compile errors
 #define BOOST_NO_CXX11_VARIADIC_TEMPLATES //Variadic templates are buggy in VS2015 and VS2017, so turn this off to avoid compile errors
 #endif
 #endif
+#if BOOST_VERSION >= 106600
+#define BOOST_ASIO_ENABLE_OLD_SERVICES
+#endif
 
 
 #include <boost/algorithm/string.hpp>
 #include <boost/algorithm/string.hpp>
 #include <boost/any.hpp>
 #include <boost/any.hpp>

+ 3 - 1
client/CGameInfo.cpp

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

+ 1 - 1
client/CGameInfo.h

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

+ 95 - 207
client/CMT.cpp

@@ -20,7 +20,8 @@
 
 
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/FileStream.h"
 #include "../lib/filesystem/FileStream.h"
-#include "CPreGame.h"
+#include "mainmenu/CMainMenu.h"
+#include "lobby/CSelectionBase.h"
 #include "windows/CCastleInterface.h"
 #include "windows/CCastleInterface.h"
 #include "../lib/CConsoleHandler.h"
 #include "../lib/CConsoleHandler.h"
 #include "gui/CCursorHandler.h"
 #include "gui/CCursorHandler.h"
@@ -54,6 +55,12 @@
 #include "../lib/StringConstants.h"
 #include "../lib/StringConstants.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/CPlayerState.h"
 #include "gui/CAnimation.h"
 #include "gui/CAnimation.h"
+#include "../lib/serializer/Connection.h"
+#include "CServerHandler.h"
+
+#include <boost/asio.hpp>
+
+#include "mainmenu/CPrologEpilogVideo.h"
 
 
 #ifdef VCMI_WINDOWS
 #ifdef VCMI_WINDOWS
 #include "SDL_syswm.h"
 #include "SDL_syswm.h"
@@ -75,7 +82,6 @@ namespace bfs = boost::filesystem;
 std::string NAME_AFFIX = "client";
 std::string NAME_AFFIX = "client";
 std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
 std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
 CGuiHandler GH;
 CGuiHandler GH;
-static CClient *client = nullptr;
 
 
 int preferredDriverIndex = -1;
 int preferredDriverIndex = -1;
 SDL_Window * mainWindow = nullptr;
 SDL_Window * mainWindow = nullptr;
@@ -91,7 +97,6 @@ SDL_Surface *screen = nullptr, //main screen surface
 std::queue<SDL_Event> events;
 std::queue<SDL_Event> events;
 boost::mutex eventsM;
 boost::mutex eventsM;
 
 
-CondSh<bool> serverAlive(false);
 static po::variables_map vm;
 static po::variables_map vm;
 
 
 //static bool setResolution = false; //set by event handling thread after resolution is adjusted
 //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 dispose();
 void playIntro();
 void playIntro();
 static void mainLoop();
 static void mainLoop();
-//void requestChangingResolution();
-void startGame(StartInfo * options, CConnection *serv = nullptr);
-void endGame();
 
 
 #ifndef VCMI_WINDOWS
 #ifndef VCMI_WINDOWS
 #ifndef _GNU_SOURCE
 #ifndef _GNU_SOURCE
@@ -113,54 +115,6 @@ void endGame();
 #include <getopt.h>
 #include <getopt.h>
 #endif
 #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()
 void init()
 {
 {
 	CStopWatch tmh, pomtime;
 	CStopWatch tmh, pomtime;
@@ -243,15 +197,15 @@ int main(int argc, char * argv[])
 		("version,v", "display version information and exit")
 		("version,v", "display version information and exit")
 		("disable-shm", "force disable shared memory usage")
 		("disable-shm", "force disable shared memory usage")
 		("enable-shm-uuid", "use UUID for shared memory identifier")
 		("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>(), "")
 		("testmap", po::value<std::string>(), "")
+		("testsave", po::value<std::string>(), "")
 		("spectate,s", "enable spectator interface for AI-only games")
 		("spectate,s", "enable spectator interface for AI-only games")
 		("spectate-ignore-hero", "wont follow heroes on adventure map")
 		("spectate-ignore-hero", "wont follow heroes on adventure map")
 		("spectate-hero-speed", po::value<int>(), "hero movement speed 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-battle-speed", po::value<int>(), "battle animation speed for spectator")
 		("spectate-skip-battle", "skip battles in spectator view")
 		("spectate-skip-battle", "skip battles in spectator view")
 		("spectate-skip-battle-result", "skip battle result window")
 		("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")
 		("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")
 		("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")
 		("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")
 		("disable-video", "disable video player")
 		("nointro,i", "skips intro movies")
 		("nointro,i", "skips intro movies")
 		("donotstartserver,d","do not attempt to start server and just connect to it instead server")
 		("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")
 		("serverport", po::value<si64>(), "override port specified in config file")
 		("saveprefix", po::value<std::string>(), "prefix for auto save files")
 		("saveprefix", po::value<std::string>(), "prefix for auto save files")
 		("savefrequency", po::value<si64>(), "limit auto save creation to each N days");
 		("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["headless"].Bool() = true;
 		session["onlyai"].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
 	// Server settings
 	session["donotstartserver"].Bool() = vm.count("donotstartserver");
 	session["donotstartserver"].Bool() = vm.count("donotstartserver");
 
 
@@ -441,6 +400,7 @@ int main(int argc, char * argv[])
 
 
 	CCS = new CClientState();
 	CCS = new CClientState();
 	CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
 	CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
+	CSH = new CServerHandler();
 	// Initialize video
 	// Initialize video
 #ifdef DISABLE_VIDEO
 #ifdef DISABLE_VIDEO
 	CCS->videoh = new CEmptyVideoPlayer();
 	CCS->videoh = new CEmptyVideoPlayer();
@@ -453,15 +413,17 @@ int main(int argc, char * argv[])
 
 
 	logGlobal->info("\tInitializing video: %d ms", pomtime.getDiff());
 	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__
 #ifdef __APPLE__
 	// Ctrl+click should be treated as a right click on Mac OS X
 	// Ctrl+click should be treated as a right click on Mac OS X
 	SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
 	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["oneGoodAI"].Bool() = vm.count("oneGoodAI");
 	session["aiSolo"].Bool() = false;
 	session["aiSolo"].Bool() = false;
 
 
-	bfs::path fileToStartFrom; //none by default
-	if(vm.count("start"))
-		fileToStartFrom = vm["start"].as<bfs::path>();
 	if(vm.count("testmap"))
 	if(vm.count("testmap"))
 	{
 	{
 		session["testmap"].String() = vm["testmap"].as<std::string>();
 		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
 	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())
 	if(!settings["session"]["headless"].Bool())
@@ -614,8 +556,8 @@ void processCommand(const std::string &message)
 		}
 		}
 		else
 		else
 		{
 		{
-			if(client && client->erm)
-				client->erm->executeUserCommand(message);
+			if(CSH->client && CSH->client->erm)
+				CSH->client->erm->executeUserCommand(message);
 			std::cout << "erm>";
 			std::cout << "erm>";
 		}
 		}
 	}
 	}
@@ -665,21 +607,21 @@ void processCommand(const std::string &message)
 	}
 	}
 	else if(cn=="save")
 	else if(cn=="save")
 	{
 	{
-		if(!client)
+		if(!CSH->client)
 		{
 		{
 			std::cout << "Game in not active";
 			std::cout << "Game in not active";
 			return;
 			return;
 		}
 		}
 		std::string fname;
 		std::string fname;
 		readed >> fname;
 		readed >> fname;
-		client->save(fname);
+		CSH->client->save(fname);
 	}
 	}
 //	else if(cn=="load")
 //	else if(cn=="load")
 //	{
 //	{
 //		// TODO: this code should end the running game and manage to call startGame instead
 //		// TODO: this code should end the running game and manage to call startGame instead
 //		std::string fname;
 //		std::string fname;
 //		readed >> fname;
 //		readed >> fname;
-//		client->loadGame(fname);
+//		CSH->client->loadGame(fname);
 //	}
 //	}
 	else if(message=="convert txt")
 	else if(message=="convert txt")
 	{
 	{
@@ -861,22 +803,6 @@ void processCommand(const std::string &message)
 			logGlobal->info("Option %s disabled!", what);
 			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")
 	else if(cn == "unlock")
 	{
 	{
 		std::string mxname;
 		std::string mxname;
@@ -935,8 +861,8 @@ void processCommand(const std::string &message)
 	{
 	{
 		YourTurn yt;
 		YourTurn yt;
 		yt.player = player;
 		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"];
 	Settings session = settings.write["session"];
@@ -947,7 +873,7 @@ void processCommand(const std::string &message)
 	else if(cn == "gosolo")
 	else if(cn == "gosolo")
 	{
 	{
 		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
 		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-		if(!client)
+		if(!CSH->client)
 		{
 		{
 			std::cout << "Game in not active";
 			std::cout << "Game in not active";
 			return;
 			return;
@@ -955,23 +881,23 @@ void processCommand(const std::string &message)
 		PlayerColor color;
 		PlayerColor color;
 		if(session["aiSolo"].Bool())
 		if(session["aiSolo"].Bool())
 		{
 		{
-			for(auto & elem : client->gameState()->players)
+			for(auto & elem : CSH->client->gameState()->players)
 			{
 			{
 				if(elem.second.human)
 				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
 		else
 		{
 		{
 			color = LOCPLINT->playerID;
 			color = LOCPLINT->playerID;
 			removeGUI();
 			removeGUI();
-			for(auto & elem : client->gameState()->players)
+			for(auto & elem : CSH->client->gameState()->players)
 			{
 			{
 				if(elem.second.human)
 				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);
 					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();
 			GH.totalRedraw();
@@ -986,7 +912,7 @@ void processCommand(const std::string &message)
 		boost::to_lower(colorName);
 		boost::to_lower(colorName);
 
 
 		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
 		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-		if(!client)
+		if(!CSH->client)
 		{
 		{
 			std::cout << "Game in not active";
 			std::cout << "Game in not active";
 			return;
 			return;
@@ -994,7 +920,7 @@ void processCommand(const std::string &message)
 		PlayerColor color;
 		PlayerColor color;
 		if(LOCPLINT)
 		if(LOCPLINT)
 			color = LOCPLINT->playerID;
 			color = LOCPLINT->playerID;
-		for(auto & elem : client->gameState()->players)
+		for(auto & elem : CSH->client->gameState()->players)
 		{
 		{
 			if(elem.second.human || (colorName.length() &&
 			if(elem.second.human || (colorName.length() &&
 				elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName)))
 				elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName)))
@@ -1003,7 +929,7 @@ void processCommand(const std::string &message)
 			}
 			}
 
 
 			removeGUI();
 			removeGUI();
-			client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
+			CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
 		}
 		}
 		GH.totalRedraw();
 		GH.totalRedraw();
 		if(color != PlayerColor::NEUTRAL)
 		if(color != PlayerColor::NEUTRAL)
@@ -1290,46 +1216,59 @@ static void handleEvent(SDL_Event & ev)
 	{
 	{
 		switch(ev.user.code)
 		switch(ev.user.code)
 		{
 		{
-		case FORCE_QUIT:
+		case EUserEvent::FORCE_QUIT:
 			{
 			{
 				handleQuit(false);
 				handleQuit(false);
 				return;
 				return;
 			}
 			}
 		    break;
 		    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;
 				GH.defActionsDef = 63;
 			}
 			}
 			break;
 			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;
 			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;
 			break;
-		case RETURN_TO_MENU_LOAD:
-			endGame();
-			CGPreGame::create();
+		case EUserEvent::RETURN_TO_MENU_LOAD:
+			CSH->endGameplay();
+			CMainMenu::create();
 			GH.defActionsDef = 63;
 			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;
 			break;
-		case FULLSCREEN_TOGGLED:
+		case EUserEvent::FULLSCREEN_TOGGLED:
 			fullScreenChanged();
 			fullScreenChanged();
 			break;
 			break;
-		case INTERFACE_CHANGED:
+		case EUserEvent::INTERFACE_CHANGED:
 			if(LOCPLINT)
 			if(LOCPLINT)
 				LOCPLINT->updateAmbientSounds();
 				LOCPLINT->updateAmbientSounds();
 			break;
 			break;
@@ -1360,7 +1299,7 @@ static void handleEvent(SDL_Event & ev)
 static void mainLoop()
 static void mainLoop()
 {
 {
 	SettingsListener resChanged = settings.listen["video"]["fullscreen"];
 	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));
 	inGuiThread.reset(new bool(true));
 	GH.mainFPSmng->init();
 	GH.mainFPSmng->init();
@@ -1374,69 +1313,18 @@ static void mainLoop()
 			handleEvent(ev);
 			handleEvent(ev);
 		}
 		}
 
 
+		CSH->applyPacksOnLobbyScreen();
 		GH.renderFrame();
 		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)
 void handleQuit(bool ask)
 {
 {
 	auto quitApplication = []()
 	auto quitApplication = []()
 	{
 	{
-		if(client)
-			endGame();
+		if(CSH->client)
+			CSH->endGameplay();
 		dispose();
 		dispose();
 		vstd::clear_pointer(console);
 		vstd::clear_pointer(console);
 		boost::this_thread::sleep(boost::posix_time::milliseconds(750));
 		boost::this_thread::sleep(boost::posix_time::milliseconds(750));
@@ -1450,7 +1338,7 @@ void handleQuit(bool ask)
 		exit(0);
 		exit(0);
 	};
 	};
 
 
-	if(client && LOCPLINT && ask)
+	if(CSH->client && LOCPLINT && ask)
 	{
 	{
 		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
 		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
 		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, 0);
 		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, 0);

+ 0 - 3
client/CMT.h

@@ -9,7 +9,6 @@
  */
  */
 #pragma once
 #pragma once
 #include <SDL_render.h>
 #include <SDL_render.h>
-#include "../lib/CondSh.h"
 
 
 extern SDL_Texture * screenTexture;
 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 *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 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 removeGUI();
 void handleQuit(bool ask = true);
 void handleQuit(bool ask = true);

+ 31 - 2
client/CMakeLists.txt

@@ -43,6 +43,20 @@ set(client_SRCS
 		windows/InfoWindows.cpp
 		windows/InfoWindows.cpp
 		windows/QuickRecruitmentWindow.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
 		CBitmapHandler.cpp
 		CreatureCostBox.cpp
 		CreatureCostBox.cpp
 		CGameInfo.cpp
 		CGameInfo.cpp
@@ -51,11 +65,12 @@ set(client_SRCS
 		CMT.cpp
 		CMT.cpp
 		CMusicHandler.cpp
 		CMusicHandler.cpp
 		CPlayerInterface.cpp
 		CPlayerInterface.cpp
-		CPreGame.cpp
 		CVideoHandler.cpp
 		CVideoHandler.cpp
+		CServerHandler.cpp
 		Graphics.cpp
 		Graphics.cpp
 		mapHandler.cpp
 		mapHandler.cpp
 		NetPacksClient.cpp
 		NetPacksClient.cpp
+		NetPacksLobbyClient.cpp
 		SDLRWwrapper.cpp
 		SDLRWwrapper.cpp
 )
 )
 
 
@@ -100,6 +115,20 @@ set(client_HEADERS
 		windows/InfoWindows.h
 		windows/InfoWindows.h
 		windows/QuickRecruitmentWindow.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
 		CBitmapHandler.h
 		CreatureCostBox.h
 		CreatureCostBox.h
 		CGameInfo.h
 		CGameInfo.h
@@ -108,8 +137,8 @@ set(client_HEADERS
 		CMT.h
 		CMT.h
 		CMusicHandler.h
 		CMusicHandler.h
 		CPlayerInterface.h
 		CPlayerInterface.h
-		CPreGame.h
 		CVideoHandler.h
 		CVideoHandler.h
+		CServerHandler.h
 		Graphics.h
 		Graphics.h
 		mapHandler.h
 		mapHandler.h
 		resource.h
 		resource.h

+ 36 - 71
client/CPlayerInterface.cpp

@@ -49,13 +49,16 @@
 #include "mapHandler.h"
 #include "mapHandler.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/StartInfo.h"
 #include "../lib/StartInfo.h"
-#include "../lib/CGameState.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/CPlayerState.h"
 #include "../lib/GameConstants.h"
 #include "../lib/GameConstants.h"
 #include "gui/CGuiHandler.h"
 #include "gui/CGuiHandler.h"
 #include "windows/InfoWindows.h"
 #include "windows/InfoWindows.h"
 #include "../lib/UnlockGuard.h"
 #include "../lib/UnlockGuard.h"
+#include "../lib/CPathfinder.h"
 #include <SDL.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.
 // 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};
 enum  EMoveState {STOP_MOVE, WAITING_MOVE, CONTINUE_MOVE, DURING_MOVE};
 CondSh<EMoveState> stillMoveHero(STOP_MOVE); //used during hero movement
 CondSh<EMoveState> stillMoveHero(STOP_MOVE); //used during hero movement
 
 
-int CPlayerInterface::howManyPeople = 0;
-
 static bool objectBlitOrderSorter(const TerrainTileObject  & a, const TerrainTileObject & b)
 static bool objectBlitOrderSorter(const TerrainTileObject  & a, const TerrainTileObject & b)
 {
 {
 	return CMapHandler::compareObjectBlitOrder(a.obj, b.obj);
 	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());
 	logGlobal->trace("\tHuman player interface for player %s being constructed", Player.getStr());
 	destinationTeleport = ObjectInstanceID();
 	destinationTeleport = ObjectInstanceID();
 	destinationTeleportPos = int3(-1);
 	destinationTeleportPos = int3(-1);
-	howManyPeople++;
 	GH.defActionsDef = 0;
 	GH.defActionsDef = 0;
 	LOCPLINT = this;
 	LOCPLINT = this;
 	curAction = nullptr;
 	curAction = nullptr;
@@ -139,7 +139,6 @@ CPlayerInterface::~CPlayerInterface()
 {
 {
 	CCS->soundh->ambientStopAllChannels();
 	CCS->soundh->ambientStopAllChannels();
 	logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.getStr());
 	logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.getStr());
-	//howManyPeople--;
 	delete showingDialog;
 	delete showingDialog;
 	delete cingconsole;
 	delete cingconsole;
 	if (LOCPLINT == this)
 	if (LOCPLINT == this)
@@ -148,9 +147,7 @@ CPlayerInterface::~CPlayerInterface()
 void CPlayerInterface::init(std::shared_ptr<CCallback> CB)
 void CPlayerInterface::init(std::shared_ptr<CCallback> CB)
 {
 {
 	cb = CB;
 	cb = CB;
-
-	if (!towns.size() && !wanderingHeroes.size())
-		initializeHeroTownList();
+	initializeHeroTownList();
 
 
 	// always recreate advmap interface to avoid possible memory-corruption bugs
 	// always recreate advmap interface to avoid possible memory-corruption bugs
 	if (adventureInt)
 	if (adventureInt)
@@ -170,7 +167,7 @@ void CPlayerInterface::yourTurn()
 		std::string prefix = settings["session"]["saveprefix"].String();
 		std::string prefix = settings["session"]["saveprefix"].String();
 		if (firstCall)
 		if (firstCall)
 		{
 		{
-			if (howManyPeople == 1)
+			if(CSH->howManyPlayerInterfaces() == 1)
 				adventureInt->setPlayer(playerID);
 				adventureInt->setPlayer(playerID);
 
 
 			autosaveCount = getLastIndex(prefix + "Autosave_");
 			autosaveCount = getLastIndex(prefix + "Autosave_");
@@ -192,7 +189,7 @@ void CPlayerInterface::yourTurn()
 		if (adventureInt->player != playerID)
 		if (adventureInt->player != playerID)
 			adventureInt->setPlayer(playerID);
 			adventureInt->setPlayer(playerID);
 
 
-		if (howManyPeople > 1) //hot seat message
+		if (CSH->howManyPlayerInterfaces() > 1) //hot seat message
 		{
 		{
 			adventureInt->startHotSeatWait(playerID);
 			adventureInt->startHotSeatWait(playerID);
 
 
@@ -1581,45 +1578,20 @@ void CPlayerInterface::objectPropertyChanged(const SetObjectProperty * sop)
 
 
 void CPlayerInterface::initializeHeroTownList()
 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);
 		adventureInt->updateNextHero(nullptr);
 }
 }
 
 
@@ -1728,7 +1700,7 @@ void CPlayerInterface::update()
 		return;
 		return;
 
 
 	//if there are any waiting dialogs, show them
 	//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);
 		showingDialog->set(true);
 		GH.pushInt(dialogs.front());
 		GH.pushInt(dialogs.front());
@@ -2227,37 +2199,27 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
 			waitForAllDialogs(); //wait till all dialogs are displayed and closed
 			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);
 				GH.terminate_cond->setn(true);
 				adventureInt->deactivate();
 				adventureInt->deactivate();
 				if (GH.topInt() == adventureInt)
 				if (GH.topInt() == adventureInt)
 					GH.popInt(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;
 		if (GH.curInt == this) GH.curInt = nullptr;
@@ -2378,7 +2340,7 @@ void CPlayerInterface::acceptTurn()
 	}
 	}
 	waitWhileDialog();
 	waitWhileDialog();
 
 
-	if (howManyPeople > 1)
+	if(CSH->howManyPlayerInterfaces() > 1)
 		adventureInt->startTurn();
 		adventureInt->startTurn();
 
 
 	adventureInt->heroList.update();
 	adventureInt->heroList.update();
@@ -2573,11 +2535,14 @@ void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj)
 		showShipyardDialog(obj);
 		showShipyardDialog(obj);
 }
 }
 
 
-void CPlayerInterface::requestReturningToMainMenu()
+void CPlayerInterface::requestReturningToMainMenu(bool won)
 {
 {
-	sendCustomEvent(RETURN_TO_MAIN_MENU);
+	CSH->state = EClientState::DISCONNECTING;
 	CCS->soundh->ambientStopAllChannels();
 	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 )
 void CPlayerInterface::sendCustomEvent( int code )
@@ -2672,7 +2637,7 @@ void CPlayerInterface::playerStartsTurn(PlayerColor player)
 			GH.popInts(1);
 			GH.popInts(1);
 	}
 	}
 
 
-	if (howManyPeople == 1)
+	if(CSH->howManyPlayerInterfaces() == 1)
 	{
 	{
 		GH.curInt = this;
 		GH.curInt = this;
 		adventureInt->startTurn();
 		adventureInt->startTurn();
@@ -2696,7 +2661,7 @@ void CPlayerInterface::waitForAllDialogs(bool unlockPim)
 
 
 void CPlayerInterface::proposeLoadingGame()
 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()
 CPlayerInterface::SpellbookLastSetting::SpellbookLastSetting()

+ 1 - 15
client/CPlayerInterface.h

@@ -59,19 +59,6 @@ namespace boost
 	class recursive_mutex;
 	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
 /// Central class for managing user interface logic
 class CPlayerInterface : public CGameInterface, public IUpdateable
 class CPlayerInterface : public CGameInterface, public IUpdateable
 {
 {
@@ -88,7 +75,6 @@ public:
 	int firstCall; // -1 - just loaded game; 1 - just started game; 0 otherwise
 	int firstCall; // -1 - just loaded game; 1 - just started game; 0 otherwise
 	int autosaveCount;
 	int autosaveCount;
 	static const int SAVES_COUNT = 5;
 	static const int SAVES_COUNT = 5;
-	static int howManyPeople;
 
 
 	CCastleInterface * castleInt; //nullptr if castle window isn't opened
 	CCastleInterface * castleInt; //nullptr if castle window isn't opened
 	static CBattleInterface * battleInt; //nullptr if no battle
 	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 acceptTurn(); //used during hot seat after your turn message is close
 	void tryDiggging(const CGHeroInstance *h);
 	void tryDiggging(const CGHeroInstance *h);
 	void showShipyardDialogOrProblemPopup(const IShipyard *obj); //obj may be town or shipyard;
 	void showShipyardDialogOrProblemPopup(const IShipyard *obj); //obj may be town or shipyard;
-	void requestReturningToMainMenu();
+	void requestReturningToMainMenu(bool won);
 	void sendCustomEvent(int code);
 	void sendCustomEvent(int code);
 	void proposeLoadingGame();
 	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;

Різницю між файлами не показано, бо вона завелика
+ 229 - 544
client/Client.cpp


+ 72 - 125
client/Client.h

@@ -9,25 +9,24 @@
  */
  */
 #pragma once
 #pragma once
 
 
-
 #include "../lib/IGameCallback.h"
 #include "../lib/IGameCallback.h"
 #include "../lib/battle/BattleAction.h"
 #include "../lib/battle/BattleAction.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/int3.h"
 #include "../lib/int3.h"
+#include "../lib/CondSh.h"
+#include "../lib/CPathfinder.h"
 
 
 struct CPack;
 struct CPack;
+struct CPackForServer;
 class CCampaignState;
 class CCampaignState;
 class CBattleCallback;
 class CBattleCallback;
 class IGameEventsReceiver;
 class IGameEventsReceiver;
 class IBattleEventsReceiver;
 class IBattleEventsReceiver;
 class CBattleGameInterface;
 class CBattleGameInterface;
-struct StartInfo;
 class CGameState;
 class CGameState;
 class CGameInterface;
 class CGameInterface;
-class CConnection;
 class CCallback;
 class CCallback;
-class BattleAction;
-struct SharedMemory;
+struct BattleAction;
 class CClient;
 class CClient;
 class CScriptingModule;
 class CScriptingModule;
 struct CPathsInfo;
 struct CPathsInfo;
@@ -35,31 +34,8 @@ class BinaryDeserializer;
 class BinarySerializer;
 class BinarySerializer;
 namespace boost { class thread; }
 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>
 template<typename T>
 class ThreadSafeVector
 class ThreadSafeVector
@@ -78,7 +54,7 @@ public:
 		cond.notify_all();
 		cond.notify_all();
 	}
 	}
 
 
-	void pushBack(const T &item)
+	void pushBack(const T & item)
 	{
 	{
 		TLock lock(mx);
 		TLock lock(mx);
 		items.push_back(item);
 		items.push_back(item);
@@ -97,14 +73,14 @@ public:
 		return TLock(mx);
 		return TLock(mx);
 	}
 	}
 
 
-	void waitWhileContains(const T &item)
+	void waitWhileContains(const T & item)
 	{
 	{
 		auto lock = getLock();
 		auto lock = getLock();
 		while(vstd::contains(items, item))
 		while(vstd::contains(items, item))
 			cond.wait(lock);
 			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 lock = getLock();
 		auto itr = vstd::find(items, item);
 		auto itr = vstd::find(items, item);
@@ -122,105 +98,104 @@ public:
 /// Class which handles client - server logic
 /// Class which handles client - server logic
 class CClient : public IGameCallback
 class CClient : public IGameCallback
 {
 {
+	std::shared_ptr<CApplier<CBaseForCLApply>> applier;
 	std::unique_ptr<CPathsInfo> pathInfo;
 	std::unique_ptr<CPathsInfo> pathInfo;
 
 
 	std::map<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
 	std::map<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
+	void waitForMoveAndSend(PlayerColor color);
+
 public:
 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<IGameEventsReceiver>> privilegedGameEventReceivers; //scripting modules, spectator interfaces
 	std::vector<std::shared_ptr<IBattleEventsReceiver>> privilegedBattleEventReceivers; //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<CGameInterface>> playerint;
 	std::map<PlayerColor, std::shared_ptr<CBattleGameInterface>> battleints;
 	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;
 	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();
-	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 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);
 	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;
 	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;};
 	bool removeObject(const CGObjectInstance * obj) override {return false;};
 	void setBlockVis(ObjectInstanceID objid, bool bv) override {};
 	void setBlockVis(ObjectInstanceID objid, bool bv) override {};
 	void setOwner(const CGObjectInstance * obj, PlayerColor owner) 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 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 showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) override {};
 	void giveResource(PlayerColor player, Res::ERes which, int val) override {};
 	void giveResource(PlayerColor player, Res::ERes which, int val) override {};
 	virtual void giveResources(PlayerColor player, TResources resources) 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 synchronizeArtifactHandlerLists() override {};
 
 
 	void showCompInfo(ShowInInfobox * comp) override {};
 	void showCompInfo(ShowInInfobox * comp) override {};
 	void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
 	void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
 	void stopHeroVisitCastle(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 {};
 	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;};
 	bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
 	void giveHeroBonus(GiveBonus * bonus) override {};
 	void giveHeroBonus(GiveBonus * bonus) override {};
@@ -228,37 +203,9 @@ public:
 	void setManaPoints(ObjectInstanceID hid, int val) override {};
 	void setManaPoints(ObjectInstanceID hid, int val) override {};
 	void giveHero(ObjectInstanceID id, PlayerColor player) override {};
 	void giveHero(ObjectInstanceID id, PlayerColor player) override {};
 	void changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags) override {};
 	void changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags) override {};
-	void sendAndApply(CPackForClient * info) override {};
+	void sendAndApply(CPackForClient * pack) override {};
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {};
 	void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {};
 
 
 	void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) 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/MiscWidgets.h"
 #include "widgets/AdventureMapClasses.h"
 #include "widgets/AdventureMapClasses.h"
 #include "CMT.h"
 #include "CMT.h"
+#include "CServerHandler.h"
 
 
 // TODO: as Tow suggested these template should all be part of CClient
 // TODO: as Tow suggested these template should all be part of CClient
 // This will require rework spectator interface properly though
 // 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)
 void RemoveObject::applyFirstCl(CClient *cl)
 {
 {
 	const CGObjectInstance *o = cl->getObj(id);
 	const CGObjectInstance *o = cl->getObj(id);
@@ -757,7 +747,7 @@ void PackageApplied::applyCl(CClient *cl)
 {
 {
 	callInterfaceIfPresent(cl, player, &IGameEventsReceiver::requestRealized, this);
 	callInterfaceIfPresent(cl, player, &IGameEventsReceiver::requestRealized, this);
 	if(!CClient::waitingRequest.tryRemovingElement(requestID))
 	if(!CClient::waitingRequest.tryRemovingElement(requestID))
-		logNetwork->warn("Surprising server message!");
+		logNetwork->warn("Surprising server message! PackageApplied for unknown requestID!");
 }
 }
 
 
 void SystemMessage::applyCl(CClient *cl)
 void SystemMessage::applyCl(CClient *cl)
@@ -777,11 +767,13 @@ void PlayerBlocked::applyCl(CClient *cl)
 
 
 void YourTurn::applyCl(CClient *cl)
 void YourTurn::applyCl(CClient *cl)
 {
 {
+	logNetwork->debug("Server gives turn to %s", player.getStr());
+
 	callAllInterfaces(cl, &IGameEventsReceiver::playerStartsTurn, player);
 	callAllInterfaces(cl, &IGameEventsReceiver::playerStartsTurn, player);
 	callOnlyThatInterface(cl, player, &CGameInterface::yourTurn);
 	callOnlyThatInterface(cl, player, &CGameInterface::yourTurn);
 }
 }
 
 
-void SaveGame::applyCl(CClient *cl)
+void SaveGameClient::applyCl(CClient *cl)
 {
 {
 	const auto stem = FileInfo::GetPathStem(fname);
 	const auto stem = FileInfo::GetPathStem(fname);
 	CResourceHandler::get("local")->createResource(stem.to_string() + ".vcgm1");
 	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);
 	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();
 		listInt.front()->activate();
 	totalRedraw();
 	totalRedraw();
 
 
-	pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
+	pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
 }
 }
 
 
 void CGuiHandler::popIntTotally(IShowActivatable *top)
 void CGuiHandler::popIntTotally(IShowActivatable *top)
@@ -132,7 +132,7 @@ void CGuiHandler::pushInt(IShowActivatable *newInt)
 	objsToBlit.push_back(newInt);
 	objsToBlit.push_back(newInt);
 	totalRedraw();
 	totalRedraw();
 
 
-	pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
+	pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
 }
 }
 
 
 void CGuiHandler::popInts(int howMany)
 void CGuiHandler::popInts(int howMany)
@@ -155,7 +155,7 @@ void CGuiHandler::popInts(int howMany)
 	}
 	}
 	fakeMouseMove();
 	fakeMouseMove();
 
 
-	pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
+	pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
 }
 }
 
 
 IShowActivatable * CGuiHandler::topInt()
 IShowActivatable * CGuiHandler::topInt()

+ 15 - 5
client/gui/CGuiHandler.h

@@ -22,6 +22,20 @@ class IShowable;
 enum class EIntObjMouseBtnType;
 enum class EIntObjMouseBtnType;
 template <typename T> struct CondSh;
 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
 // A fps manager which holds game updates at a constant rate
 class CFramerateManager
 class CFramerateManager
 {
 {
@@ -119,11 +133,6 @@ public:
 
 
 extern CGuiHandler GH; //global gui handler
 extern CGuiHandler GH; //global gui handler
 
 
-template <typename T> void pushIntT()
-{
-	GH.pushInt(new T());
-}
-
 struct SObjectConstruction
 struct SObjectConstruction
 {
 {
 	CIntObject *myObj;
 	CIntObject *myObj;
@@ -142,5 +151,6 @@ struct SSetCaptureState
 #define OBJ_CONSTRUCTION SObjectConstruction obj__i(this)
 #define OBJ_CONSTRUCTION SObjectConstruction obj__i(this)
 #define OBJECT_CONSTRUCTION_CAPTURING(actions) defActions = actions; SSetCaptureState obj__i1(true, actions); 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 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 SSetCaptureState obj__i(false, 0)
 #define BLOCK_CAPTURING_DONT_TOUCH_REC_ACTIONS SSetCaptureState obj__i(false, GH.defActionsDef)
 #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::WHITE = { 255, 243, 222, 0 };
 const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, 0 };
 const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, 0 };
 const SDL_Color Colors::GREEN = { 0, 255, 0, 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};
 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)
 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 */
 	/** green color used for in-game console */
 	static const SDL_Color GREEN;
 	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 */
 	/** default key color for all 8 & 24 bit graphics */
 	static const SDL_Color DEFAULT_KEY_COLOR;
 	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 "../CGameInfo.h"
 #include "../CMusicHandler.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CPlayerInterface.h"
-#include "../CPreGame.h"
+#include "../mainmenu/CMainMenu.h"
 #include "../Graphics.h"
 #include "../Graphics.h"
 #include "../CMessage.h"
 #include "../CMessage.h"
 
 

+ 28 - 1
client/widgets/TextControls.cpp

@@ -63,6 +63,11 @@ std::string CLabel::getText()
 	return text;
 	return text;
 }
 }
 
 
+void CLabel::setAutoRedraw(bool value)
+{
+	autoRedraw = value;
+}
+
 void CLabel::setText(const std::string &Txt)
 void CLabel::setText(const std::string &Txt)
 {
 {
 	text = Txt;
 	text = Txt;
@@ -75,6 +80,23 @@ void CLabel::setText(const std::string &Txt)
 	}
 	}
 }
 }
 
 
+void CLabel::setColor(const SDL_Color & Color)
+{
+	color = Color;
+	if(autoRedraw)
+	{
+		if(bg || !parent)
+			redraw();
+		else
+			parent->redraw();
+	}
+}
+
+size_t CLabel::getWidth()
+{
+	return graphics->fonts[font]->getStringWidth(visibleText());;
+}
+
 CMultiLineLabel::CMultiLineLabel(Rect position, EFonts Font, EAlignment Align, const SDL_Color &Color, const std::string &Text):
 CMultiLineLabel::CMultiLineLabel(Rect position, EFonts Font, EAlignment Align, const SDL_Color &Color, const std::string &Text):
 	CLabel(position.x, position.y, Font, Align, Color, Text),
 	CLabel(position.x, position.y, Font, Align, Color, Text),
 	visibleSize(0, 0, position.w, position.h)
 	visibleSize(0, 0, position.w, position.h)
@@ -244,7 +266,12 @@ CLabelGroup::CLabelGroup(EFonts Font, EAlignment Align, const SDL_Color &Color):
 void CLabelGroup::add(int x, int y, const std::string &text)
 void CLabelGroup::add(int x, int y, const std::string &text)
 {
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	new CLabel(x, y, font, align, color, text);
+	labels.push_back(new CLabel(x, y, font, align, color, text));
+}
+
+size_t CLabelGroup::currentSize() const
+{
+	return labels.size();
 }
 }
 
 
 CTextBox::CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font, EAlignment Align, const SDL_Color &Color):
 CTextBox::CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font, EAlignment Align, const SDL_Color &Color):

+ 4 - 0
client/widgets/TextControls.h

@@ -47,7 +47,10 @@ public:
 	bool autoRedraw;  //whether control will redraw itself on setTxt
 	bool autoRedraw;  //whether control will redraw itself on setTxt
 
 
 	std::string getText();
 	std::string getText();
+	virtual void setAutoRedraw(bool option);
 	virtual void setText(const std::string &Txt);
 	virtual void setText(const std::string &Txt);
+	virtual void setColor(const SDL_Color & Color);
+	size_t getWidth();
 
 
 	CLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT,
 	CLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT,
 	       const SDL_Color &Color = Colors::WHITE, const std::string &Text =  "");
 	       const SDL_Color &Color = Colors::WHITE, const std::string &Text =  "");
@@ -64,6 +67,7 @@ class CLabelGroup : public CIntObject
 public:
 public:
 	CLabelGroup(EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE);
 	CLabelGroup(EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE);
 	void add(int x=0, int y=0, const std::string &text =  "");
 	void add(int x=0, int y=0, const std::string &text =  "");
+	size_t currentSize() const;
 };
 };
 
 
 /// Multi-line label that can display multiple lines of text
 /// Multi-line label that can display multiple lines of text

+ 12 - 8
client/windows/CAdvmapInterface.cpp

@@ -22,7 +22,10 @@
 #include "../CMessage.h"
 #include "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.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 "../Graphics.h"
 #include "../mapHandler.h"
 #include "../mapHandler.h"
 
 
@@ -47,6 +50,7 @@
 #include "../../lib/mapping/CMap.h"
 #include "../../lib/mapping/CMap.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/VCMI_Lib.h"
 #include "../../lib/VCMI_Lib.h"
+#include "../../lib/StartInfo.h"
 
 
 #ifdef _MSC_VER
 #ifdef _MSC_VER
 #pragma warning (disable : 4355)
 #pragma warning (disable : 4355)
@@ -935,7 +939,8 @@ void CAdvMapInt::activate()
 		}
 		}
 		minimap.activate();
 		minimap.activate();
 		terrain.activate();
 		terrain.activate();
-		LOCPLINT->cingconsole->activate();
+		if(LOCPLINT)
+			LOCPLINT->cingconsole->activate();
 
 
 		GH.fakeMouseMove(); //to restore the cursor
 		GH.fakeMouseMove(); //to restore the cursor
 	}
 	}
@@ -1215,7 +1220,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 		return;
 		return;
 	case SDLK_s:
 	case SDLK_s:
 		if(isActive() && key.type == SDL_KEYUP)
 		if(isActive() && key.type == SDL_KEYUP)
-			GH.pushInt(new CSavingScreen(CPlayerInterface::howManyPeople > 1));
+			GH.pushInt(new CSavingScreen());
 		return;
 		return;
 	case SDLK_d:
 	case SDLK_d:
 		{
 		{
@@ -1235,7 +1240,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 		if(isActive() && LOCPLINT->ctrlPressed())
 		if(isActive() && LOCPLINT->ctrlPressed())
 		{
 		{
 			LOCPLINT->showYesNoDialog("Are you sure you want to restart game?",
 			LOCPLINT->showYesNoDialog("Are you sure you want to restart game?",
-				[](){ LOCPLINT->sendCustomEvent(RESTART_GAME); },
+				[](){ LOCPLINT->sendCustomEvent(EUserEvent::RESTART_GAME); },
 				[](){}, true);
 				[](){}, true);
 		}
 		}
 		return;
 		return;
@@ -1947,14 +1952,13 @@ CAdventureOptions::CAdventureOptions():
 
 
 void CAdventureOptions::showScenarioInfo()
 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
 	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 "../CMessage.h"
 #include "../CMusicHandler.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CPlayerInterface.h"
-#include "../CPreGame.h"
 #include "../CVideoHandler.h"
 #include "../CVideoHandler.h"
 #include "../Graphics.h"
 #include "../Graphics.h"
 #include "../mapHandler.h"
 #include "../mapHandler.h"
+#include "../CServerHandler.h"
 
 
 #include "../battle/CBattleInterfaceClasses.h"
 #include "../battle/CBattleInterfaceClasses.h"
 #include "../battle/CBattleInterface.h"
 #include "../battle/CBattleInterface.h"
@@ -38,6 +38,8 @@
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/MiscWidgets.h"
 #include "../windows/InfoWindows.h"
 #include "../windows/InfoWindows.h"
 
 
+#include "../lobby/CSavingScreen.h"
+
 #include "../../CCallback.h"
 #include "../../CCallback.h"
 
 
 #include "../lib/mapObjects/CGHeroInstance.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 = new CButton (Point(246, 357), "SORSTRT", CGI->generaltexth->zelp[323], [&](){ brestartf(); }, SDLK_r);
 	restart->setImageOrder(1, 0, 2, 3);
 	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 = new CButton (Point(357, 357), "SOMAIN.DEF", CGI->generaltexth->zelp[320], [&](){ bmainmenuf(); }, SDLK_m);
 	mainMenu->setImageOrder(1, 0, 2, 3);
 	mainMenu->setImageOrder(1, 0, 2, 3);
 
 
@@ -593,7 +602,7 @@ void CSystemOptionsWindow::setGameRes(int index)
 
 
 void CSystemOptionsWindow::bquitf()
 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()
 void CSystemOptionsWindow::breturnf()
@@ -603,7 +612,7 @@ void CSystemOptionsWindow::breturnf()
 
 
 void CSystemOptionsWindow::bmainmenuf()
 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()
 void CSystemOptionsWindow::bloadf()
@@ -615,12 +624,12 @@ void CSystemOptionsWindow::bloadf()
 void CSystemOptionsWindow::bsavef()
 void CSystemOptionsWindow::bsavef()
 {
 {
 	GH.popIntTotally(this);
 	GH.popIntTotally(this);
-	GH.pushInt(new CSavingScreen(CPlayerInterface::howManyPeople > 1));
+	GH.pushInt(new CSavingScreen());
 }
 }
 
 
 void CSystemOptionsWindow::brestartf()
 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)
 void CSystemOptionsWindow::closeAndPushEvent(int eventType, int code)

+ 12 - 0
config/schemas/settings.json

@@ -46,6 +46,18 @@
 				"saveRandomMaps" : {
 				"saveRandomMaps" : {
 					"type" : "boolean",
 					"type" : "boolean",
 					"default" : false
 					"default" : false
+				},
+				"lastMap" : {
+					"type":"string",
+					"default" : "Maps/Arrogance"
+				},
+				"lastSave" : {
+					"type":"string",
+					"default" : "NEWGAME"
+				},
+				"lastCampaign" : {
+					"type":"string",
+					"default" : ""
 				}
 				}
 			}
 			}
 		},
 		},

+ 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
 void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst) const
 {
 {
 	int type = txt.first, ser = txt.second;
 	int type = txt.first, ser = txt.second;
@@ -673,9 +671,9 @@ int CGameState::getDate(Date::EDateType mode) const
 CGameState::CGameState()
 CGameState::CGameState()
 {
 {
 	gs = this;
 	gs = this;
-	applierGs = new CApplier<CBaseForGSApply>();
-	registerTypesClientPacks1(*applierGs);
-	registerTypesClientPacks2(*applierGs);
+	applier = std::make_shared<CApplier<CBaseForGSApply>>();
+	registerTypesClientPacks1(*applier);
+	registerTypesClientPacks2(*applier);
 	//objCaller = new CObjectCallersHandler();
 	//objCaller = new CObjectCallersHandler();
 	globalEffects.setDescription("Global effects");
 	globalEffects.setDescription("Global effects");
 	globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
 	globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
@@ -686,10 +684,6 @@ CGameState::~CGameState()
 {
 {
 	map.dellNull();
 	map.dellNull();
 	curB.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
 	for(auto ptr : hpool.heroesPool) // clean hero pool
 		ptr.second.dellNull();
 		ptr.second.dellNull();
@@ -709,7 +703,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
 		initNewGame(mapService, allowSavingRandomMap);
 		initNewGame(mapService, allowSavingRandomMap);
 		break;
 		break;
 	case StartInfo::CAMPAIGN:
 	case StartInfo::CAMPAIGN:
-		initCampaign(mapService);
+		initCampaign();
 		break;
 		break;
 	default:
 	default:
 		logGlobal->error("Wrong mode: %d", static_cast<int>(scenarioOps->mode));
 		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)
 void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap)
 {
 {
 	if(scenarioOps->createRandomMap())
 	if(scenarioOps->createRandomMap())
@@ -815,7 +816,7 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
 				playerSettings.compOnly = !playerInfo.canHumanPlay;
 				playerSettings.compOnly = !playerInfo.canHumanPlay;
 				playerSettings.team = playerInfo.team;
 				playerSettings.team = playerInfo.team;
 				playerSettings.castle = playerInfo.defaultCastle();
 				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];
 					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());
 	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()
 void CGameState::checkMapChecksum()
@@ -962,13 +954,11 @@ void CGameState::initPlayerStates()
 	for(auto & elem : scenarioOps->playerInfos)
 	for(auto & elem : scenarioOps->playerInfos)
 	{
 	{
 		PlayerState & p = players[elem.first];
 		PlayerState & p = players[elem.first];
-		//std::pair<PlayerColor, PlayerState> ins(elem.first,PlayerState());
 		p.color=elem.first;
 		p.color=elem.first;
-		p.human = elem.second.playerID;
+		p.human = elem.second.isControlledByHuman();
 		p.team = map->players[elem.first.getNum()].team;
 		p.team = map->players[elem.first.getNum()].team;
 		teams[p.team].id = p.team;//init team
 		teams[p.team].id = p.team;//init team
 		teams[p.team].players.insert(elem.first);//add player to 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();
 	auto bonus = campaignState->getBonusForCurrentMap();
 	if (bonus && bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO)
 	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
 	else
 	{
 	{
 		if(!campaignState->mapsConquered.empty())
 		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)
 			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
 				// remove heroes which didn't reached the end of the scenario, but were available at the start
 				for(auto hero : lostCrossoverHeroes)
 				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
 				// 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
 					// 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())
 					if (it != crossoverHeroes.heroesFromAnyPreviousScenarios.end())
 					{
 					{
 						// replace old hero with newer one
 						// replace old hero with newer one
@@ -1306,7 +1316,7 @@ void CGameState::initStartingResources()
 		for(auto it = scenarioOps->playerInfos.cbegin();
 		for(auto it = scenarioOps->playerInfos.cbegin();
 			it != scenarioOps->playerInfos.cend(); ++it)
 			it != scenarioOps->playerInfos.cend(); ++it)
 		{
 		{
-			if(it->second.playerID != PlayerSettings::PLAYER_AI)
+			if(it->second.isControlledByHuman())
 				ret.push_back(&it->second);
 				ret.push_back(&it->second);
 		}
 		}
 
 
@@ -1955,7 +1965,7 @@ PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor col
 void CGameState::apply(CPack *pack)
 void CGameState::apply(CPack *pack)
 {
 {
 	ui16 typ = typeList.getTypeID(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)
 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->objects[heroToPlace->id.getNum()] = heroToPlace;
 		map->addBlockVisTiles(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;
 	class shared_mutex;
 }
 }
 
 
+template<typename T> class CApplier;
+class CBaseForGSApply;
+
 struct DLL_LINKAGE SThievesGuildInfo
 struct DLL_LINKAGE SThievesGuildInfo
 {
 {
 	std::vector<PlayerColor> playerColors; //colors of players that are in-game
 	std::vector<PlayerColor> playerColors; //colors of players that are in-game
@@ -153,6 +156,7 @@ public:
 	virtual ~CGameState();
 	virtual ~CGameState();
 
 
 	void init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false);
 	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)
 	ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
 	PlayerColor currentPlayer; //ID of player currently having turn
 	PlayerColor currentPlayer; //ID of player currently having turn
@@ -246,7 +250,7 @@ private:
 	// ----- initialization -----
 	// ----- initialization -----
 
 
 	void initNewGame(const IMapService * mapService, bool allowSavingRandomMap);
 	void initNewGame(const IMapService * mapService, bool allowSavingRandomMap);
-	void initCampaign(const IMapService * mapService);
+	void initCampaign();
 	void checkMapChecksum();
 	void checkMapChecksum();
 	void initGrailPosition();
 	void initGrailPosition();
 	void initRandomFactionsForPlayers();
 	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
 	int pickNextHeroType(PlayerColor owner); // picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
 
 
 	// ---- data -----
 	// ---- data -----
+	std::shared_ptr<CApplier<CBaseForGSApply>> applier;
 	CRandomGenerator rand;
 	CRandomGenerator rand;
 
 
 	friend class CCallback;
 	friend class CCallback;

+ 3 - 1
lib/CMakeLists.txt

@@ -74,7 +74,7 @@ set(lib_SRCS
 		registerTypes/TypesMapObjects1.cpp
 		registerTypes/TypesMapObjects1.cpp
 		registerTypes/TypesMapObjects2.cpp
 		registerTypes/TypesMapObjects2.cpp
 		registerTypes/TypesMapObjects3.cpp
 		registerTypes/TypesMapObjects3.cpp
-		registerTypes/TypesPregamePacks.cpp
+		registerTypes/TypesLobbyPacks.cpp
 		registerTypes/TypesServerPacks.cpp
 		registerTypes/TypesServerPacks.cpp
 
 
 		rmg/CMapGenerator.cpp
 		rmg/CMapGenerator.cpp
@@ -152,6 +152,7 @@ set(lib_SRCS
 		JsonNode.cpp
 		JsonNode.cpp
 		LogicalExpression.cpp
 		LogicalExpression.cpp
 		NetPacksLib.cpp
 		NetPacksLib.cpp
+		StartInfo.cpp
 		ResourceSet.cpp
 		ResourceSet.cpp
 		VCMIDirs.cpp
 		VCMIDirs.cpp
 		VCMI_Lib.cpp
 		VCMI_Lib.cpp
@@ -335,6 +336,7 @@ set(lib_HEADERS
 		LogicalExpression.h
 		LogicalExpression.h
 		NetPacksBase.h
 		NetPacksBase.h
 		NetPacks.h
 		NetPacks.h
+		NetPacksLobby.h
 		ResourceSet.h
 		ResourceSet.h
 		ScopeGuard.h
 		ScopeGuard.h
 		StartInfo.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 ui32 BASE_MOVEMENT_COST = 100; //default cost for non-diagonal movement
 
 
 	const int HERO_PORTRAIT_SHIFT = 30;// 2 special frames + some extra portraits
 	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;
 class CArtifact;

+ 3 - 0
lib/IGameCallback.cpp

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

+ 1 - 1
lib/IGameCallback.h

@@ -91,7 +91,7 @@ public:
 	virtual void setManaPoints(ObjectInstanceID hid, int val)=0;
 	virtual void setManaPoints(ObjectInstanceID hid, int val)=0;
 	virtual void giveHero(ObjectInstanceID id, PlayerColor player)=0;
 	virtual void giveHero(ObjectInstanceID id, PlayerColor player)=0;
 	virtual void changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags)=0;
 	virtual void changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags)=0;
-	virtual void sendAndApply(CPackForClient * info)=0;
+	virtual void sendAndApply(CPackForClient * pack) = 0;
 	virtual void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)=0; //when two heroes meet on adventure map
 	virtual void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)=0; //when two heroes meet on adventure map
 	virtual void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) = 0;
 	virtual void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) = 0;
 	virtual void changeFogOfWar(std::unordered_set<int3, ShashInt3> &tiles, PlayerColor player, bool hide) = 0;
 	virtual void changeFogOfWar(std::unordered_set<int3, ShashInt3> &tiles, PlayerColor player, bool hide) = 0;

+ 64 - 204
lib/NetPacks.h

@@ -22,16 +22,15 @@
 
 
 #include "spells/ViewSpellInt.h"
 #include "spells/ViewSpellInt.h"
 
 
-class CCampaignState;
+class CClient;
+class CGameState;
+class CGameHandler;
 class CArtifact;
 class CArtifact;
-class CSelectionScreen;
 class CGObjectInstance;
 class CGObjectInstance;
 class CArtifactInstance;
 class CArtifactInstance;
 struct StackLocation;
 struct StackLocation;
 struct ArtSlotInfo;
 struct ArtSlotInfo;
 struct QuestInfo;
 struct QuestInfo;
-class CMapInfo;
-struct StartInfo;
 class IBattleState;
 class IBattleState;
 
 
 struct Query : public CPackForClient
 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
 struct SetCommanderProperty : public CPackForClient
 {
 {
 	enum ECommanderProperty {ALIVE, BONUS, SECONDARY_SKILL, EXPERIENCE, SPECIAL_SKILL};
 	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
 struct UpdateArtHandlerLists : public CPackForClient
 {
 {
 	UpdateArtHandlerLists(){}
 	UpdateArtHandlerLists(){}
@@ -1889,30 +1866,18 @@ struct CommitPackage : public CPackForServer
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & packToCommit;
 		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
 struct EndTurn : public CPackForServer
 {
 {
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
-	{}
+	{
+		h & static_cast<CPackForServer &>(*this);
+	}
 };
 };
 
 
 struct DismissHero : public CPackForServer
 struct DismissHero : public CPackForServer
@@ -1924,6 +1889,7 @@ struct DismissHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 		h & hid;
 	}
 	}
 };
 };
@@ -1939,6 +1905,7 @@ struct MoveHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & dest;
 		h & dest;
 		h & hid;
 		h & hid;
 		h & transit;
 		h & transit;
@@ -1956,6 +1923,7 @@ struct CastleTeleportHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & dest;
 		h & dest;
 		h & hid;
 		h & hid;
 	}
 	}
@@ -1974,6 +1942,7 @@ struct ArrangeStacks : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & what;
 		h & what;
 		h & p1;
 		h & p1;
 		h & p2;
 		h & p2;
@@ -1993,6 +1962,7 @@ struct DisbandCreature : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & pos;
 		h & pos;
 		h & id;
 		h & id;
 	}
 	}
@@ -2008,6 +1978,7 @@ struct BuildStructure : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & tid;
 		h & tid;
 		h & bid;
 		h & bid;
 	}
 	}
@@ -2033,6 +2004,7 @@ struct RecruitCreatures : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & tid;
 		h & tid;
 		h & dst;
 		h & dst;
 		h & crid;
 		h & crid;
@@ -2052,6 +2024,7 @@ struct UpgradeCreature : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & pos;
 		h & pos;
 		h & id;
 		h & id;
 		h & cid;
 		h & cid;
@@ -2067,6 +2040,7 @@ struct GarrisonHeroSwap : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & tid;
 		h & tid;
 	}
 	}
 };
 };
@@ -2079,6 +2053,7 @@ struct ExchangeArtifacts : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & src;
 		h & src;
 		h & dst;
 		h & dst;
 	}
 	}
@@ -2097,6 +2072,7 @@ struct AssembleArtifacts : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & heroID;
 		h & heroID;
 		h & artifactSlot;
 		h & artifactSlot;
 		h & assemble;
 		h & assemble;
@@ -2114,6 +2090,7 @@ struct BuyArtifact : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 		h & hid;
 		h & aid;
 		h & aid;
 	}
 	}
@@ -2135,6 +2112,7 @@ struct TradeOnMarketplace : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & marketId;
 		h & marketId;
 		h & heroId;
 		h & heroId;
 		h & mode;
 		h & mode;
@@ -2154,6 +2132,7 @@ struct SetFormation : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 		h & hid;
 		h & formation;
 		h & formation;
 	}
 	}
@@ -2170,6 +2149,7 @@ struct HireHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 		h & hid;
 		h & tid;
 		h & tid;
 		h & player;
 		h & player;
@@ -2184,6 +2164,7 @@ struct BuildBoat : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & objid;
 		h & objid;
 	}
 	}
 
 
@@ -2200,6 +2181,7 @@ struct QueryReply : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & qid;
 		h & qid;
 		h & player;
 		h & player;
 		h & reply;
 		h & reply;
@@ -2215,6 +2197,7 @@ struct MakeAction : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & ba;
 		h & ba;
 	}
 	}
 };
 };
@@ -2228,6 +2211,7 @@ struct MakeCustomAction : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & ba;
 		h & ba;
 	}
 	}
 };
 };
@@ -2240,6 +2224,7 @@ struct DigWithHero : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & id;
 		h & id;
 	}
 	}
 };
 };
@@ -2254,6 +2239,7 @@ struct CastAdvSpell : public CPackForServer
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & hid;
 		h & hid;
 		h & sid;
 		h & sid;
 		h & pos;
 		h & pos;
@@ -2262,212 +2248,86 @@ struct CastAdvSpell : public CPackForServer
 
 
 /***********************************************************************************************************/
 /***********************************************************************************************************/
 
 
-struct SaveGame : public CPackForClient, public CPackForServer
+struct SaveGame : public CPackForServer
 {
 {
 	SaveGame(){};
 	SaveGame(){};
 	SaveGame(const std::string &Fname) :fname(Fname){};
 	SaveGame(const std::string &Fname) :fname(Fname){};
 	std::string fname;
 	std::string fname;
 
 
-	void applyCl(CClient *cl);
 	void applyGs(CGameState *gs){};
 	void applyGs(CGameState *gs){};
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	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;
 		h & fname;
 	}
 	}
 };
 };
 
 
-struct PlayerMessage : public CPackForClient, public CPackForServer
+struct PlayerMessage : public CPackForServer
 {
 {
 	PlayerMessage(){};
 	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){};
 	void applyGs(CGameState *gs){};
 	bool applyGh(CGameHandler *gh);
 	bool applyGh(CGameHandler *gh);
 
 
-	PlayerColor player;
 	std::string text;
 	std::string text;
 	ObjectInstanceID currObj; // optional parameter that specifies current object. For cheats :)
 	ObjectInstanceID currObj; // optional parameter that specifies current object. For cheats :)
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
+		h & static_cast<CPackForServer &>(*this);
 		h & text;
 		h & text;
-		h & player;
 		h & currObj;
 		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);
 	void applyCl(CClient *cl);
 
 
 	PlayerColor player;
 	PlayerColor player;
-	int3 pos;
-	ui32 focusTime; //ms
+	std::string text;
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
-		h & pos;
 		h & player;
 		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)
 	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 CArtifactSet;
 class CBonusSystemNode;
 class CBonusSystemNode;
 struct ArtSlotInfo;
 struct ArtSlotInfo;
-class BattleInfo;
 
 
 #include "ConstTransitivePtr.h"
 #include "ConstTransitivePtr.h"
 #include "GameConstants.h"
 #include "GameConstants.h"
@@ -28,7 +27,9 @@ class BattleInfo;
 
 
 struct DLL_LINKAGE CPack
 struct DLL_LINKAGE CPack
 {
 {
-	CPack() {};
+	std::shared_ptr<CConnection> c; // Pointer to connection that pack received from
+
+	CPack() : c(nullptr) {};
 	virtual ~CPack() {};
 	virtual ~CPack() {};
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -54,12 +55,11 @@ struct CPackForClient : public CPack
 
 
 struct CPackForServer : public CPack
 struct CPackForServer : public CPack
 {
 {
-	PlayerColor player;
-	CConnection *c;
+	mutable PlayerColor player;
+	mutable si32 requestID;
 	CGameState* GS(CGameHandler *gh);
 	CGameState* GS(CGameHandler *gh);
 	CPackForServer():
 	CPackForServer():
-		player(PlayerColor::NEUTRAL),
-		c(nullptr)
+		player(PlayerColor::NEUTRAL)
 	{
 	{
 	}
 	}
 
 
@@ -69,6 +69,12 @@ struct CPackForServer : public CPack
 		return false;
 		return false;
 	}
 	}
 
 
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & player;
+		h & requestID;
+	}
+
 protected:
 protected:
 	void throwNotAllowedAction();
 	void throwNotAllowedAction();
 	void throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id);
 	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);
 	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)
 DLL_LINKAGE void SetCommanderProperty::applyGs(CGameState *gs)
 {
 {
 	CCommanderInstance * commander = gs->getHero(heroid)->commander;
 	CCommanderInstance * commander = gs->getHero(heroid)->commander;
@@ -346,8 +312,43 @@ DLL_LINKAGE void ChangeObjectVisitors::applyGs(CGameState *gs)
 DLL_LINKAGE void PlayerEndsGame::applyGs(CGameState *gs)
 DLL_LINKAGE void PlayerEndsGame::applyGs(CGameState *gs)
 {
 {
 	PlayerState *p = gs->getPlayer(player);
 	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)
 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 CMapGenOptions;
 class CCampaignState;
 class CCampaignState;
+class CMapInfo;
+struct PlayerInfo;
+class PlayerColor;
+class SharedMemory;
 
 
 /// Struct which describes the name, the color, the starting bonus of a player
 /// 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
 	enum { PLAYER_AI = 0 }; // for use in playerID
 
 
@@ -39,7 +43,7 @@ struct PlayerSettings
 	TeamID team;
 	TeamID team;
 
 
 	std::string name;
 	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
 	bool compOnly; //true if this player is a computer only player; required for RMG
 	template <typename Handler>
 	template <typename Handler>
 	void serialize(Handler &h, const int version)
 	void serialize(Handler &h, const int version)
@@ -52,20 +56,30 @@ struct PlayerSettings
 		h & color;
 		h & color;
 		h & handicap;
 		h & handicap;
 		h & name;
 		h & name;
-		h & playerID;
+		if(version < 787)
+		{
+			ui8 oldConnectedId = 0;
+			h & oldConnectedId;
+			if(oldConnectedId)
+			{
+				connectedPlayerIDs.insert(oldConnectedId);
+			}
+		}
+		else
+		{
+			h & connectedPlayerIDs;
+		}
 		h & team;
 		h & team;
 		h & compOnly;
 		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 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};
 	enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, INVALID = 255};
 
 
@@ -85,26 +99,12 @@ struct StartInfo
 
 
 	std::shared_ptr<CCampaignState> campState;
 	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>
 	template <typename Handler>
 	void serialize(Handler &h, const int version)
 	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
 class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public spells::Caster
 {
 {
+	// We serialize heroes into JSON for crossover
+	friend class CCampaignState;
 public:
 public:
 	//////////////////////////////////////////////////////////////////////////
 	//////////////////////////////////////////////////////////////////////////
 
 

+ 1 - 1
lib/mapObjects/CGPandoraBox.cpp

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

+ 6 - 0
lib/mapObjects/CGTownInstance.cpp

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

+ 1 - 0
lib/mapObjects/CGTownInstance.h

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

+ 107 - 13
lib/mapping/CCampaignHandler.cpp

@@ -23,8 +23,12 @@
 #include "../CHeroHandler.h"
 #include "../CHeroHandler.h"
 #include "CMapService.h"
 #include "CMapService.h"
 #include "CMap.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()
 CCampaignHeader::CCampaignHeader()
 	: version(0), mapVersion(0), difficultyChoosenByPlayer(0), music(0), filename(), loadFromLod(0)
 	: version(0), mapVersion(0), difficultyChoosenByPlayer(0), music(0), filename(), loadFromLod(0)
@@ -373,25 +377,42 @@ bool CCampaignScenario::isNotVoid() const
 	return mapName.size() > 0;
 	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;
 	std::vector<CGHeroInstance *> lostCrossoverHeroes;
 	if(conquered)
 	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())
 			if(it == crossoverHeroes.end())
 			{
 			{
 				lostCrossoverHeroes.push_back(hero);
 				lostCrossoverHeroes.push_back(hero);
@@ -401,9 +422,25 @@ std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes() const
 	return lostCrossoverHeroes;
 	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);
 	mapsConquered.push_back(*currentMap);
 	mapsRemaining -= *currentMap;
 	mapsRemaining -= *currentMap;
 	camp->scenarios[*currentMap].conquered = true;
 	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)
 std::string CCampaignHandler::prologVideoName(ui8 index)
 {
 {
 	JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));
 	JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));

+ 30 - 7
lib/mapping/CCampaignHandler.h

@@ -14,6 +14,10 @@
 struct StartInfo;
 struct StartInfo;
 class CGHeroInstance;
 class CGHeroInstance;
 class CBinaryReader;
 class CBinaryReader;
+class CMap;
+class CMapHeader;
+class CMapInfo;
+class JsonNode;
 
 
 namespace CampaignVersion
 namespace CampaignVersion
 {
 {
@@ -134,13 +138,15 @@ public:
 
 
 	CScenarioTravel travelOptions;
 	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<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);
 	void loadPreconditionRegions(ui32 regions);
 	bool isNotVoid() const;
 	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();
 	CCampaignScenario();
 
 
@@ -157,8 +163,19 @@ public:
 		h & prolog;
 		h & prolog;
 		h & epilog;
 		h & epilog;
 		h & travelOptions;
 		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;
 		h & keepHeroes;
 	}
 	}
 };
 };
@@ -192,13 +209,19 @@ public:
 
 
 	std::map<ui8, ui8> chosenCampaignBonuses;
 	std::map<ui8, ui8> chosenCampaignBonuses;
 
 
-	//void initNewCampaign(const StartInfo &si);
 	void setCurrentMapAsConquered(const std::vector<CGHeroInstance*> & heroes);
 	void setCurrentMapAsConquered(const std::vector<CGHeroInstance*> & heroes);
 	boost::optional<CScenarioTravel::STravelBonus> getBonusForCurrentMap() const;
 	boost::optional<CScenarioTravel::STravelBonus> getBonusForCurrentMap() const;
 	const CCampaignScenario & getCurrentScenario() const;
 	const CCampaignScenario & getCurrentScenario() const;
 	CCampaignScenario & getCurrentScenario();
 	CCampaignScenario & getCurrentScenario();
 	ui8 currentBonusID() const;
 	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();
 	CCampaignState(std::unique_ptr<CCampaign> _camp);
 	CCampaignState(std::unique_ptr<CCampaign> _camp);
 	~CCampaignState(){};
 	~CCampaignState(){};

+ 10 - 5
lib/mapping/CMap.cpp

@@ -184,11 +184,6 @@ bool TerrainTile::isWater() const
 	return terType == ETerrainType::WATER;
 	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()
 void CMapHeader::setupEvents()
 {
 {
 	EventCondition victoryCondition(EventCondition::STANDARD_WIN);
 	EventCondition victoryCondition(EventCondition::STANDARD_WIN);
@@ -269,6 +264,8 @@ CMap::~CMap()
 
 
 	for(auto quest : quests)
 	for(auto quest : quests)
 		quest.dellNull();
 		quest.dellNull();
+
+	resetStaticData();
 }
 }
 
 
 void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
 void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
@@ -647,3 +644,11 @@ CMapEditManager * CMap::getEditManager()
 	if(!editManager) editManager = make_unique<CMapEditManager>(this);
 	if(!editManager) editManager = make_unique<CMapEditManager>(this);
 	return editManager.get();
 	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();
 	void setupEvents();
 public:
 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();
 	CMapHeader();
 	virtual ~CMapHeader();
 	virtual ~CMapHeader();
@@ -382,6 +383,8 @@ public:
 	/// Sets the victory/loss condition objectives ??
 	/// Sets the victory/loss condition objectives ??
 	void checkForObjectives();
 	void checkForObjectives();
 
 
+	void resetStaticData();
+
 	ui32 checksum;
 	ui32 checksum;
 	std::vector<Rumor> rumors;
 	std::vector<Rumor> rumors;
 	std::vector<DisposedHero> disposedHeroes;
 	std::vector<DisposedHero> disposedHeroes;

+ 136 - 47
lib/mapping/CMapInfo.cpp

@@ -15,81 +15,170 @@
 #include "../GameConstants.h"
 #include "../GameConstants.h"
 #include "CMapService.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()
 void CMapInfo::countPlayers()
 {
 {
-	actualHumanPlayers = playerAmnt = humanPlayers = 0;
 	for(int i=0; i<PlayerColor::PLAYER_LIMIT_I; i++)
 	for(int i=0; i<PlayerColor::PLAYER_LIMIT_I; i++)
 	{
 	{
 		if(mapHeader->players[i].canHumanPlay)
 		if(mapHeader->players[i].canHumanPlay)
 		{
 		{
-			playerAmnt++;
-			humanPlayers++;
+			amountOfPlayersOnMap++;
+			amountOfHumanControllablePlayers++;
 		}
 		}
 		else if(mapHeader->players[i].canComputerPlay)
 		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:
 public:
 	std::unique_ptr<CMapHeader> mapHeader; //may be nullptr if campaign
 	std::unique_ptr<CMapHeader> mapHeader; //may be nullptr if campaign
 	std::unique_ptr<CCampaignHeader> campaignHeader; //may be nullptr if scenario
 	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 fileURI;
 	std::string date;
 	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(CMapInfo && tmp);
 	virtual ~CMapInfo();
 	virtual ~CMapInfo();
 
 
 	CMapInfo &operator=(CMapInfo &&other);
 	CMapInfo &operator=(CMapInfo &&other);
 
 
 	void mapInit(const std::string & fname);
 	void mapInit(const std::string & fname);
+	void saveInit(ResourceID file);
 	void campaignInit();
 	void campaignInit();
 	void countPlayers();
 	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)
 	template <typename Handler> void serialize(Handler &h, const int Version)
 	{
 	{
 		h & mapHeader;
 		h & mapHeader;
 		h & campaignHeader;
 		h & campaignHeader;
-		h & scenarioOpts;
+		h & scenarioOptionsOfSave;
 		h & fileURI;
 		h & fileURI;
 		h & date;
 		h & date;
-		h & playerAmnt;
-		h & humanPlayers;
-		h & actualHumanPlayers;
+		h & amountOfPlayersOnMap;
+		h & amountOfHumanControllablePlayers;
+		h & amountOfHumanPlayersInSave;
 		h & isRandomMap;
 		h & isRandomMap;
 	}
 	}
 };
 };

+ 1 - 1
lib/registerTypes/RegisterTypes.cpp

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

+ 31 - 23
lib/registerTypes/RegisterTypes.h

@@ -10,6 +10,7 @@
 #pragma once
 #pragma once
 
 
 #include "../NetPacks.h"
 #include "../NetPacks.h"
+#include "../NetPacksLobby.h"
 #include "../VCMI_Lib.h"
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CArtHandler.h"
 #include "../CPlayerState.h"
 #include "../CPlayerState.h"
@@ -234,8 +235,6 @@ void registerTypesClientPacks1(Serializer &s)
 	s.template registerType<CPackForClient, ChangeObjPos>();
 	s.template registerType<CPackForClient, ChangeObjPos>();
 	s.template registerType<CPackForClient, PlayerEndsGame>();
 	s.template registerType<CPackForClient, PlayerEndsGame>();
 	s.template registerType<CPackForClient, RemoveBonus>();
 	s.template registerType<CPackForClient, RemoveBonus>();
-	s.template registerType<CPackForClient, UpdateCampaignState>();
-	s.template registerType<CPackForClient, PrepareForAdvancingCampaign>();
 	s.template registerType<CPackForClient, UpdateArtHandlerLists>();
 	s.template registerType<CPackForClient, UpdateArtHandlerLists>();
 	s.template registerType<CPackForClient, UpdateMapEvents>();
 	s.template registerType<CPackForClient, UpdateMapEvents>();
 	s.template registerType<CPackForClient, UpdateCastleEvents>();
 	s.template registerType<CPackForClient, UpdateCastleEvents>();
@@ -312,16 +311,14 @@ void registerTypesClientPacks2(Serializer &s)
 	s.template registerType<CArtifactOperationPack, AssembledArtifact>();
 	s.template registerType<CArtifactOperationPack, AssembledArtifact>();
 	s.template registerType<CArtifactOperationPack, DisassembledArtifact>();
 	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>
 template<typename Serializer>
 void registerTypesServerPacks(Serializer &s)
 void registerTypesServerPacks(Serializer &s)
 {
 {
 	s.template registerType<CPack, CPackForServer>();
 	s.template registerType<CPack, CPackForServer>();
-	s.template registerType<CPackForServer, CloseServer>();
-	s.template registerType<CPackForServer, LeaveGame>();
 	s.template registerType<CPackForServer, EndTurn>();
 	s.template registerType<CPackForServer, EndTurn>();
 	s.template registerType<CPackForServer, DismissHero>();
 	s.template registerType<CPackForServer, DismissHero>();
 	s.template registerType<CPackForServer, MoveHero>();
 	s.template registerType<CPackForServer, MoveHero>();
@@ -351,23 +348,34 @@ void registerTypesServerPacks(Serializer &s)
 }
 }
 
 
 template<typename Serializer>
 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>
 template<typename Serializer>
@@ -379,7 +387,7 @@ void registerTypes(Serializer &s)
 	registerTypesClientPacks1(s);
 	registerTypesClientPacks1(s);
 	registerTypesClientPacks2(s);
 	registerTypesClientPacks2(s);
 	registerTypesServerPacks(s);
 	registerTypesServerPacks(s);
-	registerTypesPregamePacks(s);
+	registerTypesLobbyPacks(s);
 }
 }
 
 
 #ifndef INSTANTIATE_REGISTER_TYPES_HERE
 #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 "../VCMI_Lib.h"
 #include "../CTownHandler.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),
 	playerCount(RANDOM_SIZE), teamCount(RANDOM_SIZE), compOnlyPlayerCount(RANDOM_SIZE), compOnlyTeamCount(RANDOM_SIZE), humanPlayersCount(0),
 	waterContent(EWaterContent::RANDOM), monsterStrength(EMonsterStrength::RANDOM), mapTemplate(nullptr)
 	waterContent(EWaterContent::RANDOM), monsterStrength(EMonsterStrength::RANDOM), mapTemplate(nullptr)
 {
 {

+ 1 - 1
lib/serializer/CSerializer.h

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

+ 43 - 56
lib/serializer/Connection.cpp

@@ -14,9 +14,6 @@
 #include "../mapping/CMap.h"
 #include "../mapping/CMap.h"
 #include "../CGameState.h"
 #include "../CGameState.h"
 
 
-#if BOOST_VERSION >= 106600
-#define BOOST_ASIO_ENABLE_OLD_SERVICES
-#endif
 #include <boost/asio.hpp>
 #include <boost/asio.hpp>
 
 
 using namespace boost;
 using namespace boost;
@@ -35,8 +32,9 @@ using namespace boost::asio::ip;
 
 
 void CConnection::init()
 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();
 	enableSmartPointerSerialization();
 	disableStackSendingByID();
 	disableStackSendingByID();
@@ -50,25 +48,21 @@ void CConnection::init()
 	connected = true;
 	connected = true;
 	std::string pom;
 	std::string pom;
 	//we got connection
 	//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;
 	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;
 	int i;
 	boost::system::error_code error = asio::error::host_not_found;
 	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 resolver(*io_service);
 	tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, std::to_string(port)),error);
 	tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, std::to_string(port)),error);
 	if(error)
 	if(error)
@@ -114,25 +108,23 @@ connerror1:
 		logNetwork->error(error.message());
 		logNetwork->error(error.message());
 	else
 	else
 		logNetwork->error("No error info. ");
 		logNetwork->error("No error info. ");
-	delete io_service;
-	//delete socket;
 	throw std::runtime_error("Can't establish connection :(");
 	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();
 	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;
 	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);
 	acceptor->accept(*socket,error);
 	if (error)
 	if (error)
 	{
 	{
 		logNetwork->error("Error on accepting: %s", error.message());
 		logNetwork->error("Error on accepting: %s", error.message());
-		delete socket;
+		socket.reset();
 		throw std::runtime_error("Can't establish connection :(");
 		throw std::runtime_error("Can't establish connection :(");
 	}
 	}
 	init();
 	init();
@@ -171,12 +163,7 @@ CConnection::~CConnection()
 	if(handler)
 	if(handler)
 		handler->join();
 		handler->join();
 
 
-	delete handler;
-
 	close();
 	close();
-	delete io_service;
-	delete wmx;
-	delete rmx;
 }
 }
 
 
 template<class T>
 template<class T>
@@ -193,7 +180,7 @@ void CConnection::close()
 	if(socket)
 	if(socket)
 	{
 	{
 		socket->close();
 		socket->close();
-		vstd::clear_pointer(socket);
+		socket.reset();
 	}
 	}
 }
 }
 
 
@@ -202,11 +189,6 @@ bool CConnection::isOpen() const
 	return socket && connected;
 	return socket && connected;
 }
 }
 
 
-bool CConnection::isHost() const
-{
-	return connectionID == 1;
-}
-
 void CConnection::reportState(vstd::CLoggerBase * out)
 void CConnection::reportState(vstd::CLoggerBase * out)
 {
 {
 	out->debug("CConnection");
 	out->debug("CConnection");
@@ -219,19 +201,26 @@ void CConnection::reportState(vstd::CLoggerBase * out)
 
 
 CPack * CConnection::retrievePack()
 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("Received 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()
 void CConnection::disableStackSendingByID()
@@ -254,21 +243,19 @@ void CConnection::enableSmartPointerSerialization()
 	iser.smartPointerSerialization = oser.smartPointerSerialization = true;
 	iser.smartPointerSerialization = oser.smartPointerSerialization = true;
 }
 }
 
 
-void CConnection::prepareForSendingHeroes()
+void CConnection::enterLobbyConnectionMode()
 {
 {
 	iser.loadedPointers.clear();
 	iser.loadedPointers.clear();
 	oser.savedPointers.clear();
 	oser.savedPointers.clear();
 	disableSmartVectorMemberSerialization();
 	disableSmartVectorMemberSerialization();
-	enableSmartPointerSerialization();
-	disableStackSendingByID();
+	disableSmartPointerSerialization();
 }
 }
 
 
-void CConnection::enterPregameConnectionMode()
+void CConnection::enterGameplayConnectionMode(CGameState * gs)
 {
 {
-	iser.loadedPointers.clear();
-	oser.savedPointers.clear();
-	disableSmartVectorMemberSerialization();
+	enableStackSendingByID();
 	disableSmartPointerSerialization();
 	disableSmartPointerSerialization();
+	addStdVecItems(gs);
 }
 }
 
 
 void CConnection::disableSmartVectorMemberSerialization()
 void CConnection::disableSmartVectorMemberSerialization()
@@ -283,7 +270,7 @@ void CConnection::enableSmartVectorMemberSerializatoin()
 
 
 std::string CConnection::toString() const
 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
 /// Main class for network communication
 /// Allows establishing connection and bidirectional read-write
 /// Allows establishing connection and bidirectional read-write
 class DLL_LINKAGE CConnection
 class DLL_LINKAGE CConnection
-	: public IBinaryReader, public IBinaryWriter
+	: public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this<CConnection>
 {
 {
 	CConnection();
 	CConnection();
 
 
@@ -60,31 +60,31 @@ public:
 	BinaryDeserializer iser;
 	BinaryDeserializer iser;
 	BinarySerializer oser;
 	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 connected;
 	bool myEndianess, contactEndianess; //true if little endian, if endianness is different we'll have to revert received multi-byte vars
 	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 name; //who uses this connection
+	std::string uuid;
 
 
 	int connectionID;
 	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();
 	void close();
 	bool isOpen() const;
 	bool isOpen() const;
-	bool isHost() const;
 	template<class T>
 	template<class T>
 	CConnection &operator&(const T&);
 	CConnection &operator&(const T&);
 	virtual ~CConnection();
 	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 disableStackSendingByID();
 	void enableStackSendingByID();
 	void enableStackSendingByID();
@@ -93,8 +93,8 @@ public:
 	void disableSmartVectorMemberSerialization();
 	void disableSmartVectorMemberSerialization();
 	void enableSmartVectorMemberSerializatoin();
 	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;
 	std::string toString() const;
 
 

+ 1 - 1
lib/spells/Magic.h

@@ -60,7 +60,7 @@ class DLL_LINKAGE PacketSender
 {
 {
 public:
 public:
 	virtual ~PacketSender(){};
 	virtual ~PacketSender(){};
-	virtual void sendAndApply(CPackForClient * info) const = 0;
+	virtual void sendAndApply(CPackForClient * pack) const = 0;
 	virtual void complain(const std::string & problem) const = 0;
 	virtual void complain(const std::string & problem) const = 0;
 };
 };
 
 

+ 101 - 230
server/CGameHandler.cpp

@@ -50,7 +50,6 @@
 #ifndef _MSC_VER
 #ifndef _MSC_VER
 #include <boost/thread/xtime.hpp>
 #include <boost/thread/xtime.hpp>
 #endif
 #endif
-extern std::atomic<bool> serverShuttingDown;
 
 
 #define COMPLAIN_RET_IF(cond, txt) do {if (cond){complain(txt); return;}} while(0)
 #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)
 #define COMPLAIN_RET_FALSE_IF(cond, txt) do {if (cond){complain(txt); return false;}} while(0)
@@ -62,7 +61,7 @@ class ServerSpellCastEnvironment : public SpellCastEnvironment
 public:
 public:
 	ServerSpellCastEnvironment(CGameHandler * gh);
 	ServerSpellCastEnvironment(CGameHandler * gh);
 	~ServerSpellCastEnvironment() = default;
 	~ServerSpellCastEnvironment() = default;
-	void sendAndApply(CPackForClient * info) const override;
+	void sendAndApply(CPackForClient * pack) const override;
 	CRandomGenerator & getRandomGenerator() const override;
 	CRandomGenerator & getRandomGenerator() const override;
 	void complain(const std::string & problem) const override;
 	void complain(const std::string & problem) const override;
 	const CMap * getMap() const override;
 	const CMap * getMap() const override;
@@ -177,7 +176,7 @@ template <typename T> class CApplyOnGH;
 class CBaseForGHApply
 class CBaseForGHApply
 {
 {
 public:
 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(){}
 	virtual ~CBaseForGHApply(){}
 	template<typename U> static CBaseForGHApply *getApplier(const U * t=nullptr)
 	template<typename U> static CBaseForGHApply *getApplier(const U * t=nullptr)
 	{
 	{
@@ -188,11 +187,9 @@ public:
 template <typename T> class CApplyOnGH : public CBaseForGHApply
 template <typename T> class CApplyOnGH : public CBaseForGHApply
 {
 {
 public:
 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);
 		T *ptr = static_cast<T*>(pack);
-		ptr->c = c;
-		ptr->player = player;
 		try
 		try
 		{
 		{
 			return ptr->applyGh(gh);
 			return ptr->applyGh(gh);
@@ -212,7 +209,7 @@ template <>
 class CApplyOnGH<CPack> : public CBaseForGHApply
 class CApplyOnGH<CPack> : public CBaseForGHApply
 {
 {
 public:
 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!");
 		logGlobal->error("Cannot apply on GH plain CPack!");
 		assert(0);
 		assert(0);
@@ -220,8 +217,6 @@ public:
 	}
 	}
 };
 };
 
 
-static CApplier<CBaseForGHApply> *applier = nullptr;
-
 static inline double distance(int3 a, int3 b)
 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));
 	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));
 		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;
 				PlayerCheated pc;
-				pc.player = playerConn.first;
+				pc.player = playerConns.first;
 				pc.losingCheatCode = true;
 				pc.losingCheatCode = true;
 				sendAndApply(&pc);
 				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)
 int CGameHandler::moveStack(int stack, BattleHex dest)
@@ -1569,12 +1514,12 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 	return ret;
 	return ret;
 }
 }
 
 
-CGameHandler::CGameHandler()
+CGameHandler::CGameHandler(CVCMIServer * lobby)
+	: lobby(lobby)
 {
 {
 	QID = 1;
 	QID = 1;
-	//gs = nullptr;
 	IObjectInterface::cb = this;
 	IObjectInterface::cb = this;
-	applier = new CApplier<CBaseForGHApply>();
+	applier = std::make_shared<CApplier<CBaseForGHApply>>();
 	registerTypesServerPacks(*applier);
 	registerTypesServerPacks(*applier);
 	visitObjectAfterVictory = false;
 	visitObjectAfterVictory = false;
 
 
@@ -1584,8 +1529,6 @@ CGameHandler::CGameHandler()
 CGameHandler::~CGameHandler()
 CGameHandler::~CGameHandler()
 {
 {
 	delete spellEnv;
 	delete spellEnv;
-	delete applier;
-	applier = nullptr;
 	delete gs;
 	delete gs;
 }
 }
 
 
@@ -1988,16 +1931,9 @@ void CGameHandler::run(bool resume)
 	LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
 	LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
 
 
 	using namespace boost::posix_time;
 	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;
 		std::stringstream sbuffer;
 		sbuffer << "Connection " << cc->connectionID << " will handle " << players.size() << " player: ";
 		sbuffer << "Connection " << cc->connectionID << " will handle " << players.size() << " player: ";
 		for (PlayerColor color : players)
 		for (PlayerColor color : players)
@@ -2005,30 +1941,15 @@ void CGameHandler::run(bool resume)
 			sbuffer << color << " ";
 			sbuffer << color << " ";
 			{
 			{
 				boost::unique_lock<boost::recursive_mutex> lock(gsm);
 				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());
 		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();
 	auto playerTurnOrder = generatePlayerTurnOrder();
 
 
-	while(!serverShuttingDown)
+	while(lobby->state == EServerState::GAMEPLAY)
 	{
 	{
 		if (!resume) newTurn();
 		if (!resume) newTurn();
 
 
@@ -2069,12 +1990,14 @@ void CGameHandler::run(bool resume)
 
 
 					//wait till turn is done
 					//wait till turn is done
 					boost::unique_lock<boost::mutex> lock(states.mx);
 					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);
 						static time_duration p = milliseconds(100);
 						states.cv.timed_wait(lock, p);
 						states.cv.timed_wait(lock, p);
 					}
 					}
 				}
 				}
+				if(lobby->state != EServerState::GAMEPLAY)
+					break;
 			}
 			}
 		}
 		}
 		//additional check that game is not finished
 		//additional check that game is not finished
@@ -2084,11 +2007,9 @@ void CGameHandler::run(bool resume)
 			if (gs->players[player].status == EPlayerStatus::INGAME)
 			if (gs->players[player].status == EPlayerStatus::INGAME)
 					activePlayer = true;
 					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
 std::list<PlayerColor> CGameHandler::generatePlayerTurnOrder() const
@@ -2619,12 +2540,12 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st
 	sendAndApply(&cs);
 	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;
 	SystemMessage sm;
 	sm.text = message;
 	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)
 void CGameHandler::giveHeroBonus(GiveBonus * bonus)
@@ -2773,59 +2694,59 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
 	}
 	}
 }
 }
 
 
-void CGameHandler::sendToAllClients(CPackForClient * info)
+void CGameHandler::sendToAllClients(CPackForClient * pack)
 {
 {
-	logNetwork->trace("Sending to all clients a package of type %s", typeid(*info).name());
-	for (auto & elem : conns)
+	logNetwork->trace("\tSending to all clients: %s", typeid(*pack).name());
+	for (auto c : lobby->connections)
 	{
 	{
-		if(!elem->isOpen())
+		if(!c->isOpen())
 			continue;
 			continue;
 
 
-		boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
-		*elem << info;
+		c->sendPack(pack);
 	}
 	}
 }
 }
 
 
-void CGameHandler::sendAndApply(CPackForClient * info)
+void CGameHandler::sendAndApply(CPackForClient * pack)
 {
 {
-	sendToAllClients(info);
-	gs->apply(info);
+	sendToAllClients(pack);
+	gs->apply(pack);
+	logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
 }
 }
 
 
-void CGameHandler::applyAndSend(CPackForClient * info)
+void CGameHandler::applyAndSend(CPackForClient * pack)
 {
 {
-	gs->apply(info);
-	sendToAllClients(info);
+	gs->apply(pack);
+	sendToAllClients(pack);
 }
 }
 
 
-void CGameHandler::sendAndApply(CGarrisonOperationPack * info)
+void CGameHandler::sendAndApply(CGarrisonOperationPack * pack)
 {
 {
-	sendAndApply(static_cast<CPackForClient*>(info));
+	sendAndApply(static_cast<CPackForClient *>(pack));
 	checkVictoryLossConditionsForAll();
 	checkVictoryLossConditionsForAll();
 }
 }
 
 
-void CGameHandler::sendAndApply(SetResources * info)
+void CGameHandler::sendAndApply(SetResources * pack)
 {
 {
-	sendAndApply(static_cast<CPackForClient*>(info));
-	checkVictoryLossConditionsForPlayer(info->player);
+	sendAndApply(static_cast<CPackForClient *>(pack));
+	checkVictoryLossConditionsForPlayer(pack->player);
 }
 }
 
 
-void CGameHandler::sendAndApply(NewStructures * info)
+void CGameHandler::sendAndApply(NewStructures * pack)
 {
 {
-	sendAndApply(static_cast<CPackForClient*>(info));
-	checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner);
+	sendAndApply(static_cast<CPackForClient *>(pack));
+	checkVictoryLossConditionsForPlayer(getTown(pack->tid)->tempOwner);
 }
 }
 
 
 void CGameHandler::save(const std::string & filename)
 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 stem	= FileInfo::GetPathStem(filename);
 	const auto savefname = stem.to_string() + ".vsgm1";
 	const auto savefname = stem.to_string() + ".vsgm1";
 	CResourceHandler::get("local")->createResource(savefname);
 	CResourceHandler::get("local")->createResource(savefname);
 
 
 	{
 	{
 		logGlobal->info("Ordering clients to serialize...");
 		logGlobal->info("Ordering clients to serialize...");
-		SaveGame sg(savefname);
+		SaveGameClient sg(savefname);
 		sendToAllClients(&sg);
 		sendToAllClients(&sg);
 	}
 	}
 
 
@@ -2845,34 +2766,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)
 bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player)
@@ -3014,11 +2927,11 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
 	return true;
 	return true;
 }
 }
 
 
-PlayerColor CGameHandler::getPlayerAt(CConnection *c) const
+PlayerColor CGameHandler::getPlayerAt(std::shared_ptr<CConnection> c) const
 {
 {
 	std::set<PlayerColor> all;
 	std::set<PlayerColor> all;
 	for (auto i=connections.cbegin(); i!=connections.cend(); i++)
 	for (auto i=connections.cbegin(); i!=connections.cend(); i++)
-		if (i->second == c)
+		if(vstd::contains(i->second, c))
 			all.insert(i->first);
 			all.insert(i->first);
 
 
 	switch(all.size())
 	switch(all.size())
@@ -4567,7 +4480,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
 void CGameHandler::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj)
 void CGameHandler::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj)
 {
 {
 	bool cheated = true;
 	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);
 	sendAndApply(&temp_message);
 
 
 	std::vector<std::string> cheat;
 	std::vector<std::string> cheat;
@@ -5304,51 +5217,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
 		else
@@ -6856,9 +6727,9 @@ ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh
 
 
 }
 }
 
 
-void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
+void ServerSpellCastEnvironment::sendAndApply(CPackForClient * pack) const
 {
 {
-	gh->sendAndApply(info);
+	gh->sendAndApply(pack);
 }
 }
 
 
 CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const
 CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const

+ 19 - 18
server/CGameHandler.h

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

+ 1 - 0
server/CMakeLists.txt

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

+ 960 - 682
server/CVCMIServer.cpp

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

+ 3 - 3
test/game/CGameStateTest.cpp

@@ -44,9 +44,9 @@ public:
 		IObjectInterface::cb = nullptr;
 		IObjectInterface::cb = nullptr;
 	}
 	}
 
 
-	void sendAndApply(CPackForClient * info) const override
+	void sendAndApply(CPackForClient * pack) const override
 	{
 	{
-		gameState->apply(info);
+		gameState->apply(pack);
 	}
 	}
 
 
 	void complain(const std::string & problem) const
 	void complain(const std::string & problem) const
@@ -108,7 +108,7 @@ public:
 
 
 			PlayerSettings & pset = si.playerInfos[PlayerColor(i)];
 			PlayerSettings & pset = si.playerInfos[PlayerColor(i)];
 			pset.color = PlayerColor(i);
 			pset.color = PlayerColor(i);
-			pset.playerID = i;
+			pset.connectedPlayerIDs.insert(i);
 			pset.name = "Player";
 			pset.name = "Player";
 
 
 			pset.castle = pinfo.defaultCastle();
 			pset.castle = pinfo.defaultCastle();

+ 2 - 2
test/mock/mock_IGameCallback.cpp

@@ -32,7 +32,7 @@ void GameCallbackMock::commitPackage(CPackForClient * pack)
 	sendAndApply(pack);
 	sendAndApply(pack);
 }
 }
 
 
-void GameCallbackMock::sendAndApply(CPackForClient * info)
+void GameCallbackMock::sendAndApply(CPackForClient * pack)
 {
 {
-	upperCallback->sendAndApply(info);
+	upperCallback->sendAndApply(pack);
 }
 }

+ 1 - 1
test/mock/mock_IGameCallback.h

@@ -86,7 +86,7 @@ public:
 
 
 	///useful callback methods
 	///useful callback methods
 	void commitPackage(CPackForClient * pack) override;
 	void commitPackage(CPackForClient * pack) override;
-	void sendAndApply(CPackForClient * info) override;
+	void sendAndApply(CPackForClient * pack) override;
 private:
 private:
 	const UpperCallback * upperCallback;
 	const UpperCallback * upperCallback;
 };
 };

Деякі файли не було показано, через те що забагато файлів було змінено