瀏覽代碼

Merge branch 'develop' of https://github.com/vcmi/vcmi into develop

DJWarmonger 8 年之前
父節點
當前提交
e901fcd293
共有 82 個文件被更改,包括 1309 次插入823 次删除
  1. 5 7
      .travis.yml
  2. 3 4
      AI/BattleAI/BattleAI.cpp
  3. 5 0
      ChangeLog
  4. 4 0
      Global.h
  5. 104 53
      client/CMT.cpp
  6. 1 3
      client/CMT.h
  7. 2 0
      client/CMessage.cpp
  8. 23 12
      client/CPlayerInterface.cpp
  9. 1 0
      client/CPlayerInterface.h
  10. 15 12
      client/CPreGame.cpp
  11. 3 3
      client/CPreGame.h
  12. 140 71
      client/Client.cpp
  13. 9 6
      client/Client.h
  14. 46 29
      client/NetPacksClient.cpp
  15. 0 2
      client/VCMI_client.vcxproj
  16. 2 8
      client/VCMI_client.vcxproj.filters
  17. 38 14
      client/battle/CBattleInterface.cpp
  18. 1 1
      client/battle/CBattleInterface.h
  19. 4 8
      client/battle/CBattleInterfaceClasses.cpp
  20. 91 59
      client/gui/CGuiHandler.cpp
  21. 3 0
      client/gui/CGuiHandler.h
  22. 22 12
      client/gui/CIntObject.cpp
  23. 10 7
      client/gui/CIntObject.h
  24. 6 3
      client/mapHandler.cpp
  25. 1 1
      client/widgets/AdventureMapClasses.cpp
  26. 1 1
      client/widgets/Buttons.cpp
  27. 10 9
      client/widgets/CArtifactHolder.cpp
  28. 53 31
      client/windows/CAdvmapInterface.cpp
  29. 5 8
      client/windows/CAdvmapInterface.h
  30. 2 1
      client/windows/CCastleInterface.cpp
  31. 3 3
      client/windows/CSpellWindow.cpp
  32. 5 1
      client/windows/CWindowObject.cpp
  33. 1 1
      client/windows/GUIClasses.cpp
  34. 2 0
      client/windows/InfoWindows.cpp
  35. 9 5
      config/artifacts.json
  36. 7 1
      config/schemas/artifact.json
  37. 8 4
      config/schemas/settings.json
  38. 1 1
      launcher/settingsView/csettingsview_moc.cpp
  39. 26 12
      lib/BattleInfo.cpp
  40. 45 66
      lib/CArtHandler.cpp
  41. 12 8
      lib/CArtHandler.h
  42. 2 103
      lib/CBattleCallback.cpp
  43. 0 4
      lib/CBattleCallback.h
  44. 20 0
      lib/CCreatureHandler.cpp
  45. 13 0
      lib/CCreatureHandler.h
  46. 1 1
      lib/CGameInfoCallback.cpp
  47. 40 6
      lib/CGameState.cpp
  48. 2 2
      lib/CGameState.h
  49. 1 1
      lib/CTownHandler.cpp
  50. 6 0
      lib/GameConstants.cpp
  51. 2 0
      lib/GameConstants.h
  52. 35 12
      lib/Interprocess.h
  53. 22 0
      lib/NetPacks.h
  54. 9 0
      lib/NetPacksLib.cpp
  55. 1 1
      lib/VCMI_lib.vcxproj
  56. 1 1
      lib/VCMI_lib.vcxproj.filters
  57. 41 40
      lib/mapObjects/CGHeroInstance.cpp
  58. 1 1
      lib/mapObjects/MiscObjects.cpp
  59. 3 1
      lib/mapping/CMapInfo.cpp
  60. 27 14
      lib/mapping/CMapService.cpp
  61. 6 3
      lib/mapping/CMapService.h
  62. 10 2
      lib/mapping/MapFormatH3M.cpp
  63. 5 0
      lib/mapping/MapFormatJson.cpp
  64. 2 0
      lib/registerTypes/RegisterTypes.h
  65. 0 1
      lib/registerTypes/TypesClientPacks1.cpp
  66. 0 1
      lib/registerTypes/TypesClientPacks2.cpp
  67. 0 1
      lib/registerTypes/TypesMapObjects1.cpp
  68. 0 1
      lib/registerTypes/TypesMapObjects2.cpp
  69. 0 1
      lib/registerTypes/TypesMapObjects3.cpp
  70. 0 1
      lib/registerTypes/TypesServerPacks.cpp
  71. 1 1
      lib/serializer/CSerializer.h
  72. 8 4
      lib/serializer/Connection.cpp
  73. 2 1
      lib/serializer/Connection.h
  74. 47 3
      lib/spells/CSpellHandler.cpp
  75. 6 2
      lib/spells/ISpellMechanics.cpp
  76. 2 1
      lib/spells/ISpellMechanics.h
  77. 125 58
      server/CGameHandler.cpp
  78. 7 0
      server/CGameHandler.h
  79. 115 83
      server/CVCMIServer.cpp
  80. 3 1
      server/CVCMIServer.h
  81. 11 2
      server/NetPacksServer.cpp
  82. 3 2
      test/CMapEditManagerTest.cpp

+ 5 - 7
.travis.yml

@@ -36,9 +36,6 @@ matrix:
     - os: linux
     - os: linux
       compiler: clang
       compiler: clang
       env: VCMI_PLATFORM='linux' REAL_CC=clang-3.6 REAL_CXX=clang++-3.6 PACKAGE=clang-3.6 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
       env: VCMI_PLATFORM='linux' REAL_CC=clang-3.6 REAL_CXX=clang++-3.6 PACKAGE=clang-3.6 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
-    - os: linux
-      compiler: clang
-      env: VCMI_PLATFORM='linux' REAL_CC=clang-3.5 REAL_CXX=clang++-3.5 PACKAGE=clang-3.5 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
     - os: linux
     - os: linux
       compiler: clang
       compiler: clang
       env: VCMI_PLATFORM='linux' REAL_CC=clang-3.4 REAL_CXX=clang++-3.4 PACKAGE=clang-3.4 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
       env: VCMI_PLATFORM='linux' REAL_CC=clang-3.4 REAL_CXX=clang++-3.4 PACKAGE=clang-3.4 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
@@ -57,14 +54,15 @@ addons:
       name: vcmi/vcmi
       name: vcmi/vcmi
       description: Build submitted via Travis CI
       description: Build submitted via Travis CI
     notification_email: [email protected]
     notification_email: [email protected]
-    build_command_prepend: "cov-configure --compiler clang-3.6 --comptype clangcc && cov-configure --comptype clangcxx --compiler clang++-3.6 && cmake -G Ninja .. -DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=1 -DENABLE_TEST=0"
-    build_command: ninja -j 2
+    build_command_prepend: "cov-configure --compiler clang-3.6 --comptype clangcc && cov-configure --comptype clangcxx --compiler clang++-3.6 && cmake -G Ninja .. -DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=0 -DENABLE_TEST=0"
+    build_command: ninja -j 3
     branch_pattern: coverity_scan
     branch_pattern: coverity_scan
 
 
 notifications:
 notifications:
   email:
   email:
     recipients:
     recipients:
-      - [email protected]
-      - [email protected]
+      - [email protected]
     on_success: change
     on_success: change
     on_failure: always
     on_failure: always
+  slack:
+    secure: "KHXFe14FFKtw5mErWbj730+utqy7i/3AUobWfAMAGvWI5sJYlhbBU+KvvCoD2SlRQg3mQqgwVw8NBJF1Mffs7WcRmrFFFmuMqZxFLAfKBd3T0CxWpAGfnfNgDmlfV4OfEgQWk1pakEPOymhxbbmLUuCjykZDuTcioxAk0UAHDwY="

+ 3 - 4
AI/BattleAI/BattleAI.cpp

@@ -186,16 +186,15 @@ void CBattleAI::attemptCastingSpell()
 	if(!hero)
 	if(!hero)
 		return;
 		return;
 
 
-	if(!cb->battleCanCastSpell())
+	if(cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING) != ESpellCastProblem::OK)
 		return;
 		return;
 
 
 	LOGL("Casting spells sounds like fun. Let's see...");
 	LOGL("Casting spells sounds like fun. Let's see...");
 	//Get all spells we can cast
 	//Get all spells we can cast
 	std::vector<const CSpell*> possibleSpells;
 	std::vector<const CSpell*> possibleSpells;
-	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool
+	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this, hero] (const CSpell *s) -> bool
 	{
 	{
-		auto problem = getCbc()->battleCanCastThisSpell(s);
-		return problem == ESpellCastProblem::OK;
+		return s->canBeCast(getCbc().get(), ECastingMode::HERO_CASTING, hero) == ESpellCastProblem::OK;
 	});
 	});
 	LOGFL("I can cast %d spells.", possibleSpells.size());
 	LOGFL("I can cast %d spells.", possibleSpells.size());
 
 

+ 5 - 0
ChangeLog

@@ -1,3 +1,8 @@
+0.99 -> 1.00
+
+GENERAL:
+* Spectator mode was implemented through command-line options
+
 0.98 -> 0.99
 0.98 -> 0.99
 
 
 GENERAL:
 GENERAL:

+ 4 - 0
Global.h

@@ -143,6 +143,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #include <unordered_map>
 #include <unordered_map>
 #include <utility>
 #include <utility>
 #include <vector>
 #include <vector>
+#include <atomic>
 
 
 //The only available version is 3, as of Boost 1.50
 //The only available version is 3, as of Boost 1.50
 #include <boost/version.hpp>
 #include <boost/version.hpp>
@@ -184,6 +185,9 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #include <boost/variant.hpp>
 #include <boost/variant.hpp>
 #include <boost/math/special_functions/round.hpp>
 #include <boost/math/special_functions/round.hpp>
 #include <boost/multi_array.hpp>
 #include <boost/multi_array.hpp>
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_io.hpp>
+#include <boost/uuid/uuid_generators.hpp>
 
 
 #ifndef M_PI
 #ifndef M_PI
 #  define M_PI 3.14159265358979323846
 #  define M_PI 3.14159265358979323846

+ 104 - 53
client/CMT.cpp

@@ -91,7 +91,6 @@ SDL_Surface *screen = nullptr, //main screen surface
 std::queue<SDL_Event> events;
 std::queue<SDL_Event> events;
 boost::mutex eventsM;
 boost::mutex eventsM;
 
 
-bool gNoGUI = false;
 CondSh<bool> serverAlive(false);
 CondSh<bool> serverAlive(false);
 static po::variables_map vm;
 static po::variables_map vm;
 
 
@@ -114,6 +113,29 @@ 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)
 void startGameFromFile(const bfs::path &fname)
 {
 {
 	StartInfo si;
 	StartInfo si;
@@ -150,7 +172,7 @@ void init()
 	logGlobal->infoStream()<<"Initializing VCMI_Lib: "<<tmh.getDiff();
 	logGlobal->infoStream()<<"Initializing VCMI_Lib: "<<tmh.getDiff();
 
 
 
 
-	if(!gNoGUI)
+	if(!settings["session"]["headless"].Bool())
 	{
 	{
 		pomtime.getDiff();
 		pomtime.getDiff();
 		CCS->curh = new CCursorHandler;
 		CCS->curh = new CCursorHandler;
@@ -238,10 +260,19 @@ int main(int argc, char** argv)
 	opts.add_options()
 	opts.add_options()
 		("help,h", "display help and exit")
 		("help,h", "display help and exit")
 		("version,v", "display version information and exit")
 		("version,v", "display version information and exit")
+		("disable-shm", "force disable shared memory usage")
+		("enable-shm-uuid", "use UUID for shared memory identifier")
 		("battle,b", po::value<std::string>(), "runs game in duel mode (battle-only")
 		("battle,b", po::value<std::string>(), "runs game in duel mode (battle-only")
 		("start", po::value<bfs::path>(), "starts game from saved StartInfo file")
 		("start", po::value<bfs::path>(), "starts game from saved StartInfo file")
+		("testmap", po::value<std::string>(), "")
+		("spectate,s", "enable spectator interface for AI-only games")
+		("spectate-ignore-hero", "wont follow heroes on adventure map")
+		("spectate-hero-speed", po::value<int>(), "hero movement speed on adventure map")
+		("spectate-battle-speed", po::value<int>(), "battle animation speed for spectator")
+		("spectate-skip-battle", "skip battles in spectator view")
+		("spectate-skip-battle-result", "skip battle result window")
 		("onlyAI", "runs without human player, all players will be default AI")
 		("onlyAI", "runs without human player, all players will be default AI")
-		("noGUI", "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")
 		("autoSkip", "automatically skip turns in GUI")
 		("autoSkip", "automatically skip turns in GUI")
@@ -254,9 +285,9 @@ int main(int argc, char** argv)
         ("loadplayer", po::value<int>(),"specifies which player we are in multiplayer loaded games (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")
         ("loadserverip",po::value<std::string>(),"IP for loaded game server")
 		("loadserverport",po::value<std::string>(),"port for loaded game server")
 		("loadserverport",po::value<std::string>(),"port for loaded game server")
-		("testingport",po::value<std::string>(),"port for testing, override specified in config file")
-		("testingfileprefix",po::value<std::string>(),"prefix for auto save files")
-		("testingsavefrequency",po::value<int>(),"how often auto save should be created");
+		("serverport", po::value<si64>(), "override port specified in config file")
+		("saveprefix", po::value<std::string>(), "prefix for auto save files")
+		("savefrequency", po::value<si64>(), "limit auto save creation to each N days");
 
 
 	if(argc > 1)
 	if(argc > 1)
 	{
 	{
@@ -281,11 +312,6 @@ int main(int argc, char** argv)
 		prog_version();
 		prog_version();
 		return 0;
 		return 0;
 	}
 	}
-	if(vm.count("noGUI"))
-	{
-		gNoGUI = true;
-		vm.insert(std::pair<std::string, po::variable_value>("onlyAI", po::variable_value()));
-	}
 	if(vm.count("donotstartserver"))
 	if(vm.count("donotstartserver"))
 	{
 	{
 		CServerHandler::DO_NOT_START_SERVER = true;
 		CServerHandler::DO_NOT_START_SERVER = true;
@@ -314,16 +340,21 @@ int main(int argc, char** argv)
 	// Init filesystem and settings
 	// Init filesystem and settings
 	preinitDLL(::console);
 	preinitDLL(::console);
 	settings.init();
 	settings.init();
-
-	// Init special testing settings
-	Settings testingSettings = settings.write["testing"];
-	if(vm.count("testingport") && vm.count("testingfileprefix"))
+	Settings session = settings.write["session"];
+	session["onlyai"].Bool() = vm.count("onlyAI");
+	if(vm.count("headless"))
 	{
 	{
-		testingSettings["enabled"].Bool() = true;
-		testingSettings["port"].String() = vm["testingport"].as<std::string>();
-		testingSettings["prefix"].String() = vm["testingfileprefix"].as<std::string>();
-		testingSettings["savefrequency"].Float() = vm.count("testingsavefrequency") ? vm["testingsavefrequency"].as<int>() : 1;
+		session["headless"].Bool() = true;
+		session["onlyai"].Bool() = true;
 	}
 	}
+	// Shared memory options
+	session["disable-shm"].Bool() = vm.count("disable-shm");
+	session["enable-shm-uuid"].Bool() = vm.count("enable-shm-uuid");
+
+	// Init special testing settings
+	session["serverport"].Integer() = vm.count("serverport") ? vm["serverport"].as<si64>() : 0;
+	session["saveprefix"].String() = vm.count("saveprefix") ? vm["saveprefix"].as<std::string>() : "";
+	session["savefrequency"].Integer() = vm.count("savefrequency") ? vm["savefrequency"].as<si64>() : 1;
 
 
 	// Initialize logging based on settings
 	// Initialize logging based on settings
 	logConfig.configure();
 	logConfig.configure();
@@ -368,7 +399,7 @@ int main(int argc, char** argv)
 		exit(EXIT_FAILURE);
 		exit(EXIT_FAILURE);
 	}
 	}
 
 
-	if(!gNoGUI)
+	if(!settings["session"]["headless"].Bool())
 	{
 	{
 		if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE))
 		if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE))
 		{
 		{
@@ -431,7 +462,7 @@ int main(int argc, char** argv)
 #ifdef DISABLE_VIDEO
 #ifdef DISABLE_VIDEO
 	CCS->videoh = new CEmptyVideoPlayer;
 	CCS->videoh = new CEmptyVideoPlayer;
 #else
 #else
-	if (!gNoGUI && !vm.count("disable-video"))
+	if (!settings["session"]["headless"].Bool() && !vm.count("disable-video"))
 		CCS->videoh = new CVideoPlayer;
 		CCS->videoh = new CVideoPlayer;
 	else
 	else
 		CCS->videoh = new CEmptyVideoPlayer;
 		CCS->videoh = new CEmptyVideoPlayer;
@@ -460,7 +491,7 @@ int main(int argc, char** argv)
 	init();
 	init();
 #endif
 #endif
 
 
-	if(!gNoGUI )
+	if(!settings["session"]["headless"].Bool())
 	{
 	{
 		if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
 		if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
 			playIntro();
 			playIntro();
@@ -484,7 +515,6 @@ int main(int argc, char** argv)
 
 
 	if(!vm.count("battle"))
 	if(!vm.count("battle"))
 	{
 	{
-		Settings session = settings.write["session"];
 		session["autoSkip"].Bool()  = vm.count("autoSkip");
 		session["autoSkip"].Bool()  = vm.count("autoSkip");
 		session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
 		session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
 		session["aiSolo"].Bool() = false;
 		session["aiSolo"].Bool() = false;
@@ -492,17 +522,39 @@ int main(int argc, char** argv)
 		bfs::path fileToStartFrom; //none by default
 		bfs::path fileToStartFrom; //none by default
 		if(vm.count("start"))
 		if(vm.count("start"))
 			fileToStartFrom = vm["start"].as<bfs::path>();
 			fileToStartFrom = vm["start"].as<bfs::path>();
+		if(vm.count("testmap"))
+		{
+			session["testmap"].String() = vm["testmap"].as<std::string>();
+		}
 
 
-		if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom))
-			startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file
+		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())
+		{
+			startTestMap(session["testmap"].String());
+		}
 		else
 		else
 		{
 		{
-			if(!fileToStartFrom.empty())
+			if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom))
+				startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file
+			else
 			{
 			{
-				logGlobal->warnStream() << "Warning: cannot find given file to start from (" << fileToStartFrom
-					<< "). Falling back to main menu.";
+				if(!fileToStartFrom.empty())
+				{
+					logGlobal->warnStream() << "Warning: cannot find given file to start from (" << fileToStartFrom
+						<< "). Falling back to main menu.";
+				}
+				GH.curInt = CGPreGame::create(); //will set CGP pointer to itself
 			}
 			}
-			GH.curInt = CGPreGame::create(); //will set CGP pointer to itself
 		}
 		}
 	}
 	}
 	else
 	else
@@ -515,7 +567,7 @@ int main(int argc, char** argv)
 		startGame(si);
 		startGame(si);
 	}
 	}
 
 
-	if(!gNoGUI)
+	if(!settings["session"]["headless"].Bool())
 	{
 	{
 		mainLoop();
 		mainLoop();
 	}
 	}
@@ -558,6 +610,20 @@ void printInfoAboutIntObject(const CIntObject *obj, int level)
 		printInfoAboutIntObject(child, level+1);
 		printInfoAboutIntObject(child, level+1);
 }
 }
 
 
+void removeGUI()
+{
+	// CClient::endGame
+	GH.curInt = nullptr;
+	if(GH.topInt())
+		GH.topInt()->deactivate();
+	GH.listInt.clear();
+	GH.objsToBlit.clear();
+	GH.statusbar = nullptr;
+	logGlobal->infoStream() << "Removed GUI.";
+
+	LOCPLINT = nullptr;
+};
+
 void processCommand(const std::string &message)
 void processCommand(const std::string &message)
 {
 {
 	std::istringstream readed;
 	std::istringstream readed;
@@ -678,10 +744,6 @@ void processCommand(const std::string &message)
 		*ptr = 666;
 		*ptr = 666;
 		//disaster!
 		//disaster!
 	}
 	}
-	else if(cn == "onlyai")
-	{
-		vm.insert(std::pair<std::string, po::variable_value>("onlyAI", po::variable_value()));
-	}
 	else if(cn == "mp" && adventureInt)
 	else if(cn == "mp" && adventureInt)
 	{
 	{
 		if(const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(adventureInt->selection))
 		if(const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(adventureInt->selection))
@@ -817,20 +879,6 @@ void processCommand(const std::string &message)
 		}
 		}
 	}
 	}
 
 
-	auto removeGUI = [&]()
-	{
-		// CClient::endGame
-		GH.curInt = nullptr;
-		if(GH.topInt())
-			GH.topInt()->deactivate();
-		GH.listInt.clear();
-		GH.objsToBlit.clear();
-		GH.statusbar = nullptr;
-		logNetwork->infoStream() << "Removed GUI.";
-
-		LOCPLINT = nullptr;
-
-	};
 	auto giveTurn = [&](PlayerColor player)
 	auto giveTurn = [&](PlayerColor player)
 	{
 	{
 		YourTurn yt;
 		YourTurn yt;
@@ -1273,10 +1321,13 @@ static void mainLoop()
 
 
 void startGame(StartInfo * options, CConnection *serv/* = nullptr*/)
 void startGame(StartInfo * options, CConnection *serv/* = nullptr*/)
 {
 {
-	serverAlive.waitWhileTrue();
-	serverAlive.setn(true);
+	if(!CServerHandler::DO_NOT_START_SERVER)
+	{
+		serverAlive.waitWhileTrue();
+		serverAlive.setn(true);
+	}
 
 
-	if(vm.count("onlyAI"))
+	if(settings["session"]["onlyai"].Bool())
 	{
 	{
 		auto ais = vm.count("ai") ? vm["ai"].as<std::vector<std::string>>() : std::vector<std::string>();
 		auto ais = vm.count("ai") ? vm["ai"].as<std::vector<std::string>>() : std::vector<std::string>();
 
 
@@ -1306,7 +1357,7 @@ void startGame(StartInfo * options, CConnection *serv/* = nullptr*/)
         if(!vm.count("loadplayer"))
         if(!vm.count("loadplayer"))
             client->loadGame(fname);
             client->loadGame(fname);
         else
         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<std::string>() : "3030");
+			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;
 		break;
 	}
 	}
 
 
@@ -1328,7 +1379,7 @@ void handleQuit(bool ask/* = true*/)
 		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));
-		if(!gNoGUI)
+		if(!settings["session"]["headless"].Bool())
 		{
 		{
 			cleanupRenderer();
 			cleanupRenderer();
 			SDL_Quit();
 			SDL_Quit();

+ 1 - 3
client/CMT.h

@@ -11,9 +11,7 @@ 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 bool gNoGUI; //if true there is no client window and game is silently played between AIs
-
 extern CondSh<bool> serverAlive; //used to prevent game start from executing if server is already running
 extern CondSh<bool> serverAlive; //used to prevent game start from executing if server is already running
 
 
+void removeGUI();
 void handleQuit(bool ask = true);
 void handleQuit(bool ask = true);

+ 2 - 0
client/CMessage.cpp

@@ -296,6 +296,8 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play
 
 
 void CMessage::drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int h, int x, int y)
 void CMessage::drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int h, int x, int y)
 {
 {
+	if(playerColor.isSpectator())
+		playerColor = PlayerColor(1);
 	std::vector<const IImage*> &box = piecesOfBox.at(playerColor.getNum());
 	std::vector<const IImage*> &box = piecesOfBox.at(playerColor.getNum());
 
 
 	// Note: this code assumes that the corner dimensions are all the same.
 	// Note: this code assumes that the corner dimensions are all the same.

+ 23 - 12
client/CPlayerInterface.cpp

@@ -171,12 +171,7 @@ void CPlayerInterface::yourTurn()
 		GH.curInt = this;
 		GH.curInt = this;
 		adventureInt->selection = nullptr;
 		adventureInt->selection = nullptr;
 
 
-		std::string prefix = "";
-		if (settings["testing"]["enabled"].Bool())
-		{
-			prefix = settings["testing"]["prefix"].String();
-		}
-
+		std::string prefix = settings["session"]["saveprefix"].String();
 		if (firstCall)
 		if (firstCall)
 		{
 		{
 			if (howManyPeople == 1)
 			if (howManyPeople == 1)
@@ -192,7 +187,7 @@ void CPlayerInterface::yourTurn()
 			}
 			}
 			firstCall = 0;
 			firstCall = 0;
 		}
 		}
-		else if (settings["testing"].isNull() || cb->getDate() % static_cast<int>(settings["testing"]["savefrequency"].Float()) == 0)
+		else if (cb->getDate() % static_cast<int>(settings["session"]["savefrequency"].Integer()) == 0)
 		{
 		{
 			LOCPLINT->cb->save("Saves/" + prefix + "Autosave_" + boost::lexical_cast<std::string>(autosaveCount++ + 1));
 			LOCPLINT->cb->save("Saves/" + prefix + "Autosave_" + boost::lexical_cast<std::string>(autosaveCount++ + 1));
 			autosaveCount %= 5;
 			autosaveCount %= 5;
@@ -250,6 +245,9 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
 	if (LOCPLINT != this)
 	if (LOCPLINT != this)
 		return;
 		return;
 
 
+	if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-ignore-hero"].Bool())
+		return;
+
 	const CGHeroInstance * hero = cb->getHero(details.id); //object representing this hero
 	const CGHeroInstance * hero = cb->getHero(details.id); //object representing this hero
 	int3 hp = details.start;
 	int3 hp = details.start;
 
 
@@ -326,7 +324,12 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
 	}
 	}
 
 
 	ui32 speed;
 	ui32 speed;
-	if (makingTurn) // our turn, our hero moves
+	if(settings["session"]["spectate"].Bool())
+	{
+		if(!settings["session"]["spectate-hero-speed"].isNull())
+			speed = settings["session"]["spectate-hero-speed"].Integer();
+	}
+	else if (makingTurn) // our turn, our hero moves
 		speed = settings["adventure"]["heroSpeed"].Float();
 		speed = settings["adventure"]["heroSpeed"].Float();
 	else
 	else
 		speed = settings["adventure"]["enemySpeed"].Float();
 		speed = settings["adventure"]["enemySpeed"].Float();
@@ -339,7 +342,6 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
 		return; // no animation
 		return; // no animation
 	}
 	}
 
 
-
 	adventureInt->centerOn(hero); //actualizing screen pos
 	adventureInt->centerOn(hero); //actualizing screen pos
 	adventureInt->minimap.redraw();
 	adventureInt->minimap.redraw();
 	adventureInt->heroList.redraw();
 	adventureInt->heroList.redraw();
@@ -476,6 +478,14 @@ int3 CPlayerInterface::repairScreenPos(int3 pos)
 		pos.y = CGI->mh->sizes.y - adventureInt->terrain.tilesh + CGI->mh->frameH;
 		pos.y = CGI->mh->sizes.y - adventureInt->terrain.tilesh + CGI->mh->frameH;
 	return pos;
 	return pos;
 }
 }
+
+void CPlayerInterface::activateForSpectator()
+{
+	adventureInt->state = CAdvMapInt::INGAME;
+	adventureInt->activate();
+	adventureInt->minimap.activate();
+}
+
 void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val)
 void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -1642,7 +1652,8 @@ void CPlayerInterface::update()
 	}
 	}
 
 
 	//in some conditions we may receive calls before selection is initialized - we must ignore them
 	//in some conditions we may receive calls before selection is initialized - we must ignore them
-	if (adventureInt && !adventureInt->selection && GH.topInt() == adventureInt)
+	if(adventureInt && GH.topInt() == adventureInt
+		&& (!adventureInt->selection && !settings["session"]["spectate"].Bool()))
 	{
 	{
 		return;
 		return;
 	}
 	}
@@ -2136,7 +2147,7 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
 
 
 		--howManyPeople;
 		--howManyPeople;
 
 
-		if (howManyPeople == 0) //all human players eliminated
+		if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated
 		{
 		{
 			if (adventureInt)
 			if (adventureInt)
 			{
 			{
@@ -2157,7 +2168,7 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
 		}
 		}
 		else
 		else
 		{
 		{
-			if (howManyPeople == 0) //all human players eliminated
+			if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated
 			{
 			{
 				requestReturningToMainMenu();
 				requestReturningToMainMenu();
 			}
 			}

+ 1 - 0
client/CPlayerInterface.h

@@ -236,6 +236,7 @@ public:
 	void updateInfo(const CGObjectInstance * specific);
 	void updateInfo(const CGObjectInstance * specific);
 	void init(std::shared_ptr<CCallback> CB) override;
 	void init(std::shared_ptr<CCallback> CB) override;
 	int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
 	int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
+	void activateForSpectator(); // TODO: spectator probably need own player interface class
 
 
 	// show dialogs
 	// show dialogs
 	void showInfoDialog(const std::string &text, CComponent * component);
 	void showInfoDialog(const std::string &text, CComponent * component);

+ 15 - 12
client/CPreGame.cpp

@@ -83,7 +83,7 @@ struct EvilHlpStruct
 
 
 	void reset()
 	void reset()
 	{
 	{
-		vstd::clear_pointer(serv);
+//		vstd::clear_pointer(serv);
 		vstd::clear_pointer(sInfo);
 		vstd::clear_pointer(sInfo);
 	}
 	}
 
 
@@ -131,10 +131,10 @@ void setPlayer(PlayerSettings &pset, ui8 player, const std::map<ui8, std::string
 		playerColor = pset.color;
 		playerColor = pset.color;
 }
 }
 
 
-void updateStartInfo(std::string filename, StartInfo & sInfo, const CMapHeader * mapHeader, const std::map<ui8, std::string> &playerNames)
+void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader, const std::map<ui8, std::string> &playerNames)
 {
 {
 	sInfo.playerInfos.clear();
 	sInfo.playerInfos.clear();
-	if(!mapHeader)
+	if(!mapHeader.get())
 	{
 	{
 		return;
 		return;
 	}
 	}
@@ -559,7 +559,7 @@ void CGPreGame::removeFromGui()
 	GH.popInt(GH.topInt()); //remove background
 	GH.popInt(GH.topInt()); //remove background
 }
 }
 
 
-CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer /*= CMenuScreen::SINGLE_PLAYER*/, const std::map<ui8, std::string> * Names /*= nullptr*/, const std::string & Address /*=""*/, const std::string & Port /*= ""*/)
+CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer /*= CMenuScreen::SINGLE_PLAYER*/, const std::map<ui8, std::string> * Names /*= nullptr*/, const std::string & Address /*=""*/, const ui16 Port)
 	: ISelectionScreenInfo(Names), serverHandlingThread(nullptr), mx(new boost::recursive_mutex),
 	: ISelectionScreenInfo(Names), serverHandlingThread(nullptr), mx(new boost::recursive_mutex),
 	  serv(nullptr), ongoingClosing(false), myNameID(255)
 	  serv(nullptr), ongoingClosing(false), myNameID(255)
 {
 {
@@ -799,7 +799,12 @@ void CSelectionScreen::changeSelection(const CMapInfo * to)
 	   SEL->sInfo.difficulty = to->scenarioOpts->difficulty;
 	   SEL->sInfo.difficulty = to->scenarioOpts->difficulty;
 	if(screenType != CMenuScreen::campaignList)
 	if(screenType != CMenuScreen::campaignList)
 	{
 	{
-		updateStartInfo(to ? to->fileURI : "", sInfo, to ? to->mapHeader.get() : nullptr);
+		std::unique_ptr<CMapHeader> emptyHeader;
+        if(to)
+			updateStartInfo(to->fileURI, sInfo, to->mapHeader);
+		else
+			updateStartInfo("", sInfo, emptyHeader);
+
 		if(screenType == CMenuScreen::newGame)
 		if(screenType == CMenuScreen::newGame)
 		{
 		{
 			if(to && to->isRandomMap)
 			if(to && to->isRandomMap)
@@ -3219,7 +3224,7 @@ void CHotSeatPlayers::enterSelectionScreen()
 void CBonusSelection::init()
 void CBonusSelection::init()
 {
 {
 	highlightedRegion = nullptr;
 	highlightedRegion = nullptr;
-	ourHeader = nullptr;
+	ourHeader.reset();
 	diffLb = nullptr;
 	diffLb = nullptr;
 	diffRb = nullptr;
 	diffRb = nullptr;
 	bonuses = nullptr;
 	bonuses = nullptr;
@@ -3342,7 +3347,6 @@ CBonusSelection::CBonusSelection(const std::string & campaignFName)
 CBonusSelection::~CBonusSelection()
 CBonusSelection::~CBonusSelection()
 {
 {
 	SDL_FreeSurface(background);
 	SDL_FreeSurface(background);
-	delete ourHeader;
 	sFlags->unload();
 	sFlags->unload();
 }
 }
 
 
@@ -3423,10 +3427,9 @@ void CBonusSelection::selectMap(int mapNr, bool initialSelect)
 		scenarioName += ':' + boost::lexical_cast<std::string>(selectedMap);
 		scenarioName += ':' + boost::lexical_cast<std::string>(selectedMap);
 
 
 		//get header
 		//get header
-		delete ourHeader;
 		std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
 		std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
 		auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
 		auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
-		ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName).release();
+		ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName);
 
 
 		std::map<ui8, std::string> names;
 		std::map<ui8, std::string> names;
 		names[1] = settings["general"]["playerName"].String();
 		names[1] = settings["general"]["playerName"].String();
@@ -3922,7 +3925,7 @@ ISelectionScreenInfo::~ISelectionScreenInfo()
 	SEL = nullptr;
 	SEL = nullptr;
 }
 }
 
 
-void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const CMapHeader * mapHeader)
+void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader)
 {
 {
 	::updateStartInfo(filename, sInfo, mapHeader, playerNames);
 	::updateStartInfo(filename, sInfo, mapHeader, playerNames);
 }
 }
@@ -4320,7 +4323,7 @@ CSimpleJoinScreen::CSimpleJoinScreen(CMenuScreen::EMultiMode mode)
 	cancel = new CButton(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE);
 	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));
 	bar = new CGStatusBar(new CPicture(Rect(7, 186, 218, 18), 0));
 
 
-	port->setText(boost::lexical_cast<std::string>(settings["server"]["port"].Float()), true);
+	port->setText(CServerHandler::getDefaultPortStr(), true);
 	address->setText(settings["server"]["server"].String(), true);
 	address->setText(settings["server"]["server"].String(), true);
 	address->giveFocus();
 	address->giveFocus();
 }
 }
@@ -4332,7 +4335,7 @@ void CSimpleJoinScreen::enterSelectionScreen(CMenuScreen::EMultiMode mode)
 
 
 	GH.popIntTotally(this);
 	GH.popIntTotally(this);
 
 
-	GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, textPort));
+	GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, boost::lexical_cast<ui16>(textPort)));
 }
 }
 void CSimpleJoinScreen::onChange(const std::string & newText)
 void CSimpleJoinScreen::onChange(const std::string & newText)
 {
 {

+ 3 - 3
client/CPreGame.h

@@ -341,7 +341,7 @@ public:
 	virtual void postChatMessage(const std::string &txt){};
 	virtual void postChatMessage(const std::string &txt){};
 
 
 	void setPlayer(PlayerSettings &pset, ui8 player);
 	void setPlayer(PlayerSettings &pset, ui8 player);
-	void updateStartInfo( std::string filename, StartInfo & sInfo, const CMapHeader * mapHeader );
+	void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader);
 
 
 	ui8 getIdOfFirstUnallocatedPlayer(); //returns 0 if none
 	ui8 getIdOfFirstUnallocatedPlayer(); //returns 0 if none
 	bool isGuest() const;
 	bool isGuest() const;
@@ -371,7 +371,7 @@ public:
 	bool ongoingClosing;
 	bool ongoingClosing;
 	ui8 myNameID; //used when networking - otherwise all player are "mine"
 	ui8 myNameID; //used when networking - otherwise all player are "mine"
 
 
-	CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer = CMenuScreen::SINGLE_PLAYER, const std::map<ui8, std::string> * Names = nullptr, const std::string & Address = "", const std::string & Port = "");
+	CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer = CMenuScreen::SINGLE_PLAYER, const std::map<ui8, std::string> * Names = nullptr, const std::string & Address = "", const ui16 Port = 0);
 	~CSelectionScreen();
 	~CSelectionScreen();
 	void toggleTab(CIntObject *tab);
 	void toggleTab(CIntObject *tab);
 	void changeSelection(const CMapInfo *to);
 	void changeSelection(const CMapInfo *to);
@@ -539,7 +539,7 @@ private:
 	int selectedMap;
 	int selectedMap;
 	boost::optional<int> selectedBonus;
 	boost::optional<int> selectedBonus;
 	StartInfo startInfo;
 	StartInfo startInfo;
-	CMapHeader * ourHeader;
+	std::unique_ptr<CMapHeader> ourHeader;
 };
 };
 
 
 /// Campaign selection screen
 /// Campaign selection screen

+ 140 - 71
client/Client.cpp

@@ -41,9 +41,7 @@
 #include "CMT.h"
 #include "CMT.h"
 
 
 extern std::string NAME;
 extern std::string NAME;
-#ifndef VCMI_ANDROID
-namespace intpr = boost::interprocess;
-#else
+#ifdef VCMI_ANDROID
 #include "lib/CAndroidVMHelper.h"
 #include "lib/CAndroidVMHelper.h"
 #endif
 #endif
 
 
@@ -251,24 +249,16 @@ void CClient::endGame(bool closeConnection /*= true*/)
 }
 }
 
 
 #if 1
 #if 1
-void CClient::loadGame(const std::string & fname, const bool server, const std::vector<int>& humanplayerindices, const int loadNumPlayers, int player_, const std::string & ipaddr, const std::string & port)
+void CClient::loadGame(const std::string & fname, const bool server, const std::vector<int>& humanplayerindices, const int loadNumPlayers, int player_, const std::string & ipaddr, const ui16 port)
 {
 {
 	PlayerColor player(player_); //intentional shadowing
 	PlayerColor player(player_); //intentional shadowing
 	logNetwork->infoStream() << "Loading procedure started!";
 	logNetwork->infoStream() << "Loading procedure started!";
 
 
-	std::string realPort;
-	if(settings["testing"]["enabled"].Bool())
-		realPort = settings["testing"]["port"].String();
-	else if(port.size())
-		realPort = port;
-	else
-		realPort = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
-
 	CServerHandler sh;
 	CServerHandler sh;
 	if(server)
 	if(server)
 		sh.startServer();
 		sh.startServer();
 	else
 	else
-		serv = sh.justConnectToServer(ipaddr, realPort);
+		serv = sh.justConnectToServer(ipaddr, port);
 
 
 	CStopWatch tmh;
 	CStopWatch tmh;
 	std::unique_ptr<CLoadFile> loader;
 	std::unique_ptr<CLoadFile> loader;
@@ -391,7 +381,7 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 	else
 	else
 	{
 	{
 		serv = con;
 		serv = con;
-		networkMode = (con->connectionID == 1) ? HOST : GUEST;
+		networkMode = con->isHost() ? HOST : GUEST;
 	}
 	}
 
 
 	CConnection &c = *serv;
 	CConnection &c = *serv;
@@ -416,10 +406,9 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 
 
 	// Initialize game state
 	// Initialize game state
 	gs = new CGameState();
 	gs = new CGameState();
-	logNetwork->infoStream() <<"\tCreating gamestate: "<<tmh.getDiff();
+	logNetwork->info("\tCreating gamestate: %i",tmh.getDiff());
 
 
-	gs->scenarioOps = si;
-	gs->init(si);
+	gs->init(si, settings["general"]["saveRandomMaps"].Bool());
 	logNetwork->infoStream() <<"Initializing GameState (together): "<<tmh.getDiff();
 	logNetwork->infoStream() <<"Initializing GameState (together): "<<tmh.getDiff();
 
 
 	// Now after possible random map gen, we know exact player count.
 	// Now after possible random map gen, we know exact player count.
@@ -441,10 +430,13 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 	// Init map handler
 	// Init map handler
 	if(gs->map)
 	if(gs->map)
 	{
 	{
-		const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
-		CGI->mh->map = gs->map;
-		logNetwork->infoStream() << "Creating mapHandler: " << tmh.getDiff();
-		CGI->mh->init();
+		if(!settings["session"]["headless"].Bool())
+		{
+			const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
+			CGI->mh->map = gs->map;
+			logNetwork->infoStream() << "Creating mapHandler: " << tmh.getDiff();
+			CGI->mh->init();
+		}
 		pathInfo = make_unique<CPathsInfo>(getMapSize());
 		pathInfo = make_unique<CPathsInfo>(getMapSize());
 		logNetwork->infoStream() << "Initializing mapHandler (together): " << tmh.getDiff();
 		logNetwork->infoStream() << "Initializing mapHandler (together): " << tmh.getDiff();
 	}
 	}
@@ -481,7 +473,7 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 
 
 	if(si->mode == StartInfo::DUEL)
 	if(si->mode == StartInfo::DUEL)
 	{
 	{
-		if(!gNoGUI)
+		if(!settings["session"]["headless"].Bool())
 		{
 		{
 			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
 			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
 			auto p = std::make_shared<CPlayerInterface>(PlayerColor::NEUTRAL);
 			auto p = std::make_shared<CPlayerInterface>(PlayerColor::NEUTRAL);
@@ -493,6 +485,10 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 	}
 	}
 	else
 	else
 	{
 	{
+		if(settings["session"]["spectate"].Bool())
+		{
+			installNewPlayerInterface(std::make_shared<CPlayerInterface>(PlayerColor::SPECTATOR), PlayerColor::SPECTATOR, true);
+		}
 		loadNeutralBattleAI();
 		loadNeutralBattleAI();
 	}
 	}
 
 
@@ -644,10 +640,33 @@ void CClient::serialize(BinaryDeserializer & h, const int version, const std::se
 			nInt->human = isHuman;
 			nInt->human = isHuman;
 			nInt->playerID = pid;
 			nInt->playerID = pid;
 
 
-			if(playerIDs.count(pid))
-				installNewPlayerInterface(nInt, pid);
-
 			nInt->loadGame(h, version);
 			nInt->loadGame(h, version);
+			if(settings["session"]["onlyai"].Bool() && isHuman)
+			{
+				removeGUI();
+				nInt.reset();
+				dllname = aiNameForPlayer(false);
+				nInt = CDynLibHandler::getNewAI(dllname);
+				nInt->dllName = dllname;
+				nInt->human = false;
+				nInt->playerID = pid;
+				installNewPlayerInterface(nInt, pid);
+				GH.totalRedraw();
+			}
+			else
+			{
+				if(playerIDs.count(pid))
+					installNewPlayerInterface(nInt, pid);
+			}
+		}
+		if(settings["session"]["spectate"].Bool())
+		{
+			removeGUI();
+			auto p = std::make_shared<CPlayerInterface>(PlayerColor::SPECTATOR);
+			installNewPlayerInterface(p, PlayerColor::SPECTATOR, true);
+			GH.curInt = p.get();
+			LOCPLINT->activateForSpectator();
+			GH.totalRedraw();
 		}
 		}
 
 
 		if(playerIDs.count(PlayerColor::NEUTRAL))
 		if(playerIDs.count(PlayerColor::NEUTRAL))
@@ -693,13 +712,22 @@ void CClient::stopConnection()
 {
 {
 	terminate = true;
 	terminate = true;
 
 
-	if (serv) //request closing connection
+	if(serv)
 	{
 	{
-		logNetwork->infoStream() << "Connection has been requested to be closed.";
 		boost::unique_lock<boost::mutex>(*serv->wmx);
 		boost::unique_lock<boost::mutex>(*serv->wmx);
-		CloseServer close_server;
-		sendRequest(&close_server, PlayerColor::NEUTRAL);
-		logNetwork->infoStream() << "Sent closing signal to the server";
+		if(serv->isHost()) //request closing connection
+		{
+			logNetwork->infoStream() << "Connection has been requested to be closed.";
+			CloseServer close_server;
+			sendRequest(&close_server, PlayerColor::NEUTRAL);
+			logNetwork->infoStream() << "Sent closing signal to the server";
+		}
+		else
+		{
+			LeaveGame leave_Game;
+			sendRequest(&leave_Game, PlayerColor::NEUTRAL);
+			logNetwork->infoStream() << "Sent leaving signal to the server";
+		}
 	}
 	}
 
 
 	if(connectionHandler)//end connection handler
 	if(connectionHandler)//end connection handler
@@ -708,16 +736,13 @@ void CClient::stopConnection()
 			connectionHandler->join();
 			connectionHandler->join();
 
 
 		logNetwork->infoStream() << "Connection handler thread joined";
 		logNetwork->infoStream() << "Connection handler thread joined";
-
-		delete connectionHandler;
-		connectionHandler = nullptr;
+		vstd::clear_pointer(connectionHandler);
 	}
 	}
 
 
 	if (serv) //and delete connection
 	if (serv) //and delete connection
 	{
 	{
 		serv->close();
 		serv->close();
-		delete serv;
-		serv = nullptr;
+		vstd::clear_pointer(serv);
 		logNetwork->warnStream() << "Our socket has been closed.";
 		logNetwork->warnStream() << "Our socket has been closed.";
 	}
 	}
 }
 }
@@ -750,14 +775,29 @@ void CClient::battleStarted(const BattleInfo * info)
 			def = std::dynamic_pointer_cast<CPlayerInterface>( playerint[rightSide.color] );
 			def = std::dynamic_pointer_cast<CPlayerInterface>( playerint[rightSide.color] );
 	}
 	}
 
 
-	if(!gNoGUI && (!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL))
+	if(!settings["session"]["headless"].Bool())
 	{
 	{
-		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-		auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
-			Rect((screen->w - 800)/2,
-			     (screen->h - 600)/2, 800, 600), att, def);
+		if(!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL)
+		{
+			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
+			auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
+				Rect((screen->w - 800)/2,
+					 (screen->h - 600)/2, 800, 600), att, def);
 
 
-		GH.pushInt(bi);
+			GH.pushInt(bi);
+		}
+		else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
+		{
+			//TODO: This certainly need improvement
+			auto spectratorInt = std::dynamic_pointer_cast<CPlayerInterface>(playerint[PlayerColor::SPECTATOR]);
+			spectratorInt->cb->setBattle(info);
+			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
+			auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
+				Rect((screen->w - 800)/2,
+					 (screen->h - 600)/2, 800, 600), att, def, spectratorInt);
+
+			GH.pushInt(bi);
+		}
 	}
 	}
 
 
 	auto callBattleStart = [&](PlayerColor color, ui8 side){
 	auto callBattleStart = [&](PlayerColor color, ui8 side){
@@ -768,6 +808,8 @@ void CClient::battleStarted(const BattleInfo * info)
 	callBattleStart(leftSide.color, 0);
 	callBattleStart(leftSide.color, 0);
 	callBattleStart(rightSide.color, 1);
 	callBattleStart(rightSide.color, 1);
 	callBattleStart(PlayerColor::UNFLAGGABLE, 1);
 	callBattleStart(PlayerColor::UNFLAGGABLE, 1);
+	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
+		callBattleStart(PlayerColor::SPECTATOR, 1);
 
 
 	if(info->tacticDistance && vstd::contains(battleints,info->sides[info->tacticsSide].color))
 	if(info->tacticDistance && vstd::contains(battleints,info->sides[info->tacticsSide].color))
 	{
 	{
@@ -780,6 +822,9 @@ void CClient::battleFinished()
 	for(auto & side : gs->curB->sides)
 	for(auto & side : gs->curB->sides)
 		if(battleCallbacks.count(side.color))
 		if(battleCallbacks.count(side.color))
 			battleCallbacks[side.color]->setBattle(nullptr);
 			battleCallbacks[side.color]->setBattle(nullptr);
+
+	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
+		battleCallbacks[PlayerColor::SPECTATOR]->setBattle(nullptr);
 }
 }
 
 
 void CClient::loadNeutralBattleAI()
 void CClient::loadNeutralBattleAI()
@@ -877,7 +922,7 @@ void CClient::campaignMapFinished( std::shared_ptr<CCampaignState> camp )
 	}
 	}
 }
 }
 
 
-void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color)
+void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color, bool battlecb)
 {
 {
 	boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
 	boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
 	PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);
 	PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);
@@ -893,7 +938,7 @@ void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInte
 	battleCallbacks[colorUsed] = cb;
 	battleCallbacks[colorUsed] = cb;
 	gameInterface->init(cb);
 	gameInterface->init(cb);
 
 
-	installNewBattleInterface(gameInterface, color, false);
+	installNewBattleInterface(gameInterface, color, battlecb);
 }
 }
 
 
 void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback /*= true*/)
 void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback /*= true*/)
@@ -924,6 +969,11 @@ std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI)
 			return ps.name;
 			return ps.name;
 	}
 	}
 
 
+	return aiNameForPlayer(battleAI);
+}
+
+std::string CClient::aiNameForPlayer(bool battleAI)
+{
 	const int sensibleAILimit = settings["session"]["oneGoodAI"].Bool() ? 1 : PlayerColor::PLAYER_LIMIT_I;
 	const int sensibleAILimit = settings["session"]["oneGoodAI"].Bool() ? 1 : PlayerColor::PLAYER_LIMIT_I;
 	std::string goodAI = battleAI ? settings["server"]["neutralAI"].String() : settings["server"]["playerAI"].String();
 	std::string goodAI = battleAI ? settings["server"]["neutralAI"].String() : settings["server"]["playerAI"].String();
 	std::string badAI = battleAI ? "StupidAI" : "EmptyAI";
 	std::string badAI = battleAI ? "StupidAI" : "EmptyAI";
@@ -966,11 +1016,8 @@ void CServerHandler::waitForServer()
 	th.update();
 	th.update();
 
 
 #ifndef VCMI_ANDROID
 #ifndef VCMI_ANDROID
-	intpr::scoped_lock<intpr::interprocess_mutex> slock(shared->sr->mutex);
-	while(!shared->sr->ready)
-	{
-		shared->sr->cond.wait(slock);
-	}
+	if(shared)
+		shared->sr->waitTillReady();
 #else
 #else
 	logNetwork->infoStream() << "waiting for server";
 	logNetwork->infoStream() << "waiting for server";
 	while (!androidTestServerReadyFlag.load())
 	while (!androidTestServerReadyFlag.load())
@@ -987,16 +1034,15 @@ void CServerHandler::waitForServer()
 
 
 CConnection * CServerHandler::connectToServer()
 CConnection * CServerHandler::connectToServer()
 {
 {
-#ifndef VCMI_ANDROID
-	if(!shared->sr->ready)
-		waitForServer();
-#else
 	waitForServer();
 	waitForServer();
-#endif
 
 
 	th.update(); //put breakpoint here to attach to server before it does something stupid
 	th.update(); //put breakpoint here to attach to server before it does something stupid
 
 
-	CConnection *ret = justConnectToServer(settings["server"]["server"].String(), port);
+#ifndef VCMI_ANDROID
+	CConnection *ret = justConnectToServer(settings["server"]["server"].String(), shared ? shared->sr->port : 0);
+#else
+	CConnection *ret = justConnectToServer(settings["server"]["server"].String());
+#endif
 
 
 	if(verbose)
 	if(verbose)
 		logNetwork->infoStream()<<"\tConnecting to the server: "<<th.getDiff();
 		logNetwork->infoStream()<<"\tConnecting to the server: "<<th.getDiff();
@@ -1004,24 +1050,43 @@ CConnection * CServerHandler::connectToServer()
 	return ret;
 	return ret;
 }
 }
 
 
+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());
+}
+
 CServerHandler::CServerHandler(bool runServer /*= false*/)
 CServerHandler::CServerHandler(bool runServer /*= false*/)
 {
 {
 	serverThread = nullptr;
 	serverThread = nullptr;
 	shared = nullptr;
 	shared = nullptr;
-	if(settings["testing"]["enabled"].Bool())
-		port = settings["testing"]["port"].String();
-	else
-		port = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
 	verbose = true;
 	verbose = true;
+	uuid = boost::uuids::to_string(boost::uuids::random_generator()());
 
 
 #ifndef VCMI_ANDROID
 #ifndef VCMI_ANDROID
-	boost::interprocess::shared_memory_object::remove("vcmi_memory"); //if the application has previously crashed, the memory may not have been removed. to avoid problems - try to destroy it
+	if(DO_NOT_START_SERVER || settings["session"]["disable-shm"].Bool())
+		return;
+
+	std::string sharedMemoryName = "vcmi_memory";
+	if(settings["session"]["enable-shm-uuid"].Bool())
+	{
+		//used or automated testing when multiple clients start simultaneously
+		sharedMemoryName += "_" + uuid;
+	}
 	try
 	try
 	{
 	{
-		shared = new SharedMem();
+		shared = new SharedMemory(sharedMemoryName, true);
 	}
 	}
 	catch(...)
 	catch(...)
 	{
 	{
+		vstd::clear_pointer(shared);
 		logNetwork->error("Cannot open interprocess memory.");
 		logNetwork->error("Cannot open interprocess memory.");
 		handleException();
 		handleException();
 		throw;
 		throw;
@@ -1040,7 +1105,18 @@ void CServerHandler::callServer()
 #ifndef VCMI_ANDROID
 #ifndef VCMI_ANDROID
 	setThreadName("CServerHandler::callServer");
 	setThreadName("CServerHandler::callServer");
 	const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();
 	const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();
-	const std::string comm = VCMIDirs::get().serverPath().string() + " --port=" + port + " > \"" + logName + '\"';
+	std::string comm = VCMIDirs::get().serverPath().string()
+		+ " --port=" + getDefaultPortStr()
+		+ " --run-by-client"
+		+ " --uuid=" + uuid;
+	if(shared)
+	{
+		comm += " --enable-shm";
+		if(settings["session"]["enable-shm-uuid"].Bool())
+			comm += " --enable-shm-uuid";
+	}
+	comm += " > \"" + logName + '\"';
+
 	int result = std::system(comm.c_str());
 	int result = std::system(comm.c_str());
 	if (result == 0)
 	if (result == 0)
 	{
 	{
@@ -1056,16 +1132,8 @@ void CServerHandler::callServer()
 #endif
 #endif
 }
 }
 
 
-CConnection * CServerHandler::justConnectToServer(const std::string &host, const std::string &port)
+CConnection * CServerHandler::justConnectToServer(const std::string &host, const ui16 port)
 {
 {
-	std::string realPort;
-	if(settings["testing"]["enabled"].Bool())
-		realPort = settings["testing"]["port"].String();
-	else if(port.size())
-		realPort = port;
-	else
-		realPort = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
-
 	CConnection *ret = nullptr;
 	CConnection *ret = nullptr;
 	while(!ret)
 	while(!ret)
 	{
 	{
@@ -1073,8 +1141,9 @@ CConnection * CServerHandler::justConnectToServer(const std::string &host, const
 		{
 		{
 			logNetwork->infoStream() << "Establishing connection...";
 			logNetwork->infoStream() << "Establishing connection...";
 			ret = new CConnection(	host.size() ? host : settings["server"]["server"].String(),
 			ret = new CConnection(	host.size() ? host : settings["server"]["server"].String(),
-									realPort,
+									port ? port : getDefaultPort(),
 									NAME);
 									NAME);
+			ret->connectionID = 1; // TODO: Refactoring for the server so IDs set outside of CConnection
 		}
 		}
 		catch(...)
 		catch(...)
 		{
 		{

+ 9 - 6
client/Client.h

@@ -28,7 +28,7 @@ class CGameInterface;
 class CConnection;
 class CConnection;
 class CCallback;
 class CCallback;
 struct BattleAction;
 struct BattleAction;
-struct SharedMem;
+struct SharedMemory;
 class CClient;
 class CClient;
 class CScriptingModule;
 class CScriptingModule;
 struct CPathsInfo;
 struct CPathsInfo;
@@ -46,9 +46,9 @@ public:
 
 
 	CStopWatch th;
 	CStopWatch th;
 	boost::thread *serverThread; //thread that called system to run server
 	boost::thread *serverThread; //thread that called system to run server
-	SharedMem *shared; //interprocess memory (for waiting for server)
+	SharedMemory * shared;
+	std::string uuid;
 	bool verbose; //whether to print log msgs
 	bool verbose; //whether to print log msgs
-	std::string port; //port number in text form
 
 
 	//functions setting up local server
 	//functions setting up local server
 	void startServer(); //creates a thread with callServer
 	void startServer(); //creates a thread with callServer
@@ -56,7 +56,9 @@ public:
 	CConnection * connectToServer(); //connects to server
 	CConnection * connectToServer(); //connects to server
 
 
 	//////////////////////////////////////////////////////////////////////////
 	//////////////////////////////////////////////////////////////////////////
-	static CConnection * justConnectToServer(const std::string &host = "", const std::string &port = ""); //connects to given host without taking any other actions (like setting up 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);
 	CServerHandler(bool runServer = false);
 	virtual ~CServerHandler();
 	virtual ~CServerHandler();
@@ -147,14 +149,15 @@ public:
 	void newGame(CConnection *con, StartInfo *si); //con - connection to server
 	void newGame(CConnection *con, StartInfo *si); //con - connection to server
 
 
 	void loadNeutralBattleAI();
 	void loadNeutralBattleAI();
-	void installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color);
+	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(const PlayerSettings &ps, bool battleAI); //empty means no AI -> human
+	std::string aiNameForPlayer(bool battleAI);
 
 
 	void endGame(bool closeConnection = true);
 	void endGame(bool closeConnection = true);
 	void stopConnection();
 	void stopConnection();
 	void save(const std::string & fname);
 	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 std::string & port = "");
+	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 run();
 	void campaignMapFinished( std::shared_ptr<CCampaignState> camp );
 	void campaignMapFinished( std::shared_ptr<CCampaignState> camp );
 	void finishCampaign( std::shared_ptr<CCampaignState> camp );
 	void finishCampaign( std::shared_ptr<CCampaignState> camp );

+ 46 - 29
client/NetPacksClient.cpp

@@ -96,6 +96,10 @@
 #define BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(function,...) 				\
 #define BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(function,...) 				\
 	CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[0].color, function, __VA_ARGS__)	\
 	CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[0].color, function, __VA_ARGS__)	\
 	CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[1].color, function, __VA_ARGS__)	\
 	CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[1].color, function, __VA_ARGS__)	\
+	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool() && LOCPLINT->battleInt)	\
+	{																					\
+		CALL_ONLY_THAT_BATTLE_INTERFACE(PlayerColor::SPECTATOR, function, __VA_ARGS__)	\
+	}																					\
 	BATTLE_INTERFACE_CALL_RECEIVERS(function, __VA_ARGS__)
 	BATTLE_INTERFACE_CALL_RECEIVERS(function, __VA_ARGS__)
 /*
 /*
  * NetPacksClient.cpp, part of VCMI engine
  * NetPacksClient.cpp, part of VCMI engine
@@ -283,13 +287,13 @@ void GiveBonus::applyCl(CClient *cl)
 void ChangeObjPos::applyFirstCl(CClient *cl)
 void ChangeObjPos::applyFirstCl(CClient *cl)
 {
 {
 	CGObjectInstance *obj = GS(cl)->getObjInstance(objid);
 	CGObjectInstance *obj = GS(cl)->getObjInstance(objid);
-	if(flags & 1)
+	if(flags & 1 && CGI->mh)
 		CGI->mh->hideObject(obj);
 		CGI->mh->hideObject(obj);
 }
 }
 void ChangeObjPos::applyCl(CClient *cl)
 void ChangeObjPos::applyCl(CClient *cl)
 {
 {
 	CGObjectInstance *obj = GS(cl)->getObjInstance(objid);
 	CGObjectInstance *obj = GS(cl)->getObjInstance(objid);
-	if(flags & 1)
+	if(flags & 1 && CGI->mh)
 		CGI->mh->printObject(obj);
 		CGI->mh->printObject(obj);
 
 
 	cl->invalidatePaths();
 	cl->invalidatePaths();
@@ -298,6 +302,10 @@ void ChangeObjPos::applyCl(CClient *cl)
 void PlayerEndsGame::applyCl(CClient *cl)
 void PlayerEndsGame::applyCl(CClient *cl)
 {
 {
 	CALL_IN_ALL_INTERFACES(gameOver, player, victoryLossCheckResult);
 	CALL_IN_ALL_INTERFACES(gameOver, player, victoryLossCheckResult);
+
+	// In auto testing mode we always close client if red player won or lose
+	if(!settings["session"]["testmap"].isNull() && player == PlayerColor(0))
+		handleQuit(settings["session"]["spectate"].Bool()); // if spectator is active ask to close client or not
 }
 }
 
 
 void RemoveBonus::applyCl(CClient *cl)
 void RemoveBonus::applyCl(CClient *cl)
@@ -335,7 +343,8 @@ void RemoveObject::applyFirstCl(CClient *cl)
 {
 {
 	const CGObjectInstance *o = cl->getObj(id);
 	const CGObjectInstance *o = cl->getObj(id);
 
 
-	CGI->mh->hideObject(o, true);
+	if(CGI->mh)
+		CGI->mh->hideObject(o, true);
 
 
 	//notify interfaces about removal
 	//notify interfaces about removal
 	for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
 	for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
@@ -357,18 +366,20 @@ void TryMoveHero::applyFirstCl(CClient *cl)
 	//check if playerint will have the knowledge about movement - if not, directly update maphandler
 	//check if playerint will have the knowledge about movement - if not, directly update maphandler
 	for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
 	for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
 	{
 	{
-		if(i->first >= PlayerColor::PLAYER_LIMIT)
-			continue;
-		TeamState *t = GS(cl)->getPlayerTeam(i->first);
-		if((t->fogOfWarMap[start.x-1][start.y][start.z] || t->fogOfWarMap[end.x-1][end.y][end.z])
-				&& GS(cl)->getPlayer(i->first)->human)
-			humanKnows = true;
+		auto ps = GS(cl)->getPlayer(i->first);
+		if(ps && (GS(cl)->isVisible(start - int3(1, 0, 0), i->first) || GS(cl)->isVisible(end - int3(1, 0, 0), i->first)))
+		{
+			if(ps->human)
+				humanKnows = true;
+		}
 	}
 	}
 
 
+	if(!CGI->mh)
+		return;
+
 	if(result == TELEPORTATION  ||  result == EMBARK  ||  result == DISEMBARK  ||  !humanKnows)
 	if(result == TELEPORTATION  ||  result == EMBARK  ||  result == DISEMBARK  ||  !humanKnows)
 		CGI->mh->hideObject(h, result == EMBARK && humanKnows);
 		CGI->mh->hideObject(h, result == EMBARK && humanKnows);
 
 
-
 	if(result == DISEMBARK)
 	if(result == DISEMBARK)
 		CGI->mh->printObject(h->boat);
 		CGI->mh->printObject(h->boat);
 }
 }
@@ -378,13 +389,14 @@ void TryMoveHero::applyCl(CClient *cl)
 	const CGHeroInstance *h = cl->getHero(id);
 	const CGHeroInstance *h = cl->getHero(id);
 	cl->invalidatePaths();
 	cl->invalidatePaths();
 
 
-	if(result == TELEPORTATION  ||  result == EMBARK  ||  result == DISEMBARK)
+	if(CGI->mh)
 	{
 	{
-		CGI->mh->printObject(h, result == DISEMBARK);
-	}
+		if(result == TELEPORTATION  ||  result == EMBARK  ||  result == DISEMBARK)
+			CGI->mh->printObject(h, result == DISEMBARK);
 
 
-	if(result == EMBARK)
-		CGI->mh->hideObject(h->boat);
+		if(result == EMBARK)
+			CGI->mh->hideObject(h->boat);
+	}
 
 
 	PlayerColor player = h->tempOwner;
 	PlayerColor player = h->tempOwner;
 
 
@@ -395,18 +407,17 @@ void TryMoveHero::applyCl(CClient *cl)
 	//notify interfaces about move
 	//notify interfaces about move
 	for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
 	for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
 	{
 	{
-		if(i->first >= PlayerColor::PLAYER_LIMIT) continue;
-		TeamState *t = GS(cl)->getPlayerTeam(i->first);
-		if(t->fogOfWarMap[start.x-1][start.y][start.z] || t->fogOfWarMap[end.x-1][end.y][end.z])
+		if(GS(cl)->isVisible(start - int3(1, 0, 0), i->first)
+			|| GS(cl)->isVisible(end - int3(1, 0, 0), i->first))
 		{
 		{
 			i->second->heroMoved(*this);
 			i->second->heroMoved(*this);
 		}
 		}
 	}
 	}
 
 
-	if(!humanKnows) //maphandler didn't get update from playerint, do it now
-	{				//TODO: restructure nicely
+	//maphandler didn't get update from playerint, do it now
+	//TODO: restructure nicely
+	if(!humanKnows && CGI->mh)
 		CGI->mh->printObject(h);
 		CGI->mh->printObject(h);
-	}
 }
 }
 
 
 void NewStructures::applyCl(CClient *cl)
 void NewStructures::applyCl(CClient *cl)
@@ -482,22 +493,22 @@ void HeroRecruited::applyCl(CClient *cl)
 			needsPrinting = false;
 			needsPrinting = false;
 		}
 		}
 	}
 	}
-	if (needsPrinting)
-	{
+	if(needsPrinting && CGI->mh)
 		CGI->mh->printObject(h);
 		CGI->mh->printObject(h);
-	}
 }
 }
 
 
 void GiveHero::applyCl(CClient *cl)
 void GiveHero::applyCl(CClient *cl)
 {
 {
 	CGHeroInstance *h = GS(cl)->getHero(id);
 	CGHeroInstance *h = GS(cl)->getHero(id);
-	CGI->mh->printObject(h);
+	if(CGI->mh)
+		CGI->mh->printObject(h);
 	cl->playerint[h->tempOwner]->heroCreated(h);
 	cl->playerint[h->tempOwner]->heroCreated(h);
 }
 }
 
 
 void GiveHero::applyFirstCl(CClient *cl)
 void GiveHero::applyFirstCl(CClient *cl)
 {
 {
-	CGI->mh->hideObject(GS(cl)->getHero(id));
+	if(CGI->mh)
+		CGI->mh->hideObject(GS(cl)->getHero(id));
 }
 }
 
 
 void InfoWindow::applyCl(CClient *cl)
 void InfoWindow::applyCl(CClient *cl)
@@ -588,6 +599,8 @@ void BattleStart::applyFirstCl(CClient *cl)
 		info->tile, info->sides[0].hero, info->sides[1].hero);
 		info->tile, info->sides[0].hero, info->sides[1].hero);
 	CALL_ONLY_THAT_BATTLE_INTERFACE(info->sides[1].color, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
 	CALL_ONLY_THAT_BATTLE_INTERFACE(info->sides[1].color, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
 		info->tile, info->sides[0].hero, info->sides[1].hero);
 		info->tile, info->sides[0].hero, info->sides[1].hero);
+	CALL_ONLY_THAT_BATTLE_INTERFACE(PlayerColor::SPECTATOR, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
+		info->tile, info->sides[0].hero, info->sides[1].hero);
 	BATTLE_INTERFACE_CALL_RECEIVERS(battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
 	BATTLE_INTERFACE_CALL_RECEIVERS(battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
 		info->tile, info->sides[0].hero, info->sides[1].hero);
 		info->tile, info->sides[0].hero, info->sides[1].hero);
 }
 }
@@ -707,7 +720,7 @@ void BattleResultsApplied::applyCl(CClient *cl)
 {
 {
 	INTERFACE_CALL_IF_PRESENT(player1, battleResultsApplied);
 	INTERFACE_CALL_IF_PRESENT(player1, battleResultsApplied);
 	INTERFACE_CALL_IF_PRESENT(player2, battleResultsApplied);
 	INTERFACE_CALL_IF_PRESENT(player2, battleResultsApplied);
-	INTERFACE_CALL_IF_PRESENT(PlayerColor::UNFLAGGABLE, battleResultsApplied);
+	INTERFACE_CALL_IF_PRESENT(PlayerColor::SPECTATOR, battleResultsApplied);
 	if(GS(cl)->initialOpts->mode == StartInfo::DUEL)
 	if(GS(cl)->initialOpts->mode == StartInfo::DUEL)
 	{
 	{
 		handleQuit();
 		handleQuit();
@@ -808,7 +821,10 @@ void PlayerMessage::applyCl(CClient *cl)
 	logNetwork->debugStream() << "Player "<< player <<" sends a message: " << text;
 	logNetwork->debugStream() << "Player "<< player <<" sends a message: " << text;
 
 
 	std::ostringstream str;
 	std::ostringstream str;
-	str << cl->getPlayer(player)->nodeName() <<": " << text;
+	if(player.isSpectator())
+		str << "Spectator: " << text;
+	else
+		str << cl->getPlayer(player)->nodeName() <<": " << text;
 	if(LOCPLINT)
 	if(LOCPLINT)
 		LOCPLINT->cingconsole->print(str.str());
 		LOCPLINT->cingconsole->print(str.str());
 }
 }
@@ -904,7 +920,8 @@ void NewObject::applyCl(CClient *cl)
 	cl->invalidatePaths();
 	cl->invalidatePaths();
 
 
 	const CGObjectInstance *obj = cl->getObj(id);
 	const CGObjectInstance *obj = cl->getObj(id);
-	CGI->mh->printObject(obj, true);
+	if(CGI->mh)
+		CGI->mh->printObject(obj, true);
 
 
 	for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
 	for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
 	{
 	{

+ 0 - 2
client/VCMI_client.vcxproj

@@ -292,6 +292,4 @@
     </ProjectReference>
     </ProjectReference>
   </ItemGroup>
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
-  <ImportGroup Label="ExtensionTargets">
-  </ImportGroup>
 </Project>
 </Project>

+ 2 - 8
client/VCMI_client.vcxproj.filters

@@ -109,17 +109,10 @@
       <Filter>gui</Filter>
       <Filter>gui</Filter>
     </ClCompile>
     </ClCompile>
     <ClCompile Include="..\CCallback.cpp" />
     <ClCompile Include="..\CCallback.cpp" />
-  </ItemGroup>
-  <ItemGroup>
-    <ClCompile Include="CQuestLog.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
+    <ClCompile Include="SDLRWwrapper.cpp" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="VCMI_client.rc" />
     <ResourceCompile Include="VCMI_client.rc" />
-    <ClInclude Include="CQuestLog.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <None Include="vcmi.ico" />
     <None Include="vcmi.ico" />
@@ -254,5 +247,6 @@
       <Filter>gui</Filter>
       <Filter>gui</Filter>
     </ClInclude>
     </ClInclude>
     <ClInclude Include="..\CCallback.h" />
     <ClInclude Include="..\CCallback.h" />
+    <ClInclude Include="SDLRWwrapper.h" />
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 38 - 14
client/battle/CBattleInterface.cpp

@@ -95,7 +95,7 @@ void CBattleInterface::addNewAnim(CBattleAnimation *anim)
 CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
 CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
 								   const CGHeroInstance *hero1, const CGHeroInstance *hero2,
 								   const CGHeroInstance *hero1, const CGHeroInstance *hero2,
 								   const SDL_Rect & myRect,
 								   const SDL_Rect & myRect,
-								   std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen)
+								   std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
 	: background(nullptr), queue(nullptr), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
 	: background(nullptr), queue(nullptr), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
       activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), previouslyHoveredHex(-1),
       activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), previouslyHoveredHex(-1),
 	  currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
 	  currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
@@ -105,12 +105,15 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 {
 {
 	OBJ_CONSTRUCTION;
 	OBJ_CONSTRUCTION;
 
 
-	if (!curInt)
+	if(spectatorInt)
+		curInt = spectatorInt;
+	else if(!curInt)
 	{
 	{
 		//May happen when we are defending during network MP game -> attacker interface is just not present
 		//May happen when we are defending during network MP game -> attacker interface is just not present
 		curInt = defenderInt;
 		curInt = defenderInt;
 	}
 	}
 
 
+
 	animsAreDisplayed.setn(false);
 	animsAreDisplayed.setn(false);
 	pos = myRect;
 	pos = myRect;
 	strongInterest = true;
 	strongInterest = true;
@@ -377,6 +380,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	currentAction = INVALID;
 	currentAction = INVALID;
 	selectedAction = INVALID;
 	selectedAction = INVALID;
 	addUsedEvents(RCLICK | MOVE | KEYBOARD);
 	addUsedEvents(RCLICK | MOVE | KEYBOARD);
+
+	blockUI(settings["session"]["spectate"].Bool());
 }
 }
 
 
 CBattleInterface::~CBattleInterface()
 CBattleInterface::~CBattleInterface()
@@ -448,6 +453,7 @@ CBattleInterface::~CBattleInterface()
 		int terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType;
 		int terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType;
 		CCS->musich->playMusicFromSet("terrain", terrain, true);
 		CCS->musich->playMusicFromSet("terrain", terrain, true);
 	}
 	}
+	animsAreDisplayed.setn(false);
 }
 }
 
 
 void CBattleInterface::setPrintCellBorders(bool set)
 void CBattleInterface::setPrintCellBorders(bool set)
@@ -871,19 +877,24 @@ void CBattleInterface::bSpellf()
 	if (spellDestSelectMode) //we are casting a spell
 	if (spellDestSelectMode) //we are casting a spell
 		return;
 		return;
 
 
-	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
-
 	if (!myTurn)
 	if (!myTurn)
 		return;
 		return;
 
 
 	auto myHero = currentHero();
 	auto myHero = currentHero();
-	ESpellCastProblem::ESpellCastProblem spellCastProblem;
-	if (curInt->cb->battleCanCastSpell(&spellCastProblem))
+	if(!myHero)
+		return;
+
+	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+
+	ESpellCastProblem::ESpellCastProblem spellCastProblem = curInt->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING);
+
+	if(spellCastProblem == ESpellCastProblem::OK)
 	{
 	{
 		GH.pushInt(new CSpellWindow(myHero, curInt.get()));
 		GH.pushInt(new CSpellWindow(myHero, curInt.get()));
 	}
 	}
 	else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
 	else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
 	{
 	{
+		//TODO: move to spell mechanics, add more information to spell cast problem
 		//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
 		//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
 		auto blockingBonus = currentHero()->getBonusLocalFirst(Selector::type(Bonus::BLOCK_ALL_MAGIC));
 		auto blockingBonus = currentHero()->getBonusLocalFirst(Selector::type(Bonus::BLOCK_ALL_MAGIC));
 		if (!blockingBonus)
 		if (!blockingBonus)
@@ -1241,6 +1252,11 @@ void CBattleInterface::battleFinished(const BattleResult& br)
 void CBattleInterface::displayBattleFinished()
 void CBattleInterface::displayBattleFinished()
 {
 {
 	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
 	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+	if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
+	{
+		GH.popIntTotally(this);
+		return;
+	}
 
 
 	SDL_Rect temp_rect = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19);
 	SDL_Rect temp_rect = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19);
 	resWindow = new CBattleResultWindow(*bresult, temp_rect, *this->curInt);
 	resWindow = new CBattleResultWindow(*bresult, temp_rect, *this->curInt);
@@ -1526,6 +1542,9 @@ void CBattleInterface::setAnimSpeed(int set)
 
 
 int CBattleInterface::getAnimSpeed() const
 int CBattleInterface::getAnimSpeed() const
 {
 {
+	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
+		return vstd::round(settings["session"]["spectate-battle-speed"].Float() *100);
+
 	return vstd::round(settings["battle"]["animationSpeed"].Float() *100);
 	return vstd::round(settings["battle"]["animationSpeed"].Float() *100);
 }
 }
 
 
@@ -1658,8 +1677,7 @@ void CBattleInterface::enterCreatureCastingMode()
 	{
 	{
 		const ISpellCaster *caster = activeStack;
 		const ISpellCaster *caster = activeStack;
 		const CSpell *spell = SpellID(creatureSpellToCast).toSpell();
 		const CSpell *spell = SpellID(creatureSpellToCast).toSpell();
-
-		const bool isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, spell, ECastingMode::CREATURE_ACTIVE_CASTING, BattleHex::INVALID) == ESpellCastProblem::OK);
+		const bool isCastingPossible = (spell->canBeCastAt(curInt->cb.get(), caster, ECastingMode::CREATURE_ACTIVE_CASTING, BattleHex::INVALID) == ESpellCastProblem::OK);
 
 
 		if (isCastingPossible)
 		if (isCastingPossible)
 		{
 		{
@@ -1849,11 +1867,17 @@ void CBattleInterface::showQueue()
 
 
 void CBattleInterface::blockUI(bool on)
 void CBattleInterface::blockUI(bool on)
 {
 {
-	ESpellCastProblem::ESpellCastProblem spellcastingProblem;
-	bool canCastSpells = curInt->cb->battleCanCastSpell(&spellcastingProblem);
-	//if magic is blocked, we leave button active, so the message can be displayed (cf bug #97)
-	if (!canCastSpells)
-		canCastSpells = spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
+	bool canCastSpells = false;
+	auto hero = curInt->cb->battleGetMyHero();
+
+	if(hero)
+	{
+		ESpellCastProblem::ESpellCastProblem spellcastingProblem = curInt->cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING);
+
+		//if magic is blocked, we leave button active, so the message can be displayed after button click
+		canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
+	}
+
 	bool canWait = activeStack ? !activeStack->waited() : false;
 	bool canWait = activeStack ? !activeStack->waited() : false;
 
 
 	bOptions->block(on);
 	bOptions->block(on);
@@ -2511,7 +2535,7 @@ bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack
 		else
 		else
 		{
 		{
 			const ECastingMode::ECastingMode mode = creatureCasting ? ECastingMode::CREATURE_ACTIVE_CASTING : ECastingMode::HERO_CASTING;
 			const ECastingMode::ECastingMode mode = creatureCasting ? ECastingMode::CREATURE_ACTIVE_CASTING : ECastingMode::HERO_CASTING;
-			isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, sp, mode, myNumber) == ESpellCastProblem::OK);
+			isCastingPossible = (sp->canBeCastAt(curInt->cb.get(), caster, mode, myNumber) == ESpellCastProblem::OK);
 		}
 		}
 	}
 	}
 	else
 	else

+ 1 - 1
client/battle/CBattleInterface.h

@@ -269,7 +269,7 @@ public:
 	ui32 animIDhelper; //for giving IDs for animations
 	ui32 animIDhelper; //for giving IDs for animations
 	static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
 	static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
 
 
-	CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen); //c-tor
+	CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr); //c-tor
 	virtual ~CBattleInterface(); //d-tor
 	virtual ~CBattleInterface(); //d-tor
 
 
 	//std::vector<TimeInterested*> timeinterested; //animation handling
 	//std::vector<TimeInterested*> timeinterested; //animation handling

+ 4 - 8
client/battle/CBattleInterfaceClasses.cpp

@@ -191,7 +191,7 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
 	if(myOwner->spellDestSelectMode) //we are casting a spell
 	if(myOwner->spellDestSelectMode) //we are casting a spell
 		return;
 		return;
 
 
-	if(myHero != nullptr && !down &&  myOwner->myTurn && myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell()) //check conditions
+	if(myHero != nullptr && !down &&  myOwner->myTurn && myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK) //check conditions
 	{
 	{
 		for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
 		for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
 		{
 		{
@@ -211,14 +211,10 @@ void CBattleHero::clickRight(tribool down, bool previousState)
 	windowPosition.y = myOwner->pos.y + 135;
 	windowPosition.y = myOwner->pos.y + 135;
 
 
 	InfoAboutHero targetHero;
 	InfoAboutHero targetHero;
-
-	if (down && myOwner->myTurn)
+	if(down && (myOwner->myTurn || settings["session"]["spectate"].Bool()))
 	{
 	{
-		if (myHero != nullptr)
-			targetHero.initFromHero(myHero, InfoAboutHero::EInfoLevel::INBATTLE);
-		else
-			targetHero = myOwner->enemyHero();
-
+		auto h = flip ? myOwner->defendingHeroInstance : myOwner->attackingHeroInstance;
+		targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE);
 		GH.pushInt(new CHeroInfoWindow(targetHero, &windowPosition));
 		GH.pushInt(new CHeroInfoWindow(targetHero, &windowPosition));
 	}
 	}
 }
 }

+ 91 - 59
client/gui/CGuiHandler.cpp

@@ -12,6 +12,7 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CConfigHandler.h"
 #include "../CMT.h"
 #include "../CMT.h"
 #include "../CPlayerInterface.h"
 #include "../CPlayerInterface.h"
+#include "../battle/CBattleInterface.h"
 
 
 extern std::queue<SDL_Event> events;
 extern std::queue<SDL_Event> events;
 extern boost::mutex eventsM;
 extern boost::mutex eventsM;
@@ -59,6 +60,7 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std:
 {
 {
 	processList(CIntObject::LCLICK,activityFlag,&lclickable,cb);
 	processList(CIntObject::LCLICK,activityFlag,&lclickable,cb);
 	processList(CIntObject::RCLICK,activityFlag,&rclickable,cb);
 	processList(CIntObject::RCLICK,activityFlag,&rclickable,cb);
+	processList(CIntObject::MCLICK,activityFlag,&mclickable,cb);
 	processList(CIntObject::HOVER,activityFlag,&hoverable,cb);
 	processList(CIntObject::HOVER,activityFlag,&hoverable,cb);
 	processList(CIntObject::MOVE,activityFlag,&motioninterested,cb);
 	processList(CIntObject::MOVE,activityFlag,&motioninterested,cb);
 	processList(CIntObject::KEYBOARD,activityFlag,&keyinterested,cb);
 	processList(CIntObject::KEYBOARD,activityFlag,&keyinterested,cb);
@@ -191,6 +193,46 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent)
 	if (sEvent->type==SDL_KEYDOWN || sEvent->type==SDL_KEYUP)
 	if (sEvent->type==SDL_KEYDOWN || sEvent->type==SDL_KEYUP)
 	{
 	{
 		SDL_KeyboardEvent key = sEvent->key;
 		SDL_KeyboardEvent key = sEvent->key;
+		if(sEvent->type == SDL_KEYDOWN && key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
+		{
+			//TODO: we need some central place for all interface-independent hotkeys
+			Settings s = settings.write["session"];
+			switch(key.keysym.sym)
+			{
+			case SDLK_F5:
+				if(settings["session"]["spectate-locked-pim"].Bool())
+					LOCPLINT->pim->unlock();
+				else
+					LOCPLINT->pim->lock();
+				s["spectate-locked-pim"].Bool() = !settings["session"]["spectate-locked-pim"].Bool();
+				break;
+
+			case SDLK_F6:
+				s["spectate-ignore-hero"].Bool() = !settings["session"]["spectate-ignore-hero"].Bool();
+				break;
+
+			case SDLK_F7:
+				s["spectate-skip-battle"].Bool() = !settings["session"]["spectate-skip-battle"].Bool();
+				break;
+
+			case SDLK_F8:
+				s["spectate-skip-battle-result"].Bool() = !settings["session"]["spectate-skip-battle-result"].Bool();
+				break;
+
+			case SDLK_F9:
+				//not working yet since CClient::run remain locked after CBattleInterface removal
+				if(LOCPLINT->battleInt)
+				{
+					GH.popIntTotally(GH.topInt());
+					vstd::clear_pointer(LOCPLINT->battleInt);
+				}
+				break;
+
+			default:
+				break;
+			}
+			return;
+		}
 
 
 		//translate numpad keys
 		//translate numpad keys
 		if(key.keysym.sym == SDLK_KP_ENTER)
 		if(key.keysym.sym == SDLK_KP_ENTER)
@@ -219,18 +261,18 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent)
 		CCS->curh->cursorMove(sEvent->motion.x, sEvent->motion.y);
 		CCS->curh->cursorMove(sEvent->motion.x, sEvent->motion.y);
 		handleMouseMotion(sEvent);
 		handleMouseMotion(sEvent);
 	}
 	}
-	else if (sEvent->type==SDL_MOUSEBUTTONDOWN)
+	else if(sEvent->type == SDL_MOUSEBUTTONDOWN)
 	{
 	{
-		if(sEvent->button.button == SDL_BUTTON_LEFT)
+		switch(sEvent->button.button)
 		{
 		{
-
-			if(lastClick == sEvent->motion  &&  (SDL_GetTicks() - lastClickTime) < 300)
+		case SDL_BUTTON_LEFT:
+			if(lastClick == sEvent->motion && (SDL_GetTicks() - lastClickTime) < 300)
 			{
 			{
 				std::list<CIntObject*> hlp = doubleClickInterested;
 				std::list<CIntObject*> hlp = doubleClickInterested;
-				for(auto i=hlp.begin(); i != hlp.end() && current; i++)
+				for(auto i = hlp.begin(); i != hlp.end() && current; i++)
 				{
 				{
-					if(!vstd::contains(doubleClickInterested,*i)) continue;
-					if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
+					if(!vstd::contains(doubleClickInterested, *i)) continue;
+					if(isItIn(&(*i)->pos, sEvent->motion.x, sEvent->motion.y))
 					{
 					{
 						(*i)->onDoubleClick();
 						(*i)->onDoubleClick();
 					}
 					}
@@ -241,31 +283,16 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent)
 			lastClick = sEvent->motion;
 			lastClick = sEvent->motion;
 			lastClickTime = SDL_GetTicks();
 			lastClickTime = SDL_GetTicks();
 
 
-			std::list<CIntObject*> hlp = lclickable;
-			for(auto i=hlp.begin(); i != hlp.end() && current; i++)
-			{
-				if(!vstd::contains(lclickable,*i)) continue;
-				if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
-				{
-					prev = (*i)->pressedL;
-					(*i)->pressedL = true;
-					(*i)->clickLeft(true, prev);
-				}
-			}
-		}
-		else if (sEvent->button.button == SDL_BUTTON_RIGHT)
-		{
-			std::list<CIntObject*> hlp = rclickable;
-			for(auto i=hlp.begin(); i != hlp.end() && current; i++)
-			{
-				if(!vstd::contains(rclickable,*i)) continue;
-				if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
-				{
-					prev = (*i)->pressedR;
-					(*i)->pressedR = true;
-					(*i)->clickRight(true, prev);
-				}
-			}
+			handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, true);
+			break;
+		case SDL_BUTTON_RIGHT:
+			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, true);
+			break;
+		case SDL_BUTTON_MIDDLE:
+			handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, true);
+			break;
+		default:
+			break;
 		}
 		}
 	}
 	}
 	else if (sEvent->type == SDL_MOUSEWHEEL)
 	else if (sEvent->type == SDL_MOUSEWHEEL)
@@ -295,40 +322,44 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent)
 		}
 		}
 	}
 	}
 	//todo: muiltitouch
 	//todo: muiltitouch
-	else if ((sEvent->type==SDL_MOUSEBUTTONUP) && (sEvent->button.button == SDL_BUTTON_LEFT))
+	else if(sEvent->type == SDL_MOUSEBUTTONUP)
 	{
 	{
-		std::list<CIntObject*> hlp = lclickable;
-		for(auto i=hlp.begin(); i != hlp.end() && current; i++)
+		switch(sEvent->button.button)
 		{
 		{
-			if(!vstd::contains(lclickable,*i)) continue;
-			prev = (*i)->pressedL;
-			(*i)->pressedL = false;
-			if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
-			{
-				(*i)->clickLeft(false, prev);
-			}
-			else
-				(*i)->clickLeft(boost::logic::indeterminate, prev);
+		case SDL_BUTTON_LEFT:
+			handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false);
+			break;
+		case SDL_BUTTON_RIGHT:
+			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
+			break;
+		case SDL_BUTTON_MIDDLE:
+			handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false);
+			break;
 		}
 		}
 	}
 	}
-	else if ((sEvent->type==SDL_MOUSEBUTTONUP) && (sEvent->button.button == SDL_BUTTON_RIGHT))
+	current = nullptr;
+} //event end
+
+void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed)
+{
+	auto hlp = interestedObjs;
+	for(auto i = hlp.begin(); i != hlp.end() && current; i++)
 	{
 	{
-		std::list<CIntObject*> hlp = rclickable;
-		for(auto i=hlp.begin(); i != hlp.end() && current; i++)
+		if(!vstd::contains(interestedObjs, *i)) continue;
+
+		auto prev = (*i)->mouseState(btn);
+		if(!isPressed)
+			(*i)->updateMouseState(btn, isPressed);
+		if(isItIn(&(*i)->pos, current->motion.x, current->motion.y))
 		{
 		{
-			if(!vstd::contains(rclickable,*i)) continue;
-			prev = (*i)->pressedR;
-			(*i)->pressedR = false;
-			if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
-			{
-				(*i)->clickRight(false, prev);
-			}
-			else
-				(*i)->clickRight(boost::logic::indeterminate, prev);
+			if(isPressed)
+				(*i)->updateMouseState(btn, isPressed);
+			(*i)->click(btn, isPressed, prev);
 		}
 		}
+		else if(!isPressed)
+			(*i)->click(btn, boost::logic::indeterminate, prev);
 	}
 	}
-	current = nullptr;
-} //event end
+}
 
 
 void CGuiHandler::handleMouseMotion(SDL_Event *sEvent)
 void CGuiHandler::handleMouseMotion(SDL_Event *sEvent)
 {
 {
@@ -361,7 +392,8 @@ void CGuiHandler::simpleRedraw()
 	//update only top interface and draw background
 	//update only top interface and draw background
 	if(objsToBlit.size() > 1)
 	if(objsToBlit.size() > 1)
 		blitAt(screen2,0,0,screen); //blit background
 		blitAt(screen2,0,0,screen); //blit background
-	objsToBlit.back()->show(screen); //blit active interface/window
+	if(!objsToBlit.empty())
+		objsToBlit.back()->show(screen); //blit active interface/window
 }
 }
 
 
 void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)
 void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)

+ 3 - 0
client/gui/CGuiHandler.h

@@ -10,6 +10,7 @@ class CIntObject;
 class IUpdateable;
 class IUpdateable;
 class IShowActivatable;
 class IShowActivatable;
 class IShowable;
 class IShowable;
+enum class EIntObjMouseBtnType;
 template <typename T> struct CondSh;
 template <typename T> struct CondSh;
 
 
 /*
 /*
@@ -53,6 +54,7 @@ private:
 	//active GUI elements (listening for events
 	//active GUI elements (listening for events
 	CIntObjectList lclickable,
 	CIntObjectList lclickable,
 				   rclickable,
 				   rclickable,
+				   mclickable,
 				   hoverable,
 				   hoverable,
 				   keyinterested,
 				   keyinterested,
 				   motioninterested,
 				   motioninterested,
@@ -62,6 +64,7 @@ private:
 	               textInterested;
 	               textInterested;
 
 
 
 
+	void handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed);
 	void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
 	void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
 public:
 public:
 	void handleElementActivate(CIntObject * elem, ui16 activityFlag);
 	void handleElementActivate(CIntObject * elem, ui16 activityFlag);

+ 22 - 12
client/gui/CIntObject.cpp

@@ -16,7 +16,7 @@ CIntObject::CIntObject(int used_, Point pos_):
 	parent(parent_m),
 	parent(parent_m),
 	active(active_m)
 	active(active_m)
 {
 {
-	pressedL = pressedR = hovered = captureAllKeys = strongInterest = false;
+	hovered = captureAllKeys = strongInterest = false;
 	toNextTick = timerDelay = 0;
 	toNextTick = timerDelay = 0;
 	used = used_;
 	used = used_;
 
 
@@ -134,6 +134,23 @@ CIntObject::~CIntObject()
 		parent_m->removeChild(this);
 		parent_m->removeChild(this);
 }
 }
 
 
+void CIntObject::click(EIntObjMouseBtnType btn, tribool down, bool previousState)
+{
+	switch(btn)
+	{
+	default:
+	case EIntObjMouseBtnType::LEFT:
+		clickLeft(down, previousState);
+		break;
+	case EIntObjMouseBtnType::MIDDLE:
+		clickMiddle(down, previousState);
+		break;
+	case EIntObjMouseBtnType::RIGHT:
+		clickRight(down, previousState);
+		break;
+	}
+}
+
 void CIntObject::printAtLoc( const std::string & text, int x, int y, EFonts font, SDL_Color kolor/*=Colors::WHITE*/, SDL_Surface * dst/*=screen*/ )
 void CIntObject::printAtLoc( const std::string & text, int x, int y, EFonts font, SDL_Color kolor/*=Colors::WHITE*/, SDL_Surface * dst/*=screen*/ )
 {
 {
 	graphics->fonts[font]->renderTextLeft(dst, text, kolor, Point(pos.x + x, pos.y + y));
 	graphics->fonts[font]->renderTextLeft(dst, text, kolor, Point(pos.x + x, pos.y + y));
@@ -340,16 +357,9 @@ void CKeyShortcut::keyPressed(const SDL_KeyboardEvent & key)
 	if(vstd::contains(assignedKeys,key.keysym.sym)
 	if(vstd::contains(assignedKeys,key.keysym.sym)
 	 || vstd::contains(assignedKeys, CGuiHandler::numToDigit(key.keysym.sym)))
 	 || vstd::contains(assignedKeys, CGuiHandler::numToDigit(key.keysym.sym)))
 	{
 	{
-		bool prev = pressedL;
-		if(key.state == SDL_PRESSED)
-		{
-			pressedL = true;
-			clickLeft(true, prev);
-		}
-		else
-		{
-			pressedL = false;
-			clickLeft(false, prev);
-		}
+		bool prev = mouseState(EIntObjMouseBtnType::LEFT);		
+		updateMouseState(EIntObjMouseBtnType::LEFT, key.state == SDL_PRESSED);
+		clickLeft(key.state == SDL_PRESSED, prev);
+		
 	}
 	}
 }
 }

+ 10 - 7
client/gui/CIntObject.h

@@ -61,6 +61,7 @@ public:
 	virtual ~IShowActivatable(){}; //d-tor
 	virtual ~IShowActivatable(){}; //d-tor
 };
 };
 
 
+enum class EIntObjMouseBtnType { LEFT, MIDDLE, RIGHT };
 //typedef ui16 ActivityFlag;
 //typedef ui16 ActivityFlag;
 
 
 // Base UI element
 // Base UI element
@@ -73,6 +74,8 @@ class CIntObject : public IShowActivatable //interface object
 	int toNextTick;
 	int toNextTick;
 	int timerDelay;
 	int timerDelay;
 
 
+	std::map<EIntObjMouseBtnType, bool> currentMouseState;
+
 	void onTimer(int timePassed);
 	void onTimer(int timePassed);
 
 
 	//non-const versions of fields to allow changing them in CIntObject
 	//non-const versions of fields to allow changing them in CIntObject
@@ -104,13 +107,13 @@ public:
 	CIntObject(int used=0, Point offset=Point());
 	CIntObject(int used=0, Point offset=Point());
 	virtual ~CIntObject(); //d-tor
 	virtual ~CIntObject(); //d-tor
 
 
-	//l-clicks handling
-	/*const*/ bool pressedL; //for determining if object is L-pressed
-	virtual void clickLeft(tribool down, bool previousState){}
+	void updateMouseState(EIntObjMouseBtnType btn, bool state) { currentMouseState[btn] = state; }
+	bool mouseState(EIntObjMouseBtnType btn) const { return currentMouseState.count(btn) ? currentMouseState.at(btn) : false; }
 
 
-	//r-clicks handling
-	/*const*/ bool pressedR; //for determining if object is R-pressed
-	virtual void clickRight(tribool down, bool previousState){}
+	virtual void click(EIntObjMouseBtnType btn, tribool down, bool previousState);
+	virtual void clickLeft(tribool down, bool previousState) {}
+	virtual void clickRight(tribool down, bool previousState) {}
+	virtual void clickMiddle(tribool down, bool previousState) {}
 
 
 	//hover handling
 	//hover handling
 	/*const*/ bool hovered;  //for determining if object is hovered
 	/*const*/ bool hovered;  //for determining if object is hovered
@@ -138,7 +141,7 @@ public:
 	//double click
 	//double click
 	virtual void onDoubleClick(){}
 	virtual void onDoubleClick(){}
 
 
-	enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, ALL=0xffff};
+	enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, MCLICK=1024, ALL=0xffff};
 	const ui16 & active;
 	const ui16 & active;
 	void addUsedEvents(ui16 newActions);
 	void addUsedEvents(ui16 newActions);
 	void removeUsedEvents(ui16 newActions);
 	void removeUsedEvents(ui16 newActions);

+ 6 - 3
client/mapHandler.cpp

@@ -54,7 +54,7 @@ struct NeighborTilesInfo
 			if ( dx + pos.x < 0 || dx + pos.x >= sizes.x
 			if ( dx + pos.x < 0 || dx + pos.x >= sizes.x
 			  || dy + pos.y < 0 || dy + pos.y >= sizes.y)
 			  || dy + pos.y < 0 || dy + pos.y >= sizes.y)
 				return false;
 				return false;
-			return visibilityMap[dx+pos.x][dy+pos.y][pos.z];
+			return settings["session"]["spectate"].Bool() ? true : visibilityMap[dx+pos.x][dy+pos.y][pos.z];
 		};
 		};
 		d7 = getTile(-1, -1); //789
 		d7 = getTile(-1, -1); //789
 		d8 = getTile( 0, -1); //456
 		d8 = getTile( 0, -1); //456
@@ -563,7 +563,7 @@ void CMapHandler::CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf
 		const CGObjectInstance * obj = object.obj;
 		const CGObjectInstance * obj = object.obj;
 
 
 		const bool sameLevel = obj->pos.z == pos.z;
 		const bool sameLevel = obj->pos.z == pos.z;
-		const bool isVisible = (*info->visibilityMap)[pos.x][pos.y][pos.z];
+		const bool isVisible = settings["session"]["spectate"].Bool() ? true : (*info->visibilityMap)[pos.x][pos.y][pos.z];
 		const bool isVisitable = obj->visitableAt(pos.x, pos.y);
 		const bool isVisitable = obj->visitableAt(pos.x, pos.y);
 
 
 		if(sameLevel && isVisible && isVisitable)
 		if(sameLevel && isVisible && isVisitable)
@@ -895,7 +895,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 			{
 			{
 				const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z];
 				const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z];
 
 
-				if (!(*info->visibilityMap)[pos.x][pos.y][topTile.z] && !info->showAllTerrain)
+				if(!settings["session"]["spectate"].Bool() && !(*info->visibilityMap)[pos.x][pos.y][topTile.z] && !info->showAllTerrain)
 					drawFow(targetSurf);
 					drawFow(targetSurf);
 
 
 				// overlay needs to be drawn over fow, because of artifacts-aura-like spells
 				// overlay needs to be drawn over fow, because of artifacts-aura-like spells
@@ -1099,6 +1099,9 @@ bool CMapHandler::CMapBlitter::canDrawObject(const CGObjectInstance * obj) const
 
 
 bool CMapHandler::CMapBlitter::canDrawCurrentTile() const
 bool CMapHandler::CMapBlitter::canDrawCurrentTile() const
 {
 {
+	if(settings["session"]["spectate"].Bool())
+		return true;
+
 	const NeighborTilesInfo neighbors(pos, parent->sizes, *info->visibilityMap);
 	const NeighborTilesInfo neighbors(pos, parent->sizes, *info->visibilityMap);
 	return !neighbors.areAllHidden();
 	return !neighbors.areAllHidden();
 }
 }

+ 1 - 1
client/widgets/AdventureMapClasses.cpp

@@ -587,7 +587,7 @@ void CMinimap::hover(bool on)
 
 
 void CMinimap::mouseMoved(const SDL_MouseMotionEvent & sEvent)
 void CMinimap::mouseMoved(const SDL_MouseMotionEvent & sEvent)
 {
 {
-	if (pressedL)
+	if(mouseState(EIntObjMouseBtnType::LEFT))
 		moveAdvMapSelection();
 		moveAdvMapSelection();
 }
 }
 
 

+ 1 - 1
client/widgets/Buttons.cpp

@@ -623,7 +623,7 @@ void CSlider::clickLeft(tribool down, bool previousState)
 			return;
 			return;
 		// 		if (rw>1) return;
 		// 		if (rw>1) return;
 		// 		if (rw<0) return;
 		// 		if (rw<0) return;
-		slider->clickLeft(true, slider->pressedL);
+		slider->clickLeft(true, slider->mouseState(EIntObjMouseBtnType::LEFT));
 		moveTo(rw * positions  +  0.5);
 		moveTo(rw * positions  +  0.5);
 		return;
 		return;
 	}
 	}

+ 10 - 9
client/widgets/CArtifactHolder.cpp

@@ -161,16 +161,18 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState)
 				{
 				{
 					const CArtifact * const cur = ourOwner->commonInfo->src.art->artType;
 					const CArtifact * const cur = ourOwner->commonInfo->src.art->artType;
 
 
-					switch(cur->id)
+					if(cur->id == ArtifactID::CATAPULT)
 					{
 					{
-					case ArtifactID::CATAPULT:
 						//should not happen, catapult cannot be selected
 						//should not happen, catapult cannot be selected
-						assert(cur->id != ArtifactID::CATAPULT);
-						break;
-					case ArtifactID::BALLISTA: case ArtifactID::AMMO_CART: case ArtifactID::FIRST_AID_TENT: //war machines cannot go to backpack
+						logGlobal->error("Attempt to move Catapult");
+					}
+					else if(cur->isBig())
+					{
+						//war machines cannot go to backpack
 						LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->Name()));
 						LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->Name()));
-						break;
-					default:
+					}
+					else
+					{
 						setMeAsDest();
 						setMeAsDest();
 						vstd::amin(ourOwner->commonInfo->dst.slotID, ArtifactPosition(
 						vstd::amin(ourOwner->commonInfo->dst.slotID, ArtifactPosition(
 							ourOwner->curHero->artifactsInBackpack.size() + GameConstants::BACKPACK_START));
 							ourOwner->curHero->artifactsInBackpack.size() + GameConstants::BACKPACK_START));
@@ -184,7 +186,6 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState)
 							deselect();
 							deselect();
 						else
 						else
 							ourOwner->realizeCurrentTransaction();
 							ourOwner->realizeCurrentTransaction();
-						break;
 					}
 					}
 				}
 				}
 			}
 			}
@@ -366,7 +367,7 @@ bool CHeroArtPlace::fitsHere(const CArtifactInstance * art) const
 
 
 	// Anything but War Machines can be placed in backpack.
 	// Anything but War Machines can be placed in backpack.
 	if (slotID >= GameConstants::BACKPACK_START)
 	if (slotID >= GameConstants::BACKPACK_START)
-		return !CGI->arth->isBigArtifact(art->artType->id);
+		return !art->artType->isBig();
 
 
 	return art->canBePutAt(ArtifactLocation(ourOwner->curHero, slotID), true);
 	return art->canBePutAt(ArtifactLocation(ourOwner->curHero, slotID), true);
 }
 }

+ 53 - 31
client/windows/CAdvmapInterface.cpp

@@ -98,7 +98,7 @@ CTerrainRect::CTerrainRect()
 	pos.w=ADVOPT.advmapW;
 	pos.w=ADVOPT.advmapW;
 	pos.h=ADVOPT.advmapH;
 	pos.h=ADVOPT.advmapH;
 	moveX = moveY = 0;
 	moveX = moveY = 0;
-	addUsedEvents(LCLICK | RCLICK | HOVER | MOVE);
+	addUsedEvents(LCLICK | RCLICK | MCLICK | HOVER | MOVE);
 }
 }
 
 
 CTerrainRect::~CTerrainRect()
 CTerrainRect::~CTerrainRect()
@@ -124,17 +124,10 @@ void CTerrainRect::clickLeft(tribool down, bool previousState)
 #ifdef VCMI_ANDROID
 #ifdef VCMI_ANDROID
 	if(adventureInt->swipeEnabled)
 	if(adventureInt->swipeEnabled)
 	{
 	{
-		if(down == true)
+		if(handleSwipeStateChange(down == true))
 		{
 		{
-			swipeInitialRealPos = int3(GH.current->motion.x, GH.current->motion.y, 0);
-			swipeInitialMapPos = int3(adventureInt->position);
 			return; // if swipe is enabled, we don't process "down" events and wait for "up" (to make sure this wasn't a swiping gesture)
 			return; // if swipe is enabled, we don't process "down" events and wait for "up" (to make sure this wasn't a swiping gesture)
 		}
 		}
-		else if(isSwiping) // only accept this touch if it wasn't a swipe
-		{
-			isSwiping = false;
-			return;
-		}
 	}
 	}
 	else
 	else
 	{
 	{
@@ -165,22 +158,32 @@ void CTerrainRect::clickRight(tribool down, bool previousState)
 		adventureInt->tileRClicked(mp);
 		adventureInt->tileRClicked(mp);
 }
 }
 
 
+void CTerrainRect::clickMiddle(tribool down, bool previousState)
+{
+	handleSwipeStateChange(down == true);
+}
+
 void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
 void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
 {
 {
 	handleHover(sEvent);
 	handleHover(sEvent);
 
 
-#ifdef VCMI_ANDROID
-	if(!adventureInt->swipeEnabled || sEvent.state == 0)
+	if(!adventureInt->swipeEnabled)
 		return;
 		return;
 
 
 	handleSwipeMove(sEvent);
 	handleSwipeMove(sEvent);
-#endif // !VCMI_ANDROID
 }
 }
 
 
-#ifdef VCMI_ANDROID
-
 void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
 void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
 {
 {
+#ifdef VCMI_ANDROID
+	if(sEvent.state == 0) // any "button" is enough on android
+#else //!VCMI_ANDROID
+	if((sEvent.state & SDL_BUTTON_MMASK) == 0) // swipe only works with middle mouse on other platforms
+#endif //!VCMI_ANDROID
+	{
+		return;
+	}
+
 	if(!isSwiping)
 	if(!isSwiping)
 	{
 	{
 		// try to distinguish if this touch was meant to be a swipe or just fat-fingering press
 		// try to distinguish if this touch was meant to be a swipe or just fat-fingering press
@@ -201,7 +204,21 @@ void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
 	}
 	}
 }
 }
 
 
-#endif // VCMI_ANDROID
+bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
+{
+	if(btnPressed)
+	{
+		swipeInitialRealPos = int3(GH.current->motion.x, GH.current->motion.y, 0);
+		swipeInitialMapPos = int3(adventureInt->position);
+		return true;
+	}
+	else if(isSwiping) // only accept this touch if it wasn't a swipe
+	{
+		isSwiping = false;
+		return true;
+	}
+	return false;
+}
 
 
 void CTerrainRect::handleHover(const SDL_MouseMotionEvent &sEvent)
 void CTerrainRect::handleHover(const SDL_MouseMotionEvent &sEvent)
 {
 {
@@ -534,11 +551,9 @@ CAdvMapInt::CAdvMapInt():
 	infoBar(Rect(ADVOPT.infoboxX, ADVOPT.infoboxY, 192, 192)), state(NA),
 	infoBar(Rect(ADVOPT.infoboxX, ADVOPT.infoboxY, 192, 192)), state(NA),
   spellBeingCasted(nullptr), position(int3(0, 0, 0)), selection(nullptr),
   spellBeingCasted(nullptr), position(int3(0, 0, 0)), selection(nullptr),
   updateScreen(false), anim(0), animValHitCount(0), heroAnim(0), heroAnimValHitCount(0),
   updateScreen(false), anim(0), animValHitCount(0), heroAnim(0), heroAnimValHitCount(0),
-	activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false)
-#ifdef VCMI_ANDROID
-	, swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false),
+	activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false), 
+	swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false),
 	swipeTargetPosition(int3(-1, -1, -1))
 	swipeTargetPosition(int3(-1, -1, -1))
-#endif
 {
 {
   adventureInt = this;
   adventureInt = this;
 	pos.x = pos.y = 0;
 	pos.x = pos.y = 0;
@@ -1007,21 +1022,24 @@ void CAdvMapInt::show(SDL_Surface * to)
 	}
 	}
 	++heroAnim;
 	++heroAnim;
 
 
-#ifdef VCMI_ANDROID
 	if(swipeEnabled)
 	if(swipeEnabled)
 	{
 	{
 		handleSwipeUpdate();
 		handleSwipeUpdate();
 	}
 	}
+#ifdef VCMI_ANDROID // on android, map-moving mode is exclusive (TODO technically it might work with both enabled; to be checked)
 	else
 	else
+#endif // VCMI_ANDROID
 	{
 	{
-#endif // !VCMI_ANDROID
 		handleMapScrollingUpdate();
 		handleMapScrollingUpdate();
-#ifdef VCMI_ANDROID
 	}
 	}
-#endif
 
 
 	for(int i = 0; i < 4; i++)
 	for(int i = 0; i < 4; i++)
-		gems[i]->setFrame(LOCPLINT->playerID.getNum());
+	{
+		if(settings["session"]["spectate"].Bool())
+			gems[i]->setFrame(PlayerColor(1).getNum());
+		else
+			gems[i]->setFrame(LOCPLINT->playerID.getNum());
+	}
 	if(updateScreen)
 	if(updateScreen)
 	{
 	{
 		int3 betterPos = LOCPLINT->repairScreenPos(position);
 		int3 betterPos = LOCPLINT->repairScreenPos(position);
@@ -1084,14 +1102,13 @@ void CAdvMapInt::handleMapScrollingUpdate()
 	}
 	}
 }
 }
 
 
-#ifdef VCMI_ANDROID
-
 void CAdvMapInt::handleSwipeUpdate()
 void CAdvMapInt::handleSwipeUpdate()
 {
 {
 	if(swipeMovementRequested)
 	if(swipeMovementRequested)
 	{
 	{
-		position.x = swipeTargetPosition.x;
-		position.y = swipeTargetPosition.y;
+		auto fixedPos = LOCPLINT->repairScreenPos(swipeTargetPosition);
+		position.x = fixedPos.x;
+		position.y = fixedPos.y;
 		CCS->curh->changeGraphic(ECursor::DEFAULT, 0);
 		CCS->curh->changeGraphic(ECursor::DEFAULT, 0);
 		updateScreen = true;
 		updateScreen = true;
 		minimap.redraw();
 		minimap.redraw();
@@ -1099,8 +1116,6 @@ void CAdvMapInt::handleSwipeUpdate()
 	}
 	}
 }
 }
 
 
-#endif
-
 void CAdvMapInt::selectionChanged()
 void CAdvMapInt::selectionChanged()
 {
 {
 	const CGTownInstance *to = LOCPLINT->towns[townList.getSelectedIndex()];
 	const CGTownInstance *to = LOCPLINT->towns[townList.getSelectedIndex()];
@@ -1481,7 +1496,8 @@ void CAdvMapInt::setPlayer(PlayerColor Player)
 void CAdvMapInt::startTurn()
 void CAdvMapInt::startTurn()
 {
 {
 	state = INGAME;
 	state = INGAME;
-	if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID)
+	if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID
+		|| settings["session"]["spectate"].Bool())
 	{
 	{
 		adjustActiveness(false);
 		adjustActiveness(false);
 		minimap.setAIRadar(false);
 		minimap.setAIRadar(false);
@@ -1490,6 +1506,9 @@ void CAdvMapInt::startTurn()
 
 
 void CAdvMapInt::endingTurn()
 void CAdvMapInt::endingTurn()
 {
 {
+	if(settings["session"]["spectate"].Bool())
+		return;
+
 	if(LOCPLINT->cingconsole->active)
 	if(LOCPLINT->cingconsole->active)
 		LOCPLINT->cingconsole->deactivate();
 		LOCPLINT->cingconsole->deactivate();
 	LOCPLINT->makingTurn = false;
 	LOCPLINT->makingTurn = false;
@@ -1817,6 +1836,9 @@ const IShipyard * CAdvMapInt::ourInaccessibleShipyard(const CGObjectInstance *ob
 
 
 void CAdvMapInt::aiTurnStarted()
 void CAdvMapInt::aiTurnStarted()
 {
 {
+	if(settings["session"]["spectate"].Bool())
+		return;
+
 	adjustActiveness(true);
 	adjustActiveness(true);
 	CCS->musich->playMusicFromSet("enemy-turn", true);
 	CCS->musich->playMusicFromSet("enemy-turn", true);
 	adventureInt->minimap.setAIRadar(true);
 	adventureInt->minimap.setAIRadar(true);

+ 5 - 8
client/windows/CAdvmapInterface.h

@@ -62,10 +62,10 @@ class CTerrainRect
 	bool isSwiping;
 	bool isSwiping;
 	static constexpr float SwipeTouchSlop = 16.0f;
 	static constexpr float SwipeTouchSlop = 16.0f;
 
 
-	void handleHover(const SDL_MouseMotionEvent &sEvent);
-#ifdef VCMI_ANDROID
-	void handleSwipeMove(const SDL_MouseMotionEvent &sEvent);
-#endif // VCMI_ANDROID
+	void handleHover(const SDL_MouseMotionEvent & sEvent);
+	void handleSwipeMove(const SDL_MouseMotionEvent & sEvent);
+	/// handles start/finish of swipe (press/release of corresponding button); returns true if state change was handled
+	bool handleSwipeStateChange(bool btnPressed);
 public:
 public:
 	int tilesw, tilesh; //width and height of terrain to blit in tiles
 	int tilesw, tilesh; //width and height of terrain to blit in tiles
 	int3 curHoveredTile;
 	int3 curHoveredTile;
@@ -77,6 +77,7 @@ public:
 	void deactivate() override;
 	void deactivate() override;
 	void clickLeft(tribool down, bool previousState) override;
 	void clickLeft(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
 	void clickRight(tribool down, bool previousState) override;
+	void clickMiddle(tribool down, bool previousState) override;
 	void hover(bool on) override;
 	void hover(bool on) override;
 	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
 	void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
 	void show(SDL_Surface * to) override;
 	void show(SDL_Surface * to) override;
@@ -132,11 +133,9 @@ public:
 	enum{LEFT=1, RIGHT=2, UP=4, DOWN=8};
 	enum{LEFT=1, RIGHT=2, UP=4, DOWN=8};
 	ui8 scrollingDir; //uses enum: LEFT RIGHT, UP, DOWN
 	ui8 scrollingDir; //uses enum: LEFT RIGHT, UP, DOWN
 	bool scrollingState;
 	bool scrollingState;
-#ifdef VCMI_ANDROID
 	bool swipeEnabled;
 	bool swipeEnabled;
 	bool swipeMovementRequested;
 	bool swipeMovementRequested;
 	int3 swipeTargetPosition;
 	int3 swipeTargetPosition;
-#endif // !VCMI_ANDROID
 
 
 	enum{NA, INGAME, WAITING} state;
 	enum{NA, INGAME, WAITING} state;
 
 
@@ -260,9 +259,7 @@ public:
 	void changeMode(EAdvMapMode newMode, float newScale = 0.36f);
 	void changeMode(EAdvMapMode newMode, float newScale = 0.36f);
 
 
 	void handleMapScrollingUpdate();
 	void handleMapScrollingUpdate();
-#ifdef VCMI_ANDROID
 	void handleSwipeUpdate();
 	void handleSwipeUpdate();
-#endif
 
 
 };
 };
 
 

+ 2 - 1
client/windows/CCastleInterface.cpp

@@ -754,7 +754,8 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID)
 	}
 	}
 	int price = CGI->arth->artifacts[artifactID]->price;
 	int price = CGI->arth->artifacts[artifactID]->price;
 	bool possible = LOCPLINT->cb->getResourceAmount(Res::GOLD) >= price && !hero->hasArt(artifactID);
 	bool possible = LOCPLINT->cb->getResourceAmount(Res::GOLD) >= price && !hero->hasArt(artifactID);
-	GH.pushInt(new CBlacksmithDialog(possible, CArtHandler::machineIDToCreature(artifactID), artifactID, hero->id));
+	CreatureID cre = artifactID.toArtifact()->warMachine;
+	GH.pushInt(new CBlacksmithDialog(possible, cre, artifactID, hero->id));
 }
 }
 
 
 void CCastleBuildings::enterBuilding(BuildingID building)
 void CCastleBuildings::enterBuilding(BuildingID building)

+ 3 - 3
client/windows/CSpellWindow.cpp

@@ -337,7 +337,7 @@ void CSpellWindow::computeSpellsPerArea()
 	spellsCurSite.reserve(mySpells.size());
 	spellsCurSite.reserve(mySpells.size());
 	for(const CSpell * spell : mySpells)
 	for(const CSpell * spell : mySpells)
 	{
 	{
-		if(spell->combatSpell ^ !battleSpellsOnly
+		if(spell->isCombatSpell() ^ !battleSpellsOnly
 			&& ((selectedTab == 4) || spell->school.at((ESpellSchool)selectedTab))
 			&& ((selectedTab == 4) || spell->school.at((ESpellSchool)selectedTab))
 			)
 			)
 		{
 		{
@@ -547,9 +547,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 		}
 		}
 
 
 		//we will cast a spell
 		//we will cast a spell
-		if(mySpell->combatSpell && owner->myInt->battleInt && owner->myInt->cb->battleCanCastSpell()) //if battle window is open
+		if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open
 		{
 		{
-			ESpellCastProblem::ESpellCastProblem problem = owner->myInt->cb->battleCanCastThisSpell(mySpell);
+			ESpellCastProblem::ESpellCastProblem problem = mySpell->canBeCast(owner->myInt->cb.get(), ECastingMode::HERO_CASTING, owner->myHero);
 			switch (problem)
 			switch (problem)
 			{
 			{
 			case ESpellCastProblem::OK:
 			case ESpellCastProblem::OK:

+ 5 - 1
client/windows/CWindowObject.cpp

@@ -225,9 +225,13 @@ void CWindowObject::setShadow(bool on)
 
 
 void CWindowObject::showAll(SDL_Surface *to)
 void CWindowObject::showAll(SDL_Surface *to)
 {
 {
+	auto color = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1);
+	if(settings["session"]["spectate"].Bool())
+		color = PlayerColor(1); // TODO: Spectator shouldn't need special code for UI colors
+
 	CIntObject::showAll(to);
 	CIntObject::showAll(to);
 	if ((options & BORDERED) && (pos.h != to->h || pos.w != to->w))
 	if ((options & BORDERED) && (pos.h != to->h || pos.w != to->w))
-		CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
+		CMessage::drawBorder(color, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
 }
 
 
 void CWindowObject::close()
 void CWindowObject::close()

+ 1 - 1
client/windows/GUIClasses.cpp

@@ -205,7 +205,7 @@ void CRecruitmentWindow::buy()
 	CreatureID crid =  selected->creature->idNumber;
 	CreatureID crid =  selected->creature->idNumber;
 	SlotID dstslot = dst-> getSlotFor(crid);
 	SlotID dstslot = dst-> getSlotFor(crid);
 
 
-	if(!dstslot.validSlot() && !vstd::contains(CGI->arth->bigArtifacts,CGI->arth->creatureToMachineID(crid))) //no available slot
+	if(!dstslot.validSlot() && (selected->creature->warMachine == ArtifactID::NONE)) //no available slot
 	{
 	{
 		std::string txt;
 		std::string txt;
 		if(dst->ID == Obj::HERO)
 		if(dst->ID == Obj::HERO)

+ 2 - 0
client/windows/InfoWindows.cpp

@@ -335,6 +335,8 @@ void CRClickPopup::close()
 void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps)
 void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps)
 {
 {
 	PlayerColor player = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); //if no player, then use blue
 	PlayerColor player = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); //if no player, then use blue
+	if(settings["session"]["spectate"].Bool())//TODO: there must be better way to implement this
+		player = PlayerColor(1);
 
 
 	CSimpleWindow * temp = new CInfoWindow(txt, player, comps);
 	CSimpleWindow * temp = new CInfoWindow(txt, player, comps);
 	temp->center(Point(GH.current->motion)); //center on mouse
 	temp->center(Point(GH.current->motion)); //center on mouse

+ 9 - 5
config/artifacts.json

@@ -17,22 +17,26 @@
 	"catapult":
 	"catapult":
 	{
 	{
 		"index" : 3,
 		"index" : 3,
-		"type" : ["HERO"]
+		"type" : ["HERO"],
+		"warMachine" : "catapult"
 	},
 	},
 	"ballista":
 	"ballista":
 	{
 	{
 		"index" : 4,
 		"index" : 4,
-		"type" : ["HERO"]
+		"type" : ["HERO"],
+		"warMachine" : "ballista"
 	},
 	},
 	"ammoCart":
 	"ammoCart":
 	{
 	{
 		"index" : 5,
 		"index" : 5,
-		"type" : ["HERO"]
+		"type" : ["HERO"],
+		"warMachine" : "ammoCart"
 	},
 	},
 	"firstAidTent":
 	"firstAidTent":
 	{
 	{
 		"index" : 6,
 		"index" : 6,
-		"type" : ["HERO"]
+		"type" : ["HERO"],
+		"warMachine" : "firstAidTent"
 	},
 	},
 	"centaurAxe":
 	"centaurAxe":
 	{
 	{
@@ -1333,7 +1337,7 @@
 				"subtype" : 1,
 				"subtype" : 1,
 				"val" : 0,
 				"val" : 0,
 				"valueType" : "BASE_NUMBER"
 				"valueType" : "BASE_NUMBER"
-			}			
+			}
 		],
 		],
 		"index" : 93,
 		"index" : 93,
 		"type" : ["HERO"]
 		"type" : ["HERO"]

+ 7 - 1
config/schemas/artifact.json

@@ -4,7 +4,7 @@
 	"title" : "VCMI artifact format",
 	"title" : "VCMI artifact format",
 	"description" : "Format used to define new artifacts in VCMI",
 	"description" : "Format used to define new artifacts in VCMI",
 	"required" : [ "class", "text", "type", "value" ],
 	"required" : [ "class", "text", "type", "value" ],
-	
+
 	"definitions" : {
 	"definitions" : {
 		"growingBonusList" : {
 		"growingBonusList" : {
 			"type" : "array",
 			"type" : "array",
@@ -117,6 +117,12 @@
 		"value": {
 		"value": {
 			"type":"number",
 			"type":"number",
 			"description": "Cost of this artifact, in gold"
 			"description": "Cost of this artifact, in gold"
+		},
+		"warMachine":
+		{
+			"type":"string",
+			"description": "Creature id to use on battle field. If set, this artifact is war machine"
 		}
 		}
+
 	}
 	}
 }
 }

+ 8 - 4
config/schemas/settings.json

@@ -5,8 +5,8 @@
 	"$schema": "http://json-schema.org/draft-04/schema",
 	"$schema": "http://json-schema.org/draft-04/schema",
 	"required" : [ "general", "video", "adventure", "pathfinder", "battle", "server", "logging", "launcher" ],
 	"required" : [ "general", "video", "adventure", "pathfinder", "battle", "server", "logging", "launcher" ],
 	"definitions" : {
 	"definitions" : {
-		"logLevelEnum" : { 
-			"type" : "string", 
+		"logLevelEnum" : {
+			"type" : "string",
 			"enum" : [ "trace", "debug", "info", "warn", "error" ]
 			"enum" : [ "trace", "debug", "info", "warn", "error" ]
 		}
 		}
 	},
 	},
@@ -17,7 +17,7 @@
 			"type" : "object",
 			"type" : "object",
 			"default": {},
 			"default": {},
 			"additionalProperties" : false,
 			"additionalProperties" : false,
-			"required" : [ "playerName", "showfps", "music", "sound", "encoding", "swipe" ],
+			"required" : [ "playerName", "showfps", "music", "sound", "encoding", "swipe", "saveRandomMaps" ],
 			"properties" : {
 			"properties" : {
 				"playerName" : {
 				"playerName" : {
 					"type":"string",
 					"type":"string",
@@ -41,8 +41,12 @@
 				},
 				},
 				"swipe" : {
 				"swipe" : {
 					"type" : "boolean",
 					"type" : "boolean",
-					"default" : false
+					"default" : true
 				},
 				},
+				"saveRandomMaps" : {
+					"type" : "boolean",
+					"default" : false
+				}
 			}
 			}
 		},
 		},
 		"video" : {
 		"video" : {

+ 1 - 1
launcher/settingsView/csettingsview_moc.cpp

@@ -55,7 +55,7 @@ void CSettingsView::loadSettings()
 	ui->comboBoxEnemyAI->setCurrentIndex(enemyAIIndex);
 	ui->comboBoxEnemyAI->setCurrentIndex(enemyAIIndex);
 	ui->comboBoxPlayerAI->setCurrentIndex(playerAIIndex);
 	ui->comboBoxPlayerAI->setCurrentIndex(playerAIIndex);
 
 
-	ui->spinBoxNetworkPort->setValue(settings["server"]["port"].Float());
+	ui->spinBoxNetworkPort->setValue(settings["server"]["port"].Integer());
 
 
 	ui->comboBoxAutoCheck->setCurrentIndex(settings["launcher"]["autoCheckRepositories"].Bool());
 	ui->comboBoxAutoCheck->setCurrentIndex(settings["launcher"]["autoCheckRepositories"].Bool());
 	// all calls to plainText will trigger textChanged() signal overwriting config. Create backup before editing widget
 	// all calls to plainText will trigger textChanged() signal overwriting config. Create backup before editing widget

+ 26 - 12
lib/BattleInfo.cpp

@@ -432,22 +432,36 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 	if(!creatureBank)
 	if(!creatureBank)
 	{
 	{
 		//Checks if hero has artifact and create appropriate stack
 		//Checks if hero has artifact and create appropriate stack
-		auto handleWarMachine= [&](int side, ArtifactPosition artslot, CreatureID cretype, BattleHex hex)
+		auto handleWarMachine= [&](int side, ArtifactPosition artslot, BattleHex hex)
 		{
 		{
-			if(heroes[side] && heroes[side]->getArt(artslot))
-				stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cretype, 1), !side, SlotID::WAR_MACHINES_SLOT, hex));
+			const CArtifactInstance * warMachineArt = heroes[side]->getArt(artslot);
+
+			if(nullptr != warMachineArt)
+			{
+				CreatureID cre = warMachineArt->artType->warMachine;
+
+				if(cre != CreatureID::NONE)
+					stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cre, 1), !side, SlotID::WAR_MACHINES_SLOT, hex));
+			}
 		};
 		};
 
 
-		handleWarMachine(0, ArtifactPosition::MACH1, CreatureID::BALLISTA, 52);
-		handleWarMachine(0, ArtifactPosition::MACH2, CreatureID::AMMO_CART, 18);
-		handleWarMachine(0, ArtifactPosition::MACH3, CreatureID::FIRST_AID_TENT, 154);
-		if(town && town->hasFort())
-			handleWarMachine(0, ArtifactPosition::MACH4, CreatureID::CATAPULT, 120);
+		if(heroes[0])
+		{
 
 
-		if(!town) //defending hero shouldn't receive ballista (bug #551)
-			handleWarMachine(1, ArtifactPosition::MACH1, CreatureID::BALLISTA, 66);
-		handleWarMachine(1, ArtifactPosition::MACH2, CreatureID::AMMO_CART, 32);
-		handleWarMachine(1, ArtifactPosition::MACH3, CreatureID::FIRST_AID_TENT, 168);
+			handleWarMachine(0, ArtifactPosition::MACH1, 52);
+			handleWarMachine(0, ArtifactPosition::MACH2, 18);
+			handleWarMachine(0, ArtifactPosition::MACH3, 154);
+			if(town && town->hasFort())
+				handleWarMachine(0, ArtifactPosition::MACH4, 120);
+		}
+
+		if(heroes[1])
+		{
+			if(!town) //defending hero shouldn't receive ballista (bug #551)
+				handleWarMachine(1, ArtifactPosition::MACH1, 66);
+			handleWarMachine(1, ArtifactPosition::MACH2, 32);
+			handleWarMachine(1, ArtifactPosition::MACH3, 168);
+		}
 	}
 	}
 	//war machines added
 	//war machines added
 
 

+ 45 - 66
lib/CArtHandler.cpp

@@ -15,6 +15,7 @@
 #include "CGeneralTextHandler.h"
 #include "CGeneralTextHandler.h"
 #include "VCMI_Lib.h"
 #include "VCMI_Lib.h"
 #include "CModHandler.h"
 #include "CModHandler.h"
+#include "CCreatureHandler.h"
 #include "spells/CSpellHandler.h"
 #include "spells/CSpellHandler.h"
 #include "mapObjects/MapObjects.h"
 #include "mapObjects/MapObjects.h"
 #include "NetPacksBase.h"
 #include "NetPacksBase.h"
@@ -61,14 +62,21 @@ const std::string & CArtifact::EventText() const
 	return eventText;
 	return eventText;
 }
 }
 
 
-bool CArtifact::isBig () const
+bool CArtifact::isBig() const
 {
 {
-	return VLC->arth->isBigArtifact(id);
+	return warMachine != CreatureID::NONE;
 }
 }
 
 
-bool CArtifact::isTradable () const
+bool CArtifact::isTradable() const
 {
 {
-	return VLC->arth->isTradableArtifact(id);
+	switch(id)
+	{
+	case ArtifactID::SPELLBOOK:
+	case ArtifactID::GRAIL:
+		return false;
+	default:
+		return !isBig();
+	}
 }
 }
 
 
 CArtifact::CArtifact()
 CArtifact::CArtifact()
@@ -120,6 +128,26 @@ void CArtifact::addNewBonus(const std::shared_ptr<Bonus>& b)
 	CBonusSystemNode::addNewBonus(b);
 	CBonusSystemNode::addNewBonus(b);
 }
 }
 
 
+void CArtifact::fillWarMachine()
+{
+	switch (id)
+	{
+	case ArtifactID::CATAPULT:
+		warMachine = CreatureID::CATAPULT;
+		break;
+	case ArtifactID::BALLISTA:
+		warMachine = CreatureID::BALLISTA;
+		break;
+	case ArtifactID::FIRST_AID_TENT:
+		warMachine = CreatureID::FIRST_AID_TENT;
+		break;
+	case ArtifactID::AMMO_CART:
+		warMachine = CreatureID::AMMO_CART;
+		break;
+	}
+	warMachine = CreatureID::NONE; //this artifact is not a creature
+}
+
 void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art)
 void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art)
 {
 {
 	auto b = std::make_shared<Bonus>();
 	auto b = std::make_shared<Bonus>();
@@ -146,11 +174,6 @@ void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art)
 
 
 CArtHandler::CArtHandler()
 CArtHandler::CArtHandler()
 {
 {
-	//VLC->arth = this;
-
-	// War machines are the default big artifacts.
-	for (ArtifactID i = ArtifactID::CATAPULT; i <= ArtifactID::FIRST_AID_TENT; i.advance(1))
-		bigArtifacts.insert(i);
 }
 }
 
 
 CArtHandler::~CArtHandler()
 CArtHandler::~CArtHandler()
@@ -310,6 +333,19 @@ CArtifact * CArtHandler::loadFromJson(const JsonNode & node, const std::string &
 		auto bonus = JsonUtils::parseBonus(b);
 		auto bonus = JsonUtils::parseBonus(b);
 		art->addNewBonus(bonus);
 		art->addNewBonus(bonus);
 	}
 	}
+
+	const JsonNode & warMachine = node["warMachine"];
+	if(warMachine.getType() == JsonNode::DATA_STRING && warMachine.String() != "")
+	{
+		VLC->modh->identifiers.requestIdentifier("creature", warMachine, [=](si32 id)
+		{
+			art->warMachine = CreatureID(id);
+
+			//this assumes that creature object is stored before registration
+			VLC->creh->creatures.at(id)->warMachine = art->id;
+		});
+	}
+
 	return art;
 	return art;
 }
 }
 
 
@@ -453,47 +489,6 @@ void CArtHandler::loadGrowingArt(CGrowingArtifact * art, const JsonNode & node)
 	}
 	}
 }
 }
 
 
-//TODO: use bimap
-ArtifactID CArtHandler::creatureToMachineID(CreatureID id)
-{
-	switch (id)
-	{
-	case CreatureID::CATAPULT: //Catapult
-		return ArtifactID::CATAPULT;
-		break;
-	case CreatureID::BALLISTA: //Ballista
-		return ArtifactID::BALLISTA;
-		break;
-	case CreatureID::FIRST_AID_TENT: //First Aid tent
-		return ArtifactID::FIRST_AID_TENT;
-		break;
-	case CreatureID::AMMO_CART: //Ammo cart
-		return ArtifactID::AMMO_CART;
-		break;
-	}
-	return ArtifactID::NONE; //this creature is not artifact
-}
-
-CreatureID CArtHandler::machineIDToCreature(ArtifactID id)
-{
-	switch (id)
-	{
-	case ArtifactID::CATAPULT:
-		return CreatureID::CATAPULT;
-		break;
-	case ArtifactID::BALLISTA:
-		return CreatureID::BALLISTA;
-		break;
-	case ArtifactID::FIRST_AID_TENT:
-		return CreatureID::FIRST_AID_TENT;
-		break;
-	case ArtifactID::AMMO_CART:
-		return CreatureID::AMMO_CART;
-		break;
-	}
-	return CreatureID::NONE; //this artifact is not a creature
-}
-
 ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts)
 ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts)
 {
 {
 	auto getAllowedArts = [&](std::vector<ConstTransitivePtr<CArtifact> > &out, std::vector<CArtifact*> *arts, CArtifact::EartClass flag)
 	auto getAllowedArts = [&](std::vector<ConstTransitivePtr<CArtifact> > &out, std::vector<CArtifact*> *arts, CArtifact::EartClass flag)
@@ -635,22 +630,6 @@ bool CArtHandler::legalArtifact(ArtifactID id)
 		art->aClass <= CArtifact::ART_RELIC);
 		art->aClass <= CArtifact::ART_RELIC);
 }
 }
 
 
-bool CArtHandler::isTradableArtifact(ArtifactID id) const
-{
-	switch (id)
-	{
-	case ArtifactID::SPELLBOOK:
-	case ArtifactID::GRAIL:
-	case ArtifactID::CATAPULT:
-	case ArtifactID::BALLISTA:
-	case ArtifactID::AMMO_CART:
-	case ArtifactID::FIRST_AID_TENT:
-		return false;
-	default:
-		return true;
-	}
-}
-
 void CArtHandler::initAllowedArtifactsList(const std::vector<bool> &allowed)
 void CArtHandler::initAllowedArtifactsList(const std::vector<bool> &allowed)
 {
 {
 	allowedArtifacts.clear();
 	allowedArtifacts.clear();

+ 12 - 8
lib/CArtHandler.h

@@ -61,6 +61,7 @@ public:
 	std::vector<CArtifact *> constituentOf; // Reverse map of constituents - combined arts that include this art
 	std::vector<CArtifact *> constituentOf; // Reverse map of constituents - combined arts that include this art
 	EartClass aClass;
 	EartClass aClass;
 	ArtifactID id;
 	ArtifactID id;
+	CreatureID warMachine;
 
 
 	const std::string &Name() const; //getter
 	const std::string &Name() const; //getter
 	const std::string &Description() const; //getter
 	const std::string &Description() const; //getter
@@ -84,12 +85,23 @@ public:
 		{
 		{
 			h & identifier;
 			h & identifier;
 		}
 		}
+
+		if(version >= 771)
+		{
+			h & warMachine;
+		}
+		else if(!h.saving)
+		{
+			fillWarMachine();
+		}
 	}
 	}
 
 
 	CArtifact();
 	CArtifact();
 	~CArtifact();
 	~CArtifact();
 
 
 	friend class CArtHandler;
 	friend class CArtHandler;
+private:
+	void fillWarMachine();
 };
 };
 
 
 class DLL_LINKAGE CGrowingArtifact : public CArtifact //for example commander artifacts getting bonuses after battle
 class DLL_LINKAGE CGrowingArtifact : public CArtifact //for example commander artifacts getting bonuses after battle
@@ -213,7 +225,6 @@ public:
 
 
 	std::vector< ConstTransitivePtr<CArtifact> > artifacts;
 	std::vector< ConstTransitivePtr<CArtifact> > artifacts;
 	std::vector<CArtifact *> allowedArtifacts;
 	std::vector<CArtifact *> allowedArtifacts;
-	std::set<ArtifactID> bigArtifacts; // Artifacts that cannot be moved to backpack, e.g. war machines.
 	std::set<ArtifactID> growingArtifacts;
 	std::set<ArtifactID> growingArtifacts;
 
 
 	void addBonuses(CArtifact *art, const JsonNode &bonusList);
 	void addBonuses(CArtifact *art, const JsonNode &bonusList);
@@ -231,13 +242,7 @@ public:
 	ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts);
 	ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts);
 
 
 	bool legalArtifact(ArtifactID id);
 	bool legalArtifact(ArtifactID id);
-	//void getAllowedArts(std::vector<ConstTransitivePtr<CArtifact> > &out, std::vector<CArtifact*> *arts, int flag);
-	//void getAllowed(std::vector<ConstTransitivePtr<CArtifact> > &out, int flags);
-	bool isBigArtifact (ArtifactID artID) const {return bigArtifacts.find(artID) != bigArtifacts.end();}
-	bool isTradableArtifact (ArtifactID id) const;
 	void initAllowedArtifactsList(const std::vector<bool> &allowed); //allowed[art_id] -> 0 if not allowed, 1 if allowed
 	void initAllowedArtifactsList(const std::vector<bool> &allowed); //allowed[art_id] -> 0 if not allowed, 1 if allowed
-	static ArtifactID creatureToMachineID(CreatureID id);
-	static CreatureID machineIDToCreature(ArtifactID id);
 	void makeItCreatureArt (CArtifact * a, bool onlyCreature = true);
 	void makeItCreatureArt (CArtifact * a, bool onlyCreature = true);
 	void makeItCreatureArt (ArtifactID aid, bool onlyCreature = true);
 	void makeItCreatureArt (ArtifactID aid, bool onlyCreature = true);
 	void makeItCommanderArt (CArtifact * a, bool onlyCommander = true);
 	void makeItCommanderArt (CArtifact * a, bool onlyCommander = true);
@@ -264,7 +269,6 @@ public:
 	{
 	{
 		h & artifacts & allowedArtifacts & treasures & minors & majors & relics
 		h & artifacts & allowedArtifacts & treasures & minors & majors & relics
 			& growingArtifacts;
 			& growingArtifacts;
-		//if(!h.saving) sortArts();
 	}
 	}
 
 
 private:
 private:

+ 2 - 103
lib/CBattleCallback.cpp

@@ -237,7 +237,7 @@ const CGTownInstance * CBattleInfoEssentials::battleGetDefendedTown() const
 BattlePerspective::BattlePerspective CBattleInfoEssentials::battleGetMySide() const
 BattlePerspective::BattlePerspective CBattleInfoEssentials::battleGetMySide() const
 {
 {
 	RETURN_IF_NOT_BATTLE(BattlePerspective::INVALID);
 	RETURN_IF_NOT_BATTLE(BattlePerspective::INVALID);
-	if(!player)
+	if(!player || player.get().isSpectator())
 		return BattlePerspective::ALL_KNOWING;
 		return BattlePerspective::ALL_KNOWING;
 	if(*player == getBattle()->sides[0].color)
 	if(*player == getBattle()->sides[0].color)
 		return BattlePerspective::LEFT_SIDE;
 		return BattlePerspective::LEFT_SIDE;
@@ -1708,59 +1708,6 @@ std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
 	return attackableBattleHexes;
 	return attackableBattleHexes;
 }
 }
 
 
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const
-{
-	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-	if(caster == nullptr)
-	{
-		logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpell: no spellcaster.";
-		return ESpellCastProblem::INVALID;
-	}
-	const PlayerColor player = caster->getOwner();
-	const si8 side = playerToSide(player);
-
-	if(side < 0)
-		return ESpellCastProblem::INVALID;
-
-	if(!battleDoWeKnowAbout(side))
-		return ESpellCastProblem::INVALID;
-
-	ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(caster, mode);
-	if(genProblem != ESpellCastProblem::OK)
-		return genProblem;
-
-	switch(mode)
-	{
-	case ECastingMode::HERO_CASTING:
-		{
-			const CGHeroInstance * castingHero = dynamic_cast<const CGHeroInstance *>(caster);//todo: unify hero|creature spell cost
-			if(!castingHero)
-			{
-				logGlobal->error("battleCanCastThisSpell: invalid caster");
-				return ESpellCastProblem::INVALID;
-			}
-
-			if(!castingHero->getArt(ArtifactPosition::SPELLBOOK))
-				return ESpellCastProblem::NO_SPELLBOOK;
-			if(!castingHero->canCastThisSpell(spell))
-				return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL;
-			if(castingHero->mana < battleGetSpellCost(spell, castingHero)) //not enough mana
-				return ESpellCastProblem::NOT_ENOUGH_MANA;
-		}
-		break;
-	}
-
-	if(!spell->combatSpell)
-		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
-
-	//effect like Recanter's Cloak. Blocks also passive casting.
-	//TODO: check creature abilities to block
-	if(battleMaxSpellLevel(side) < spell->level)
-		return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
-
-	return spell->canBeCast(this, mode, caster);
-}
-
 ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
 ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
 {
 {
 	RETURN_IF_NOT_BATTLE(-1);
 	RETURN_IF_NOT_BATTLE(-1);
@@ -1789,22 +1736,6 @@ ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInst
 	return ret - manaReduction + manaIncrease;
 	return ret - manaReduction + manaIncrease;
 }
 }
 
 
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const
-{
-	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-	if(caster == nullptr)
-	{
-		logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpellHere: no spellcaster.";
-		return ESpellCastProblem::INVALID;
-	}
-
-	ESpellCastProblem::ESpellCastProblem problem = battleCanCastThisSpell(caster, spell, mode);
-	if(problem != ESpellCastProblem::OK)
-		return problem;
-
-	return spell->canBeCastAt(this, caster, mode, dest);
-}
-
 const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
 const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
 {
 {
 	RETURN_IF_NOT_BATTLE(nullptr);
 	RETURN_IF_NOT_BATTLE(nullptr);
@@ -1887,7 +1818,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
 
 
 		if(subject->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
 		if(subject->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
 			//TODO: this ability has special limitations
 			//TODO: this ability has special limitations
-			|| battleCanCastThisSpellHere(subject, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
+			|| spellID.toSpell()->canBeCastAt(this, subject, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
 			continue;
 			continue;
 
 
 		switch (spellID)
 		switch (spellID)
@@ -2127,18 +2058,6 @@ ReachabilityInfo::Parameters::Parameters(const CStack *Stack)
 	knownAccessible = stack->getHexes();
 	knownAccessible = stack->getHexes();
 }
 }
 
 
-ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell) const
-{
-	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-	ASSERT_IF_CALLED_WITH_PLAYER
-
-	const ISpellCaster * hero = battleGetMyHero();
-	if(hero == nullptr)
-		return ESpellCastProblem::INVALID;
-	else
-		return CBattleInfoCallback::battleCanCastThisSpell(hero, spell, ECastingMode::HERO_CASTING);
-}
-
 bool CPlayerBattleCallback::battleCanFlee() const
 bool CPlayerBattleCallback::battleCanFlee() const
 {
 {
 	RETURN_IF_NOT_BATTLE(false);
 	RETURN_IF_NOT_BATTLE(false);
@@ -2169,26 +2088,6 @@ int CPlayerBattleCallback::battleGetSurrenderCost() const
 	return CBattleInfoCallback::battleGetSurrenderCost(*player);
 	return CBattleInfoCallback::battleGetSurrenderCost(*player);
 }
 }
 
 
-bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem /*= nullptr*/) const
-{
-	RETURN_IF_NOT_BATTLE(false);
-	ASSERT_IF_CALLED_WITH_PLAYER
-
-	const CGHeroInstance * hero = battleGetMyHero();
-	if(!hero)
-	{
-		if(outProblem)
-			*outProblem = ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
-		return false;
-	}
-
-	auto problem = CBattleInfoCallback::battleCanCastSpell(hero, ECastingMode::HERO_CASTING);
-	if(outProblem)
-		*outProblem = problem;
-
-	return problem == ESpellCastProblem::OK;
-}
-
 const CGHeroInstance * CPlayerBattleCallback::battleGetMyHero() const
 const CGHeroInstance * CPlayerBattleCallback::battleGetMyHero() const
 {
 {
 	return CBattleInfoEssentials::battleGetFightingHero(battleGetMySide());
 	return CBattleInfoEssentials::battleGetFightingHero(battleGetMySide());

+ 0 - 4
lib/CBattleCallback.h

@@ -293,8 +293,6 @@ public:
 	si8 battleMaxSpellLevel(ui8 side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
 	si8 battleMaxSpellLevel(ui8 side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
 	ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
 	ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
 	ESpellCastProblem::ESpellCastProblem battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
 	ESpellCastProblem::ESpellCastProblem battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
 
 
 	SpellID battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const;
 	SpellID battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const;
 	SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;
 	SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;
@@ -336,11 +334,9 @@ class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback
 public:
 public:
 	bool battleCanFlee() const; //returns true if caller can flee from the battle
 	bool battleCanFlee() const; //returns true if caller can flee from the battle
 	TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield
 	TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be cast (and returns problem description)
 
 
 	int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
 	int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
 
 
-	bool battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem = nullptr) const; //returns true, if caller can cast a spell. If not, if pointer is given via arg, the reason will be written.
 	const CGHeroInstance * battleGetMyHero() const;
 	const CGHeroInstance * battleGetMyHero() const;
 	InfoAboutHero battleGetEnemyHero() const;
 	InfoAboutHero battleGetEnemyHero() const;
 };
 };

+ 20 - 0
lib/CCreatureHandler.cpp

@@ -152,6 +152,26 @@ void CCreature::setId(CreatureID ID)
 	CBonusSystemNode::treeHasChanged();
 	CBonusSystemNode::treeHasChanged();
 }
 }
 
 
+void CCreature::fillWarMachine()
+{
+	switch (idNumber)
+	{
+	case CreatureID::CATAPULT: //Catapult
+		warMachine = ArtifactID::CATAPULT;
+		break;
+	case CreatureID::BALLISTA: //Ballista
+		warMachine = ArtifactID::BALLISTA;
+		break;
+	case CreatureID::FIRST_AID_TENT: //First Aid tent
+		warMachine = ArtifactID::FIRST_AID_TENT;
+		break;
+	case CreatureID::AMMO_CART: //Ammo cart
+		warMachine = ArtifactID::AMMO_CART;
+		break;
+	}
+	warMachine = ArtifactID::NONE; //this creature is not artifact
+}
+
 static void AddAbility(CCreature *cre, const JsonVector &ability_vec)
 static void AddAbility(CCreature *cre, const JsonVector &ability_vec)
 {
 {
 	auto nsf = std::make_shared<Bonus>();
 	auto nsf = std::make_shared<Bonus>();

+ 13 - 0
lib/CCreatureHandler.h

@@ -98,6 +98,8 @@ public:
 		}
 		}
 	} sounds;
 	} sounds;
 
 
+	ArtifactID warMachine;
+
 	bool isItNativeTerrain(int terrain) const;
 	bool isItNativeTerrain(int terrain) const;
 	bool isDoubleWide() const; //returns true if unit is double wide on battlefield
 	bool isDoubleWide() const; //returns true if unit is double wide on battlefield
 	bool isFlying() const; //returns true if it is a flying unit
 	bool isFlying() const; //returns true if it is a flying unit
@@ -142,9 +144,20 @@ public:
 		{
 		{
 			h & identifier;
 			h & identifier;
 		}
 		}
+		if(version >= 771)
+		{
+			h & warMachine;
+		}
+		else if(!h.saving)
+		{
+			fillWarMachine();
+		}
 	}
 	}
 
 
 	CCreature();
 	CCreature();
+
+private:
+	void fillWarMachine();
 };
 };
 
 
 class DLL_LINKAGE CCreatureHandler : public IHandlerBase
 class DLL_LINKAGE CCreatureHandler : public IHandlerBase

+ 1 - 1
lib/CGameInfoCallback.cpp

@@ -595,7 +595,7 @@ const CMapHeader * CGameInfoCallback::getMapHeader() const
 
 
 bool CGameInfoCallback::hasAccess(boost::optional<PlayerColor> playerId) const
 bool CGameInfoCallback::hasAccess(boost::optional<PlayerColor> playerId) const
 {
 {
-	return !player || gs->getPlayerRelations( *playerId, *player ) != PlayerRelations::ENEMIES;
+	return !player || player.get().isSpectator() || gs->getPlayerRelations( *playerId, *player ) != PlayerRelations::ENEMIES;
 }
 }
 
 
 EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bool verbose) const
 EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bool verbose) const

+ 40 - 6
lib/CGameState.cpp

@@ -16,7 +16,6 @@
 #include "StartInfo.h"
 #include "StartInfo.h"
 #include "NetPacks.h"
 #include "NetPacks.h"
 #include "registerTypes/RegisterTypes.h"
 #include "registerTypes/RegisterTypes.h"
-#include "mapping/CMapInfo.h"
 #include "BattleInfo.h"
 #include "BattleInfo.h"
 #include "JsonNode.h"
 #include "JsonNode.h"
 #include "filesystem/Filesystem.h"
 #include "filesystem/Filesystem.h"
@@ -24,8 +23,10 @@
 #include "rmg/CMapGenerator.h"
 #include "rmg/CMapGenerator.h"
 #include "CStopWatch.h"
 #include "CStopWatch.h"
 #include "mapping/CMapEditManager.h"
 #include "mapping/CMapEditManager.h"
+#include "mapping/CMapService.h"
 #include "serializer/CTypeList.h"
 #include "serializer/CTypeList.h"
 #include "serializer/CMemorySerializer.h"
 #include "serializer/CMemorySerializer.h"
+#include "VCMIDirs.h"
 
 
 #ifdef min
 #ifdef min
 #undef min
 #undef min
@@ -698,7 +699,7 @@ CGameState::~CGameState()
 		ptr.second.dellNull();
 		ptr.second.dellNull();
 }
 }
 
 
-void CGameState::init(StartInfo * si)
+void CGameState::init(StartInfo * si, bool allowSavingRandomMap)
 {
 {
 	logGlobal->infoStream() << "\tUsing random seed: "<< si->seedToBeUsed;
 	logGlobal->infoStream() << "\tUsing random seed: "<< si->seedToBeUsed;
 	getRandomGenerator().setSeed(si->seedToBeUsed);
 	getRandomGenerator().setSeed(si->seedToBeUsed);
@@ -709,7 +710,7 @@ void CGameState::init(StartInfo * si)
 	switch(scenarioOps->mode)
 	switch(scenarioOps->mode)
 	{
 	{
 	case StartInfo::NEW_GAME:
 	case StartInfo::NEW_GAME:
-		initNewGame();
+		initNewGame(allowSavingRandomMap);
 		break;
 		break;
 	case StartInfo::CAMPAIGN:
 	case StartInfo::CAMPAIGN:
 		initCampaign();
 		initCampaign();
@@ -771,7 +772,7 @@ void CGameState::init(StartInfo * si)
 	}
 	}
 }
 }
 
 
-void CGameState::initNewGame()
+void CGameState::initNewGame(bool allowSavingRandomMap)
 {
 {
 	if(scenarioOps->createRandomMap())
 	if(scenarioOps->createRandomMap())
 	{
 	{
@@ -780,8 +781,37 @@ void CGameState::initNewGame()
 
 
 		// Gen map
 		// Gen map
 		CMapGenerator mapGenerator;
 		CMapGenerator mapGenerator;
-		map = mapGenerator.generate(scenarioOps->mapGenOptions.get(), scenarioOps->seedToBeUsed).release();
 
 
+		std::unique_ptr<CMap> randomMap = mapGenerator.generate(scenarioOps->mapGenOptions.get(), scenarioOps->seedToBeUsed);
+
+		if(allowSavingRandomMap)
+		{
+			try
+			{
+				auto path = VCMIDirs::get().userCachePath() / "RandomMaps";
+				boost::filesystem::create_directories(path);
+
+				std::shared_ptr<CMapGenOptions> options = scenarioOps->mapGenOptions;
+
+				const std::string templateName = options->getMapTemplate()->getName();
+				const ui32 seed = scenarioOps->seedToBeUsed;
+
+				const std::string fileName = boost::str(boost::format("%s_%d.vmap") % templateName % seed );
+				const auto fullPath = path / fileName;
+
+				CMapService::saveMap(randomMap, fullPath);
+
+				logGlobal->info("Random map has been saved to:");
+				logGlobal->info(fullPath.string());
+			}
+			catch(...)
+			{
+				logGlobal->error("Saving random map failed with exception");
+				handleException();
+			}
+		}
+
+		map = randomMap.release();
 		// Update starting options
 		// Update starting options
 		for(int i = 0; i < map->players.size(); ++i)
 		for(int i = 0; i < map->players.size(); ++i)
 		{
 		{
@@ -809,7 +839,8 @@ void CGameState::initNewGame()
 	else
 	else
 	{
 	{
 		logGlobal->infoStream() << "Open map file: " << scenarioOps->mapname;
 		logGlobal->infoStream() << "Open map file: " << scenarioOps->mapname;
-		map = CMapService::loadMap(scenarioOps->mapname).release();
+		const ResourceID mapURI(scenarioOps->mapname, EResType::MAP);
+		map = CMapService::loadMap(mapURI).release();
 	}
 	}
 }
 }
 
 
@@ -2177,6 +2208,9 @@ bool CGameState::isVisible(int3 pos, PlayerColor player)
 {
 {
 	if(player == PlayerColor::NEUTRAL)
 	if(player == PlayerColor::NEUTRAL)
 		return false;
 		return false;
+	if(player.isSpectator())
+		return true;
+
 	return getPlayerTeam(player)->fogOfWarMap[pos.x][pos.y][pos.z];
 	return getPlayerTeam(player)->fogOfWarMap[pos.x][pos.y][pos.z];
 }
 }
 
 

+ 2 - 2
lib/CGameState.h

@@ -201,7 +201,7 @@ public:
 	CGameState();
 	CGameState();
 	virtual ~CGameState();
 	virtual ~CGameState();
 
 
-	void init(StartInfo * si);
+	void init(StartInfo * si, bool allowSavingRandomMap = false);
 
 
 	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
@@ -283,7 +283,7 @@ private:
 
 
 	// ----- initialization -----
 	// ----- initialization -----
 
 
-	void initNewGame();
+	void initNewGame(bool allowSavingRandomMap);
 	void initCampaign();
 	void initCampaign();
 	void initDuel();
 	void initDuel();
 	void checkMapChecksum();
 	void checkMapChecksum();

+ 1 - 1
lib/CTownHandler.cpp

@@ -584,7 +584,7 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source)
 	VLC->modh->identifiers.requestIdentifier("creature", source["warMachine"],
 	VLC->modh->identifiers.requestIdentifier("creature", source["warMachine"],
 	[&town](si32 creature)
 	[&town](si32 creature)
 	{
 	{
-		town.warMachine = CArtHandler::creatureToMachineID(CreatureID(creature));
+		town.warMachine = CreatureID(creature).toCreature()->warMachine;
 	});
 	});
 
 
 	town.moatDamage = source["moatDamage"].Float();
 	town.moatDamage = source["moatDamage"].Float();

+ 6 - 0
lib/GameConstants.cpp

@@ -29,6 +29,7 @@ const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);
 const SlotID SlotID::WAR_MACHINES_SLOT = SlotID(-4);
 const SlotID SlotID::WAR_MACHINES_SLOT = SlotID(-4);
 const SlotID SlotID::ARROW_TOWERS_SLOT = SlotID(-5);
 const SlotID SlotID::ARROW_TOWERS_SLOT = SlotID(-5);
 
 
+const PlayerColor PlayerColor::SPECTATOR = PlayerColor(252);
 const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253);
 const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253);
 const PlayerColor PlayerColor::UNFLAGGABLE = PlayerColor(254);
 const PlayerColor PlayerColor::UNFLAGGABLE = PlayerColor(254);
 const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255);
 const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255);
@@ -72,6 +73,11 @@ bool PlayerColor::isValidPlayer() const
 	return num < PLAYER_LIMIT_I;
 	return num < PLAYER_LIMIT_I;
 }
 }
 
 
+bool PlayerColor::isSpectator() const
+{
+	return num == 252;
+}
+
 std::string PlayerColor::getStr(bool L10n) const
 std::string PlayerColor::getStr(bool L10n) const
 {
 {
 	std::string ret = "unnamed";
 	std::string ret = "unnamed";

+ 2 - 0
lib/GameConstants.h

@@ -257,12 +257,14 @@ class PlayerColor : public BaseForID<PlayerColor, ui8>
 		PLAYER_LIMIT_I = 8
 		PLAYER_LIMIT_I = 8
 	};
 	};
 
 
+	DLL_LINKAGE static const PlayerColor SPECTATOR; //252
 	DLL_LINKAGE static const PlayerColor CANNOT_DETERMINE; //253
 	DLL_LINKAGE static const PlayerColor CANNOT_DETERMINE; //253
 	DLL_LINKAGE static const PlayerColor UNFLAGGABLE; //254 - neutral objects (pandora, banks)
 	DLL_LINKAGE static const PlayerColor UNFLAGGABLE; //254 - neutral objects (pandora, banks)
 	DLL_LINKAGE static const PlayerColor NEUTRAL; //255
 	DLL_LINKAGE static const PlayerColor NEUTRAL; //255
 	DLL_LINKAGE static const PlayerColor PLAYER_LIMIT; //player limit per map
 	DLL_LINKAGE static const PlayerColor PLAYER_LIMIT; //player limit per map
 
 
 	DLL_LINKAGE bool isValidPlayer() const; //valid means < PLAYER_LIMIT (especially non-neutral)
 	DLL_LINKAGE bool isValidPlayer() const; //valid means < PLAYER_LIMIT (especially non-neutral)
+	DLL_LINKAGE bool isSpectator() const;
 
 
 	DLL_LINKAGE std::string getStr(bool L10n = false) const;
 	DLL_LINKAGE std::string getStr(bool L10n = false) const;
 	DLL_LINKAGE std::string getStrCap(bool L10n = false) const;
 	DLL_LINKAGE std::string getStrCap(bool L10n = false) const;

+ 35 - 12
lib/Interprocess.h

@@ -19,40 +19,63 @@
 struct ServerReady
 struct ServerReady
 {
 {
 	bool ready;
 	bool ready;
-	boost::interprocess::interprocess_mutex  mutex;
-	boost::interprocess::interprocess_condition  cond;
+	uint16_t port; //ui16?
+	boost::interprocess::interprocess_mutex mutex;
+	boost::interprocess::interprocess_condition cond;
 
 
 	ServerReady()
 	ServerReady()
 	{
 	{
 		ready = false;
 		ready = false;
+		port = 0;
 	}
 	}
 
 
-	void setToTrueAndNotify()
+	void waitTillReady()
 	{
 	{
+		boost::interprocess::scoped_lock<boost::interprocess::interprocess_mutex> slock(mutex);
+		while(!ready)
 		{
 		{
-			boost::unique_lock<boost::interprocess::interprocess_mutex> lock(mutex); 
+			cond.wait(slock);
+		}
+	}
+
+	void setToReadyAndNotify(const uint16_t Port)
+	{
+		{
+			boost::unique_lock<boost::interprocess::interprocess_mutex> lock(mutex);
 			ready = true;
 			ready = true;
+			port = Port;
 		}
 		}
 		cond.notify_all();
 		cond.notify_all();
 	}
 	}
 };
 };
 
 
-struct SharedMem 
+struct SharedMemory
 {
 {
+	const char * name;
 	boost::interprocess::shared_memory_object smo;
 	boost::interprocess::shared_memory_object smo;
-	boost::interprocess::mapped_region *mr;
-	ServerReady *sr;
+	boost::interprocess::mapped_region * mr;
+	ServerReady * sr;
 	
 	
-	SharedMem() //c-tor
-		:smo(boost::interprocess::open_or_create,"vcmi_memory",boost::interprocess::read_write) 
+	SharedMemory(std::string Name, bool initialize = false)
+		: name(Name.c_str())
 	{
 	{
+		if(initialize)
+		{
+			//if the application has previously crashed, the memory may not have been removed. to avoid problems - try to destroy it
+			boost::interprocess::shared_memory_object::remove(name);
+		}
+		smo = boost::interprocess::shared_memory_object(boost::interprocess::open_or_create, name, boost::interprocess::read_write);
 		smo.truncate(sizeof(ServerReady));
 		smo.truncate(sizeof(ServerReady));
 		mr = new boost::interprocess::mapped_region(smo,boost::interprocess::read_write);
 		mr = new boost::interprocess::mapped_region(smo,boost::interprocess::read_write);
-		sr = new(mr->get_address())ServerReady();
+		if(initialize)
+			sr = new(mr->get_address())ServerReady();
+		else
+			sr = reinterpret_cast<ServerReady*>(mr->get_address());
 	};
 	};
-	~SharedMem() //d-tor
+
+	~SharedMemory()
 	{
 	{
 		delete mr;
 		delete mr;
-		boost::interprocess::shared_memory_object::remove("vcmi_memory");
+		boost::interprocess::shared_memory_object::remove(name);
 	}
 	}
 };
 };

+ 22 - 0
lib/NetPacks.h

@@ -152,6 +152,21 @@ struct PlayerBlocked : public CPackForClient
 	}
 	}
 };
 };
 
 
+struct PlayerCheated : public CPackForClient
+{
+	PlayerCheated() : losingCheatCode(false), winningCheatCode(false) {}
+	DLL_LINKAGE void applyGs(CGameState *gs);
+
+	PlayerColor player;
+	bool losingCheatCode;
+	bool winningCheatCode;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & player & losingCheatCode & winningCheatCode;
+	}
+};
+
 struct YourTurn : public CPackForClient
 struct YourTurn : public CPackForClient
 {
 {
 	YourTurn(){}
 	YourTurn(){}
@@ -1774,6 +1789,13 @@ struct CloseServer : public CPackForServer
 	{}
 	{}
 };
 };
 
 
+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);

+ 9 - 0
lib/NetPacksLib.cpp

@@ -1840,6 +1840,15 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs)
 	}
 	}
 }
 }
 
 
+DLL_LINKAGE void PlayerCheated::applyGs(CGameState *gs)
+{
+	if(!player.isValidPlayer())
+		return;
+
+	gs->getPlayer(player)->enteredLosingCheatCode = losingCheatCode;
+	gs->getPlayer(player)->enteredWinningCheatCode = winningCheatCode;
+}
+
 DLL_LINKAGE void YourTurn::applyGs(CGameState *gs)
 DLL_LINKAGE void YourTurn::applyGs(CGameState *gs)
 {
 {
 	gs->currentPlayer = player;
 	gs->currentPlayer = player;

+ 1 - 1
lib/VCMI_lib.vcxproj

@@ -424,4 +424,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
   </ImportGroup>
-</Project>
+</Project>

+ 1 - 1
lib/VCMI_lib.vcxproj.filters

@@ -669,4 +669,4 @@
       <Filter>Header Files</Filter>
       <Filter>Header Files</Filter>
     </ClInclude>
     </ClInclude>
   </ItemGroup>
   </ItemGroup>
-</Project>
+</Project>

+ 41 - 40
lib/mapObjects/CGHeroInstance.cpp

@@ -352,34 +352,43 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor *dst /*=
 
 
 		int count = rand.nextInt(stack.minAmount, stack.maxAmount);
 		int count = rand.nextInt(stack.minAmount, stack.maxAmount);
 
 
-		if(stack.creature >= CreatureID::CATAPULT &&
-		   stack.creature <= CreatureID::ARROW_TOWERS) //war machine
+		const CCreature * creature = stack.creature.toCreature();
+
+		if(creature == nullptr)
+		{
+			logGlobal->error("Hero %s has invalid creature with id %d in initial army", name, stack.creature.toEnum());
+			continue;
+		}
+
+		if(creature->warMachine != ArtifactID::NONE) //war machine
 		{
 		{
 			warMachinesGiven++;
 			warMachinesGiven++;
 			if(dst != this)
 			if(dst != this)
 				continue;
 				continue;
 
 
 			int slot = -1;
 			int slot = -1;
-			ArtifactID aid = ArtifactID::NONE;
-			switch (stack.creature)
+			ArtifactID aid = creature->warMachine;
+			const CArtifact * art = aid.toArtifact();
+
+			if(art != nullptr && !art->possibleSlots.at(ArtBearer::HERO).empty())
 			{
 			{
-			case CreatureID::CATAPULT:
-				slot = ArtifactPosition::MACH4;
-				aid = ArtifactID::CATAPULT;
-				break;
-			default:
-				aid = CArtHandler::creatureToMachineID(stack.creature);
-				slot = 9 + aid;
-				break;
+				//TODO: should we try another possible slots?
+				ArtifactPosition slot = art->possibleSlots.at(ArtBearer::HERO).front();
+
+				if(!getArt(slot))
+					putArtifact(slot, CArtifactInstance::createNewArtifactInstance(aid));
+				else
+					logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid;
 			}
 			}
-			auto convSlot = ArtifactPosition(slot);
-			if(!getArt(convSlot))
-				putArtifact(convSlot, CArtifactInstance::createNewArtifactInstance(aid));
 			else
 			else
-				logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid;
+			{
+				logGlobal->error("Hero %s has invalid war machine in initial army", name);
+			}
 		}
 		}
 		else
 		else
+		{
 			dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count);
 			dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count);
+		}
 	}
 	}
 }
 }
 
 
@@ -1610,42 +1619,34 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
 		}
 		}
 	}
 	}
 
 
+	//primary skills
+	if(handler.saving)
 	{
 	{
-		if(handler.saving)
+		const bool haveSkills = hasBonus(Selector::type(Bonus::PRIMARY_SKILL).And(Selector::sourceType(Bonus::HERO_BASE_SKILL)));
+
+		if(haveSkills)
 		{
 		{
-			bool haveSkills = false;
+			auto primarySkills = handler.enterStruct("primarySkills");
 
 
 			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
 			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
 			{
 			{
-				if(valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType(Bonus::HERO_BASE_SKILL))) != 0)
-				{
-					haveSkills = true;
-					break;
-				}
-			}
+				int value = valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType(Bonus::HERO_BASE_SKILL)));
 
 
-			if(haveSkills)
-			{
-				auto primarySkills = handler.enterStruct("primarySkills");
-
-				for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
-				{
-					int value = valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType(Bonus::HERO_BASE_SKILL)));
-
-					handler.serializeInt(PrimarySkill::names[i], value, 0);
-				}
+				handler.serializeInt(PrimarySkill::names[i], value, 0);
 			}
 			}
 		}
 		}
-		else
-		{
-			auto primarySkills = handler.enterStruct("primarySkills");
+	}
+	else
+	{
+		auto primarySkills = handler.enterStruct("primarySkills");
 
 
+		if(primarySkills.get().getType() == JsonNode::DATA_STRUCT)
+		{
 			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
 			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
 			{
 			{
 				int value = 0;
 				int value = 0;
-				handler.serializeInt(PrimarySkill::names[i], value, 0);
-				if(value != 0)
-					pushPrimSkill(static_cast<PrimarySkill::PrimarySkill>(i), value);
+				primarySkills->serializeInt(PrimarySkill::names[i], value, 0);
+				pushPrimSkill(static_cast<PrimarySkill::PrimarySkill>(i), value);
 			}
 			}
 		}
 		}
 	}
 	}

+ 1 - 1
lib/mapObjects/MiscObjects.cpp

@@ -1477,7 +1477,7 @@ std::string CGWitchHut::getHoverText(PlayerColor player) const
 std::string CGWitchHut::getHoverText(const CGHeroInstance * hero) const
 std::string CGWitchHut::getHoverText(const CGHeroInstance * hero) const
 {
 {
 	std::string hoverName = getHoverText(hero->tempOwner);
 	std::string hoverName = getHoverText(hero->tempOwner);
-	if(hero->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability
+	if(wasVisited(hero->tempOwner) && hero->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability
 		hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned)
 		hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned)
 	return hoverName;
 	return hoverName;
 }
 }

+ 3 - 1
lib/mapping/CMapInfo.cpp

@@ -1,6 +1,7 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "CMapInfo.h"
 #include "CMapInfo.h"
 
 
+#include "../filesystem/ResourceID.h"
 #include "../StartInfo.h"
 #include "../StartInfo.h"
 #include "../GameConstants.h"
 #include "../GameConstants.h"
 #include "CMapService.h"
 #include "CMapService.h"
@@ -58,7 +59,7 @@ CMapInfo::~CMapInfo()
 void CMapInfo::mapInit(const std::string & fname)
 void CMapInfo::mapInit(const std::string & fname)
 {
 {
 	fileURI = fname;
 	fileURI = fname;
-	mapHeader = CMapService::loadMapHeader(fname);
+	mapHeader = CMapService::loadMapHeader(ResourceID(fname, EResType::MAP));
 	countPlayers();
 	countPlayers();
 }
 }
 
 
@@ -81,3 +82,4 @@ CMapInfo & CMapInfo::operator=(CMapInfo &&tmp)
 	return *this;
 	return *this;
 }
 }
 
 
+#undef STEAL

+ 27 - 14
lib/mapping/CMapService.cpp

@@ -5,6 +5,7 @@
 #include "../filesystem/CBinaryReader.h"
 #include "../filesystem/CBinaryReader.h"
 #include "../filesystem/CCompressedStream.h"
 #include "../filesystem/CCompressedStream.h"
 #include "../filesystem/CMemoryStream.h"
 #include "../filesystem/CMemoryStream.h"
+#include "../filesystem/CMemoryBuffer.h"
 
 
 #include "CMap.h"
 #include "CMap.h"
 
 
@@ -12,24 +13,16 @@
 #include "MapFormatJson.h"
 #include "MapFormatJson.h"
 
 
 
 
-std::unique_ptr<CMap> CMapService::loadMap(const std::string & name)
+std::unique_ptr<CMap> CMapService::loadMap(const ResourceID & name)
 {
 {
 	auto stream = getStreamFromFS(name);
 	auto stream = getStreamFromFS(name);
-	std::unique_ptr<CMap> map(getMapLoader(stream)->loadMap());
-	std::unique_ptr<CMapHeader> header(map.get());
-
-	getMapPatcher(name)->patchMapHeader(header);
-	header.release();
-
-	return map;
+	return getMapLoader(stream)->loadMap();
 }
 }
 
 
-std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const std::string & name)
+std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const ResourceID & name)
 {
 {
 	auto stream = getStreamFromFS(name);
 	auto stream = getStreamFromFS(name);
-	std::unique_ptr<CMapHeader> header = getMapLoader(stream)->loadMapHeader();
-	getMapPatcher(name)->patchMapHeader(header);
-	return header;
+	return getMapLoader(stream)->loadMapHeader();
 }
 }
 
 
 std::unique_ptr<CMap> CMapService::loadMap(const ui8 * buffer, int size, const std::string & name)
 std::unique_ptr<CMap> CMapService::loadMap(const ui8 * buffer, int size, const std::string & name)
@@ -38,6 +31,7 @@ std::unique_ptr<CMap> CMapService::loadMap(const ui8 * buffer, int size, const s
 	std::unique_ptr<CMap> map(getMapLoader(stream)->loadMap());
 	std::unique_ptr<CMap> map(getMapLoader(stream)->loadMap());
 	std::unique_ptr<CMapHeader> header(map.get());
 	std::unique_ptr<CMapHeader> header(map.get());
 
 
+	//might be original campaign and require patch
 	getMapPatcher(name)->patchMapHeader(header);
 	getMapPatcher(name)->patchMapHeader(header);
 	header.release();
 	header.release();
 
 
@@ -48,13 +42,32 @@ std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const ui8 * buffer, int s
 {
 {
 	auto stream = getStreamFromMem(buffer, size);
 	auto stream = getStreamFromMem(buffer, size);
 	std::unique_ptr<CMapHeader> header = getMapLoader(stream)->loadMapHeader();
 	std::unique_ptr<CMapHeader> header = getMapLoader(stream)->loadMapHeader();
+
+	//might be original campaign and require patch
 	getMapPatcher(name)->patchMapHeader(header);
 	getMapPatcher(name)->patchMapHeader(header);
 	return header;
 	return header;
 }
 }
 
 
-std::unique_ptr<CInputStream> CMapService::getStreamFromFS(const std::string & name)
+void CMapService::saveMap(const std::unique_ptr<CMap> & map, boost::filesystem::path fullPath)
+{
+	CMemoryBuffer serializeBuffer;
+	{
+		CMapSaverJson saver(&serializeBuffer);
+		saver.saveMap(map);
+	}
+	{
+		boost::filesystem::remove(fullPath);
+		boost::filesystem::ofstream tmp(fullPath, boost::filesystem::ofstream::binary);
+
+		tmp.write((const char *)serializeBuffer.getBuffer().data(),serializeBuffer.getSize());
+		tmp.flush();
+		tmp.close();
+	}
+}
+
+std::unique_ptr<CInputStream> CMapService::getStreamFromFS(const ResourceID & name)
 {
 {
-	return CResourceHandler::get()->load(ResourceID(name, EResType::MAP));
+	return CResourceHandler::get()->load(name);
 }
 }
 
 
 std::unique_ptr<CInputStream> CMapService::getStreamFromMem(const ui8 * buffer, int size)
 std::unique_ptr<CInputStream> CMapService::getStreamFromMem(const ui8 * buffer, int size)

+ 6 - 3
lib/mapping/CMapService.h

@@ -11,6 +11,8 @@
 
 
 #pragma once
 #pragma once
 
 
+class ResourceID;
+
 class CMap;
 class CMap;
 class CMapHeader;
 class CMapHeader;
 class CInputStream;
 class CInputStream;
@@ -31,7 +33,7 @@ public:
 	 * @param name the name of the map
 	 * @param name the name of the map
 	 * @return a unique ptr to the loaded map class
 	 * @return a unique ptr to the loaded map class
 	 */
 	 */
-	static std::unique_ptr<CMap> loadMap(const std::string & name);
+	static std::unique_ptr<CMap> loadMap(const ResourceID & name);
 
 
 	/**
 	/**
 	 * Loads the VCMI/H3 map header specified by the name.
 	 * Loads the VCMI/H3 map header specified by the name.
@@ -39,7 +41,7 @@ public:
 	 * @param name the name of the map
 	 * @param name the name of the map
 	 * @return a unique ptr to the loaded map header class
 	 * @return a unique ptr to the loaded map header class
 	 */
 	 */
-	static std::unique_ptr<CMapHeader> loadMapHeader(const std::string & name);
+	static std::unique_ptr<CMapHeader> loadMapHeader(const ResourceID & name);
 
 
 	/**
 	/**
 	 * Loads the VCMI/H3 map file from a buffer. This method is temporarily
 	 * Loads the VCMI/H3 map file from a buffer. This method is temporarily
@@ -69,6 +71,7 @@ public:
 	 */
 	 */
 	static std::unique_ptr<CMapHeader> loadMapHeader(const ui8 * buffer, int size, const std::string & name);
 	static std::unique_ptr<CMapHeader> loadMapHeader(const ui8 * buffer, int size, const std::string & name);
 
 
+	static void saveMap(const std::unique_ptr<CMap> & map, boost::filesystem::path fullPath);
 private:
 private:
 	/**
 	/**
 	 * Gets a map input stream object specified by a map name.
 	 * Gets a map input stream object specified by a map name.
@@ -76,7 +79,7 @@ private:
 	 * @param name the name of the map
 	 * @param name the name of the map
 	 * @return a unique ptr to the input stream class
 	 * @return a unique ptr to the input stream class
 	 */
 	 */
-	static std::unique_ptr<CInputStream> getStreamFromFS(const std::string & name);
+	static std::unique_ptr<CInputStream> getStreamFromFS(const ResourceID & name);
 
 
 	/**
 	/**
 	 * Gets a map input stream from a buffer.
 	 * Gets a map input stream from a buffer.

+ 10 - 2
lib/mapping/MapFormatH3M.cpp

@@ -872,9 +872,17 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot)
 	bool isArt  =  aid != artmask;
 	bool isArt  =  aid != artmask;
 	if(isArt)
 	if(isArt)
 	{
 	{
-		if(vstd::contains(VLC->arth->bigArtifacts, aid) && slot >= GameConstants::BACKPACK_START)
+		const CArtifact * art = ArtifactID(aid).toArtifact();
+
+		if(nullptr == art)
+		{
+			logGlobal->warnStream() << "Invalid artifact in hero's backpack, ignoring...";
+			return false;
+		}
+
+		if(art->isBig() && slot >= GameConstants::BACKPACK_START)
 		{
 		{
-			logGlobal->warnStream() << "Warning: A big artifact (war machine) in hero's backpack, ignoring...";
+			logGlobal->warnStream() << "A big artifact (war machine) in hero's backpack, ignoring...";
 			return false;
 			return false;
 		}
 		}
 		if(aid == 0 && slot == ArtifactPosition::MISC5)
 		if(aid == 0 && slot == ArtifactPosition::MISC5)

+ 5 - 0
lib/mapping/MapFormatJson.cpp

@@ -1212,6 +1212,8 @@ void CMapSaverJson::saveMap(const std::unique_ptr<CMap>& map)
 
 
 void CMapSaverJson::writeHeader()
 void CMapSaverJson::writeHeader()
 {
 {
+	logGlobal->trace("Saving header");
+
 	JsonNode header;
 	JsonNode header;
 	JsonSerializer handler(mapObjectResolver.get(), header);
 	JsonSerializer handler(mapObjectResolver.get(), header);
 
 
@@ -1283,6 +1285,7 @@ JsonNode CMapSaverJson::writeTerrainLevel(const int index)
 
 
 void CMapSaverJson::writeTerrain()
 void CMapSaverJson::writeTerrain()
 {
 {
+	logGlobal->trace("Saving terrain");
 	//todo: multilevel map save support
 	//todo: multilevel map save support
 
 
 	JsonNode surface = writeTerrainLevel(0);
 	JsonNode surface = writeTerrainLevel(0);
@@ -1297,12 +1300,14 @@ void CMapSaverJson::writeTerrain()
 
 
 void CMapSaverJson::writeObjects()
 void CMapSaverJson::writeObjects()
 {
 {
+	logGlobal->trace("Saving objects");
 	JsonNode data(JsonNode::DATA_STRUCT);
 	JsonNode data(JsonNode::DATA_STRUCT);
 
 
 	JsonSerializer handler(mapObjectResolver.get(), data);
 	JsonSerializer handler(mapObjectResolver.get(), data);
 
 
 	for(CGObjectInstance * obj : map->objects)
 	for(CGObjectInstance * obj : map->objects)
 	{
 	{
+		logGlobal->trace("\t%s", obj->instanceName);
 		auto temp = handler.enterStruct(obj->instanceName);
 		auto temp = handler.enterStruct(obj->instanceName);
 
 
 		obj->serializeJson(handler);
 		obj->serializeJson(handler);

+ 2 - 0
lib/registerTypes/RegisterTypes.h

@@ -210,6 +210,7 @@ void registerTypesClientPacks1(Serializer &s)
 	s.template registerType<CPackForClient, PackageApplied>();
 	s.template registerType<CPackForClient, PackageApplied>();
 	s.template registerType<CPackForClient, SystemMessage>();
 	s.template registerType<CPackForClient, SystemMessage>();
 	s.template registerType<CPackForClient, PlayerBlocked>();
 	s.template registerType<CPackForClient, PlayerBlocked>();
+	s.template registerType<CPackForClient, PlayerCheated>();
 	s.template registerType<CPackForClient, YourTurn>();
 	s.template registerType<CPackForClient, YourTurn>();
 	s.template registerType<CPackForClient, SetResources>();
 	s.template registerType<CPackForClient, SetResources>();
 	s.template registerType<CPackForClient, SetPrimSkill>();
 	s.template registerType<CPackForClient, SetPrimSkill>();
@@ -314,6 +315,7 @@ void registerTypesServerPacks(Serializer &s)
 {
 {
 	s.template registerType<CPack, CPackForServer>();
 	s.template registerType<CPack, CPackForServer>();
 	s.template registerType<CPackForServer, CloseServer>();
 	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>();

+ 0 - 1
lib/registerTypes/TypesClientPacks1.cpp

@@ -1,7 +1,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "RegisterTypes.h"
 #include "RegisterTypes.h"
 
 
-#include "../mapping/CMapInfo.h"
 #include "../StartInfo.h"
 #include "../StartInfo.h"
 #include "../CGameState.h"
 #include "../CGameState.h"
 #include "../mapping/CMap.h"
 #include "../mapping/CMap.h"

+ 0 - 1
lib/registerTypes/TypesClientPacks2.cpp

@@ -1,7 +1,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "RegisterTypes.h"
 #include "RegisterTypes.h"
 
 
-#include "../mapping/CMapInfo.h"
 #include "../StartInfo.h"
 #include "../StartInfo.h"
 #include "../CStack.h"
 #include "../CStack.h"
 #include "../BattleInfo.h"
 #include "../BattleInfo.h"

+ 0 - 1
lib/registerTypes/TypesMapObjects1.cpp

@@ -1,7 +1,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "RegisterTypes.h"
 #include "RegisterTypes.h"
 
 
-#include "../mapping/CMapInfo.h"
 #include "../StartInfo.h"
 #include "../StartInfo.h"
 #include "../CGameState.h"
 #include "../CGameState.h"
 #include "../mapping/CMap.h"
 #include "../mapping/CMap.h"

+ 0 - 1
lib/registerTypes/TypesMapObjects2.cpp

@@ -1,7 +1,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "RegisterTypes.h"
 #include "RegisterTypes.h"
 
 
-#include "../mapping/CMapInfo.h"
 #include "../StartInfo.h"
 #include "../StartInfo.h"
 #include "../CStack.h"
 #include "../CStack.h"
 #include "../BattleInfo.h"
 #include "../BattleInfo.h"

+ 0 - 1
lib/registerTypes/TypesMapObjects3.cpp

@@ -1,7 +1,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "RegisterTypes.h"
 #include "RegisterTypes.h"
 
 
-#include "../mapping/CMapInfo.h"
 #include "../StartInfo.h"
 #include "../StartInfo.h"
 #include "../CGameState.h"
 #include "../CGameState.h"
 #include "../mapping/CMap.h"
 #include "../mapping/CMap.h"

+ 0 - 1
lib/registerTypes/TypesServerPacks.cpp

@@ -1,7 +1,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "RegisterTypes.h"
 #include "RegisterTypes.h"
 
 
-#include "../mapping/CMapInfo.h"
 #include "../StartInfo.h"
 #include "../StartInfo.h"
 #include "../CGameState.h"
 #include "../CGameState.h"
 #include "../mapping/CMap.h"
 #include "../mapping/CMap.h"

+ 1 - 1
lib/serializer/CSerializer.h

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

+ 8 - 4
lib/serializer/Connection.cpp

@@ -61,14 +61,14 @@ void CConnection::init()
 	iser.fileVersion = SERIALIZATION_VERSION;
 	iser.fileVersion = SERIALIZATION_VERSION;
 }
 }
 
 
-CConnection::CConnection(std::string host, std::string port, std::string Name)
+CConnection::CConnection(std::string host, ui16 port, std::string Name)
 :iser(this), oser(this), io_service(new asio::io_service), name(Name)
 :iser(this), oser(this), io_service(new asio::io_service), name(Name)
 {
 {
 	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 = new 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,port),error);
+	tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, std::to_string(port)),error);
 	if(error)
 	if(error)
 	{
 	{
 		logNetwork->errorStream() << "Problem with resolving: \n" << error;
 		logNetwork->errorStream() << "Problem with resolving: \n" << error;
@@ -191,8 +191,7 @@ void CConnection::close()
 	if(socket)
 	if(socket)
 	{
 	{
 		socket->close();
 		socket->close();
-		delete socket;
-		socket = nullptr;
+		vstd::clear_pointer(socket);
 	}
 	}
 }
 }
 
 
@@ -201,6 +200,11 @@ bool CConnection::isOpen() const
 	return socket && connected;
 	return socket && connected;
 }
 }
 
 
+bool CConnection::isHost() const
+{
+	return connectionID == 1;
+}
+
 void CConnection::reportState(CLogger * out)
 void CConnection::reportState(CLogger * out)
 {
 {
 	out->debugStream() << "CConnection";
 	out->debugStream() << "CConnection";

+ 2 - 1
lib/serializer/Connection.h

@@ -68,12 +68,13 @@ public:
 
 
 	bool receivedStop, sendStop;
 	bool receivedStop, sendStop;
 
 
-	CConnection(std::string host, std::string port, std::string Name);
+	CConnection(std::string host, ui16 port, std::string Name);
 	CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, 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(TSocket * Socket, std::string Name); //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(void);
 	virtual ~CConnection(void);

+ 47 - 3
lib/spells/CSpellHandler.cpp

@@ -157,10 +157,50 @@ ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affecte
 
 
 ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
 {
-	const ESpellCastProblem::ESpellCastProblem generalProblem = mechanics->canBeCast(cb, mode, caster);
+	ESpellCastProblem::ESpellCastProblem genProblem = cb->battleCanCastSpell(caster, mode);
+	if(genProblem != ESpellCastProblem::OK)
+		return genProblem;
 
 
-	if(generalProblem != ESpellCastProblem::OK)
-		return generalProblem;
+	switch(mode)
+	{
+	case ECastingMode::HERO_CASTING:
+		{
+			const CGHeroInstance * castingHero = dynamic_cast<const CGHeroInstance *>(caster);//todo: unify hero|creature spell cost
+			if(!castingHero)
+			{
+				logGlobal->debug("CSpell::canBeCast: invalid caster");
+				return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
+			}
+
+			if(!castingHero->getArt(ArtifactPosition::SPELLBOOK))
+				return ESpellCastProblem::NO_SPELLBOOK;
+			if(!castingHero->canCastThisSpell(this))
+				return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL;
+			if(castingHero->mana < cb->battleGetSpellCost(this, castingHero)) //not enough mana
+				return ESpellCastProblem::NOT_ENOUGH_MANA;
+		}
+		break;
+	}
+
+	if(!isCombatSpell())
+		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
+
+	const PlayerColor player = caster->getOwner();
+	const si8 side = cb->playerToSide(player);
+
+	if(side < 0)
+		return ESpellCastProblem::INVALID;
+
+	//effect like Recanter's Cloak. Blocks also passive casting.
+	//TODO: check creature abilities to block
+	//TODO: check any possible caster
+	if(cb->battleMaxSpellLevel(side) < level)
+		return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
+
+	const ESpellCastProblem::ESpellCastProblem specificProblem = mechanics->canBeCast(cb, mode, caster);
+
+	if(specificProblem != ESpellCastProblem::OK)
+		return specificProblem;
 
 
 	//check for creature target existence
 	//check for creature target existence
 	//allow to cast spell if there is at least one smart target
 	//allow to cast spell if there is at least one smart target
@@ -368,6 +408,10 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
 
 
 ESpellCastProblem::ESpellCastProblem CSpell::canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
 ESpellCastProblem::ESpellCastProblem CSpell::canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
 {
 {
+	ESpellCastProblem::ESpellCastProblem problem = canBeCast(cb, mode, caster);
+	if(problem != ESpellCastProblem::OK)
+		return problem;
+
 	SpellTargetingContext ctx(this, mode, caster, caster->getSpellSchoolLevel(this), destination);
 	SpellTargetingContext ctx(this, mode, caster, caster->getSpellSchoolLevel(this), destination);
 
 
 	return mechanics->canBeCast(cb, ctx);
 	return mechanics->canBeCast(cb, ctx);

+ 6 - 2
lib/spells/ISpellMechanics.cpp

@@ -87,10 +87,14 @@ void BattleSpellCastParameters::cast(const SpellCastEnvironment * env)
 	spell->battleCast(env, *this);
 	spell->battleCast(env, *this);
 }
 }
 
 
-void BattleSpellCastParameters::castIfPossible(const SpellCastEnvironment * env)
+bool BattleSpellCastParameters::castIfPossible(const SpellCastEnvironment * env)
 {
 {
-	if(ESpellCastProblem::OK == cb->battleCanCastThisSpell(caster, spell, mode))
+	if(ESpellCastProblem::OK == spell->canBeCast(cb, mode, caster))
+	{
 		cast(env);
 		cast(env);
+		return true;
+	}
+	return false;
 }
 }
 
 
 BattleHex BattleSpellCastParameters::getFirstDestinationHex() const
 BattleHex BattleSpellCastParameters::getFirstDestinationHex() const

+ 2 - 1
lib/spells/ISpellMechanics.h

@@ -57,7 +57,8 @@ public:
 	void cast(const SpellCastEnvironment * env);
 	void cast(const SpellCastEnvironment * env);
 
 
 	///cast with silent check for permitted cast
 	///cast with silent check for permitted cast
-	void castIfPossible(const SpellCastEnvironment * env);
+	///returns true if cast was permitted
+	bool castIfPossible(const SpellCastEnvironment * env);
 
 
 	BattleHex getFirstDestinationHex() const;
 	BattleHex getFirstDestinationHex() const;
 
 

+ 125 - 58
server/CGameHandler.cpp

@@ -48,7 +48,7 @@
 #ifndef _MSC_VER
 #ifndef _MSC_VER
 #include <boost/thread/xtime.hpp>
 #include <boost/thread/xtime.hpp>
 #endif
 #endif
-extern bool end2;
+extern std::atomic<bool> serverShuttingDown;
 #ifdef min
 #ifdef min
 #undef min
 #undef min
 #endif
 #endif
@@ -1031,6 +1031,25 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
 {
 {
 	setThreadName("CGameHandler::handleConnection");
 	setThreadName("CGameHandler::handleConnection");
 
 
+	auto handleDisconnection = [&](const std::exception & e)
+	{
+		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)
+		{
+			if(!serverShuttingDown && playerConn.second == &c)
+			{
+				PlayerCheated pc;
+				pc.player = playerConn.first;
+				pc.losingCheatCode = true;
+				sendAndApply(&pc);
+				checkVictoryLossConditionsForPlayer(playerConn.first);
+			}
+		}
+	};
+
 	try
 	try
 	{
 	{
 		while(1)//server should never shut connection first //was: while(!end2)
 		while(1)//server should never shut connection first //was: while(!end2)
@@ -1042,6 +1061,8 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
 
 
 			{
 			{
 				boost::unique_lock<boost::mutex> lock(*c.rmx);
 				boost::unique_lock<boost::mutex> lock(*c.rmx);
+				if(!c.connected)
+					throw clientDisconnectedException();
 				c >> player >> requestID >> pack; //get the package
 				c >> player >> requestID >> pack; //get the package
 
 
 				if (!pack)
 				if (!pack)
@@ -1060,6 +1081,11 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
 			//prepare struct informing that action was applied
 			//prepare struct informing that action was applied
 			auto sendPackageResponse = [&](bool succesfullyApplied)
 			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;
 				PackageApplied applied;
 				applied.player = player;
 				applied.player = player;
 				applied.result = succesfullyApplied;
 				applied.result = succesfullyApplied;
@@ -1095,13 +1121,15 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
 	}
 	}
 	catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
 	catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
 	{
 	{
-		assert(!c.connected); //make sure that connection has been marked as broken
-		logGlobal->error(e.what());
-		end2 = true;
+		handleDisconnection(e);
+	}
+	catch(clientDisconnectedException & e)
+	{
+		handleDisconnection(e);
 	}
 	}
 	catch(...)
 	catch(...)
 	{
 	{
-		end2 = true;
+		serverShuttingDown = true;
 		handleException();
 		handleException();
 		throw;
 		throw;
 	}
 	}
@@ -1851,7 +1879,8 @@ 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);
-				connections[color] = cc;
+				if(!color.isSpectator()) // there can be more than one spectator
+					connections[color] = cc;
 			}
 			}
 		}
 		}
 		logGlobal->info(sbuffer.str());
 		logGlobal->info(sbuffer.str());
@@ -1874,7 +1903,7 @@ void CGameHandler::run(bool resume)
 	if (gs->scenarioOps->mode == StartInfo::DUEL)
 	if (gs->scenarioOps->mode == StartInfo::DUEL)
 	{
 	{
 		runBattle();
 		runBattle();
-		end2 = true;
+		serverShuttingDown = true;
 
 
 
 
 		while(conns.size() && (*conns.begin())->isOpen())
 		while(conns.size() && (*conns.begin())->isOpen())
@@ -1885,7 +1914,7 @@ void CGameHandler::run(bool resume)
 
 
 	auto playerTurnOrder = generatePlayerTurnOrder();
 	auto playerTurnOrder = generatePlayerTurnOrder();
 
 
-	while(!end2)
+	while(!serverShuttingDown)
 	{
 	{
 		if (!resume) newTurn();
 		if (!resume) newTurn();
 
 
@@ -1926,7 +1955,7 @@ 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 && !end2)
+					while(states.players.at(playerColor).makingTurn && !serverShuttingDown)
 					{
 					{
 						static time_duration p = milliseconds(100);
 						static time_duration p = milliseconds(100);
 						states.cv.timed_wait(lock, p);
 						states.cv.timed_wait(lock, p);
@@ -1942,7 +1971,7 @@ void CGameHandler::run(bool resume)
 					activePlayer = true;
 					activePlayer = true;
 		}
 		}
 		if (!activePlayer)
 		if (!activePlayer)
-			end2 = true;
+			serverShuttingDown = true;
 	}
 	}
 	while(conns.size() && (*conns.begin())->isOpen())
 	while(conns.size() && (*conns.begin())->isOpen())
 		boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
 		boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
@@ -2631,6 +2660,9 @@ void CGameHandler::sendToAllClients(CPackForClient * info)
 	logNetwork->trace("Sending to all clients a package of type %s", typeid(*info).name());
 	logNetwork->trace("Sending to all clients a package of type %s", typeid(*info).name());
 	for (auto & elem : conns)
 	for (auto & elem : conns)
 	{
 	{
+		if(!elem->isOpen())
+			continue;
+
 		boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
 		boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
 		*elem << info;
 		*elem << info;
 	}
 	}
@@ -2703,11 +2735,31 @@ void CGameHandler::close()
 	{
 	{
 		exit(0);
 		exit(0);
 	}
 	}
+	serverShuttingDown = true;
 
 
-	//for (CConnection *cc : conns)
-	//	if (cc && cc->socket && cc->socket->is_open())
-	//		cc->socket->close();
-	//exit(0);
+	for (auto & elem : conns)
+	{
+		if(!elem->isOpen())
+			continue;
+
+		boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
+		elem->close();
+		elem->connected = false;
+	}
+}
+
+void CGameHandler::playerLeftGame(int cid)
+{
+	for (auto & elem : conns)
+	{
+		if(elem->isOpen() && elem->connectionID == cid)
+		{
+			boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
+			elem->close();
+			elem->connected = false;
+			break;
+		}
+	}
 }
 }
 
 
 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)
@@ -3083,7 +3135,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst
 	const CGDwelling * dw = static_cast<const CGDwelling *>(getObj(objid));
 	const CGDwelling * dw = static_cast<const CGDwelling *>(getObj(objid));
 	const CArmedInstance *dst = nullptr;
 	const CArmedInstance *dst = nullptr;
 	const CCreature *c = VLC->creh->creatures.at(crid);
 	const CCreature *c = VLC->creh->creatures.at(crid);
-	bool warMachine = c->hasBonusOfType(Bonus::SIEGE_WEAPON);
+	const bool warMachine = c->warMachine != ArtifactID::NONE;
 
 
 	//TODO: test for owning
 	//TODO: test for owning
 	//TODO: check if dst can recruit objects (e.g. hero is actually visiting object, town and source are same, etc)
 	//TODO: check if dst can recruit objects (e.g. hero is actually visiting object, town and source are same, etc)
@@ -3134,24 +3186,18 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst
 	if (warMachine)
 	if (warMachine)
 	{
 	{
 		const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(dst);
 		const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(dst);
-		if (!h)
-			COMPLAIN_RET("Only hero can buy war machines");
 
 
-		switch(crid)
-		{
-		case CreatureID::BALLISTA:
-			giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::BALLISTA], ArtifactPosition::MACH1);
-			break;
-		case CreatureID::FIRST_AID_TENT:
-			giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
-			break;
-		case CreatureID::AMMO_CART:
-			giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::AMMO_CART], ArtifactPosition::MACH2);
-			break;
-		default:
-			complain("This war machine cannot be recruited!");
-			return false;
-		}
+		COMPLAIN_RET_FALSE_IF(!h, "Only hero can buy war machines");
+
+		ArtifactID artId = c->warMachine;
+
+		COMPLAIN_RET_FALSE_IF(artId == ArtifactID::CATAPULT, "Catapult cannot be recruited!");
+
+		const CArtifact * art = artId.toArtifact();
+
+		COMPLAIN_RET_FALSE_IF(nullptr == art, "Invalid war machine artifact");
+
+		return giveHeroNewArtifact(h, art);
 	}
 	}
 	else
 	else
 	{
 	{
@@ -3392,7 +3438,10 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition
 bool CGameHandler::buyArtifact(ObjectInstanceID hid, ArtifactID aid)
 bool CGameHandler::buyArtifact(ObjectInstanceID hid, ArtifactID aid)
 {
 {
 	const CGHeroInstance * hero = getHero(hid);
 	const CGHeroInstance * hero = getHero(hid);
+	COMPLAIN_RET_FALSE_IF(nullptr == hero, "Invalid hero index");
 	const CGTownInstance * town = hero->visitedTown;
 	const CGTownInstance * town = hero->visitedTown;
+	COMPLAIN_RET_FALSE_IF(nullptr == town, "Hero not in town");
+
 	if (aid==ArtifactID::SPELLBOOK)
 	if (aid==ArtifactID::SPELLBOOK)
 	{
 	{
 		if ((!town->hasBuilt(BuildingID::MAGES_GUILD_1) && complain("Cannot buy a spellbook, no mage guild in the town!"))
 		if ((!town->hasBuilt(BuildingID::MAGES_GUILD_1) && complain("Cannot buy a spellbook, no mage guild in the town!"))
@@ -3407,26 +3456,24 @@ bool CGameHandler::buyArtifact(ObjectInstanceID hid, ArtifactID aid)
 		giveSpells(town,hero);
 		giveSpells(town,hero);
 		return true;
 		return true;
 	}
 	}
-	else if (aid < 7  &&  aid > 3) //war machine
+	else
 	{
 	{
-		int price = VLC->arth->artifacts[aid]->price;
+		const CArtifact * art = aid.toArtifact();
+		COMPLAIN_RET_FALSE_IF(nullptr == art, "Invalid artifact index to buy");
+		COMPLAIN_RET_FALSE_IF(art->warMachine == CreatureID::NONE, "War machine artifact required");
+		COMPLAIN_RET_FALSE_IF(hero->hasArt(aid),"Hero already has this machine!");
+		const int price = art->price;
+		COMPLAIN_RET_FALSE_IF(getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price, "Not enough gold!");
 
 
-		if ((hero->getArt(ArtifactPosition(9+aid)) && complain("Hero already has this machine!"))
-		 || (getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price && complain("Not enough gold!")))
-		{
-			return false;
-		}
 		if  ((town->hasBuilt(BuildingID::BLACKSMITH) && town->town->warMachine == aid)
 		if  ((town->hasBuilt(BuildingID::BLACKSMITH) && town->town->warMachine == aid)
 		 || ((town->hasBuilt(BuildingID::BALLISTA_YARD, ETownType::STRONGHOLD)) && aid == ArtifactID::BALLISTA))
 		 || ((town->hasBuilt(BuildingID::BALLISTA_YARD, ETownType::STRONGHOLD)) && aid == ArtifactID::BALLISTA))
 		{
 		{
 			giveResource(hero->getOwner(),Res::GOLD,-price);
 			giveResource(hero->getOwner(),Res::GOLD,-price);
-			giveHeroNewArtifact(hero, VLC->arth->artifacts[aid], ArtifactPosition(9+aid));
-			return true;
+			return giveHeroNewArtifact(hero, art);
 		}
 		}
 		else
 		else
 			COMPLAIN_RET("This machine is unavailable here!");
 			COMPLAIN_RET("This machine is unavailable here!");
 	}
 	}
-	return false;
 }
 }
 
 
 bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, Res::ERes rid, ArtifactID aid)
 bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, Res::ERes rid, ArtifactID aid)
@@ -4379,7 +4426,9 @@ void CGameHandler::playerMessage(PlayerColor player, const std::string &message,
 	{
 	{
 		SystemMessage temp_message(VLC->generaltexth->allTexts.at(260));
 		SystemMessage temp_message(VLC->generaltexth->allTexts.at(260));
 		sendAndApply(&temp_message);
 		sendAndApply(&temp_message);
-		checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
+
+		if(!player.isSpectator())
+			checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
 	}
 	}
 }
 }
 
 
@@ -4407,7 +4456,7 @@ bool CGameHandler::makeCustomAction(BattleAction &ba)
 			if (ba.selectedStack >= 0)
 			if (ba.selectedStack >= 0)
 				parameters.aimToStack(gs->curB->battleGetStackByID(ba.selectedStack, false));
 				parameters.aimToStack(gs->curB->battleGetStackByID(ba.selectedStack, false));
 
 
-			ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast(battleCanCastThisSpellHere)?
+			ESpellCastProblem::ESpellCastProblem escp = s->canBeCast(gs->curB, ECastingMode::HERO_CASTING, h);//todo: should we check aimed cast?
 			if (escp != ESpellCastProblem::OK)
 			if (escp != ESpellCastProblem::OK)
 			{
 			{
 				logGlobal->warn("Spell cannot be cast! Problem: %d", escp);
 				logGlobal->warn("Spell cannot be cast! Problem: %d", escp);
@@ -4580,14 +4629,14 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
 				const CSpell * spell = SpellID(spellID).toSpell();
 				const CSpell * spell = SpellID(spellID).toSpell();
 				bl.remove_if([&bonus](const Bonus* b){return b==bonus.get();});
 				bl.remove_if([&bonus](const Bonus* b){return b==bonus.get();});
 
 
-				if (gs->curB->battleCanCastThisSpell(st, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK)
-				{
-					BattleSpellCastParameters parameters(gs->curB, st, spell);
-					parameters.spellLvl = bonus->val;
-					parameters.effectLevel = bonus->val;//todo: recheck
-					parameters.mode = ECastingMode::ENCHANTER_CASTING;
-					parameters.cast(spellEnv);
+				BattleSpellCastParameters parameters(gs->curB, st, spell);
+				parameters.spellLvl = bonus->val;
+				parameters.effectLevel = bonus->val;//todo: recheck
+				parameters.mode = ECastingMode::ENCHANTER_CASTING;
 
 
+				cast = parameters.castIfPossible(spellEnv);
+				if(cast)
+				{
 					//todo: move to mechanics
 					//todo: move to mechanics
 					BattleSetStackProperty ssp;
 					BattleSetStackProperty ssp;
 					ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
 					ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
@@ -4595,8 +4644,6 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
 					ssp.val = bonus->additionalInfo; //increase cooldown counter
 					ssp.val = bonus->additionalInfo; //increase cooldown counter
 					ssp.stackID = st->ID;
 					ssp.stackID = st->ID;
 					sendAndApply(&ssp);
 					sendAndApply(&ssp);
-
-					cast = true;
 				}
 				}
 			}
 			}
 		}
 		}
@@ -5051,7 +5098,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 
 
 			if (p->human)
 			if (p->human)
 			{
 			{
-				end2 = true;
+				serverShuttingDown = true;
 
 
 				if (gs->scenarioOps->campState)
 				if (gs->scenarioOps->campState)
 				{
 				{
@@ -5251,7 +5298,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
 			vstd::amin(chance, 100);
 			vstd::amin(chance, 100);
 
 
 			const CSpell * spell = SpellID(spellID).toSpell();
 			const CSpell * spell = SpellID(spellID).toSpell();
-			if (gs->curB->battleCanCastThisSpellHere(attacker, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
+			if(spell->canBeCastAt(gs->curB, attacker, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
 				continue;
 				continue;
 
 
 			//check if spell should be cast (probability handling)
 			//check if spell should be cast (probability handling)
@@ -6001,6 +6048,18 @@ void CGameHandler::putArtifact(const ArtifactLocation &al, const CArtifactInstan
 	sendAndApply(&pa);
 	sendAndApply(&pa);
 }
 }
 
 
+bool CGameHandler::giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * art)
+{
+	COMPLAIN_RET_FALSE_IF(art->possibleSlots.at(ArtBearer::HERO).empty(), "Not a hero artifact!");
+
+	ArtifactPosition slot = art->possibleSlots.at(ArtBearer::HERO).front();
+
+	COMPLAIN_RET_FALSE_IF(nullptr != h->getArt(slot, false), "Hero already has artifact in slot");
+
+	giveHeroNewArtifact(h, art, slot);
+	return true;
+}
+
 void CGameHandler::giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos)
 void CGameHandler::giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos)
 {
 {
 	CArtifactInstance *a = nullptr;
 	CArtifactInstance *a = nullptr;
@@ -6162,12 +6221,18 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
 	else if (cheat == "vcmisilmaril")
 	else if (cheat == "vcmisilmaril")
 	{
 	{
 		///Player wins
 		///Player wins
-		gs->getPlayer(player)->enteredWinningCheatCode = 1;
+		PlayerCheated pc;
+		pc.player = player;
+		pc.winningCheatCode = true;
+		sendAndApply(&pc);
 	}
 	}
 	else if (cheat == "vcmimelkor")
 	else if (cheat == "vcmimelkor")
 	{
 	{
 		///Player looses
 		///Player looses
-		gs->getPlayer(player)->enteredLosingCheatCode = 1;
+		PlayerCheated pc;
+		pc.player = player;
+		pc.losingCheatCode = true;
+		sendAndApply(&pc);
 	}
 	}
 	else if (cheat == "vcmieagles" || cheat == "vcmiungoliant")
 	else if (cheat == "vcmieagles" || cheat == "vcmiungoliant")
 	{
 	{
@@ -6357,10 +6422,12 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
 		}
 		}
 		else if (st->slot == SlotID::WAR_MACHINES_SLOT)
 		else if (st->slot == SlotID::WAR_MACHINES_SLOT)
 		{
 		{
-			auto warMachine = VLC->arth->creatureToMachineID(st->type->idNumber);
+			auto warMachine = st->type->warMachine;
 
 
 			if (warMachine == ArtifactID::NONE)
 			if (warMachine == ArtifactID::NONE)
+			{
 				logGlobal->error("Invalid creature in war machine virtual slot. Stack: %s", st->nodeName());
 				logGlobal->error("Invalid creature in war machine virtual slot. Stack: %s", st->nodeName());
+			}
 			//catapult artifact remain even if "creature" killed in siege
 			//catapult artifact remain even if "creature" killed in siege
 			else if (warMachine != ArtifactID::CATAPULT && !st->count)
 			else if (warMachine != ArtifactID::CATAPULT && !st->count)
 			{
 			{

+ 7 - 0
server/CGameHandler.h

@@ -143,6 +143,7 @@ public:
 
 
 	void removeAfterVisit(const CGObjectInstance *object) override;
 	void removeAfterVisit(const CGObjectInstance *object) override;
 
 
+	bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * art);
 	void giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos) override;
 	void giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos) override;
 	void giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos) override;
 	void giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos) override;
 	void putArtifact(const ArtifactLocation &al, const CArtifactInstance *a) override;
 	void putArtifact(const ArtifactLocation &al, const CArtifactInstance *a) override;
@@ -223,6 +224,7 @@ public:
 	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 close();
+	void playerLeftGame(int cid);
 	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
@@ -298,4 +300,9 @@ private:
 	void checkVictoryLossConditionsForAll();
 	void checkVictoryLossConditionsForAll();
 };
 };
 
 
+class clientDisconnectedException : public std::exception
+{
+
+};
+
 void makeStackDoNothing();
 void makeStackDoNothing();

+ 115 - 83
server/CVCMIServer.cpp

@@ -41,11 +41,7 @@
 
 
 std::string NAME_AFFIX = "server";
 std::string NAME_AFFIX = "server";
 std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
 std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
-#ifndef VCMI_ANDROID
-namespace intpr = boost::interprocess;
-#endif
-bool end2 = false;
-int port = 3030;
+std::atomic<bool> serverShuttingDown(false);
 
 
 boost::program_options::variables_map cmdLineOptions;
 boost::program_options::variables_map cmdLineOptions;
 
 
@@ -106,6 +102,10 @@ void CPregameServer::handleConnection(CConnection *cpc)
 				auto unlock = vstd::makeUnlockGuard(mx);
 				auto unlock = vstd::makeUnlockGuard(mx);
 				while(state == RUNNING) boost::this_thread::sleep(boost::posix_time::milliseconds(50));
 				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)
 	catch (const std::exception& e)
@@ -206,20 +206,29 @@ void CPregameServer::connectionAccepted(const boost::system::error_code& ec)
 		return;
 		return;
 	}
 	}
 
 
-	logNetwork->info("We got a new connection! :)");
-	CConnection *pc = new CConnection(upcomingConnection, NAME);
-	initConnection(pc);
-	upcomingConnection = nullptr;
+	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);
+		startListeningThread(pc);
 
 
-	*pc << (ui8)pc->connectionID << curmap;
+		*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);
+		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();
 	start_async_accept();
 }
 }
@@ -314,9 +323,28 @@ void CPregameServer::startListeningThread(CConnection * pc)
 }
 }
 
 
 CVCMIServer::CVCMIServer()
 CVCMIServer::CVCMIServer()
-: io(new boost::asio::io_service()), acceptor(new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))), firstConnection(nullptr)
+	: port(3030), io(new boost::asio::io_service()), firstConnection(nullptr), shared(nullptr)
 {
 {
 	logNetwork->trace("CVCMIServer created!");
 	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()
 CVCMIServer::~CVCMIServer()
 {
 {
@@ -394,70 +422,77 @@ void CVCMIServer::newPregame()
 void CVCMIServer::start()
 void CVCMIServer::start()
 {
 {
 #ifndef VCMI_ANDROID
 #ifndef VCMI_ANDROID
-	ServerReady *sr = nullptr;
-	intpr::mapped_region *mr;
-	try
+	if(cmdLineOptions.count("enable-shm"))
 	{
 	{
-		intpr::shared_memory_object smo(intpr::open_only,"vcmi_memory",intpr::read_write);
-		smo.truncate(sizeof(ServerReady));
-		mr = new intpr::mapped_region(smo,intpr::read_write);
-		sr = reinterpret_cast<ServerReady*>(mr->get_address());
-	}
-	catch(...)
-	{
-		intpr::shared_memory_object smo(intpr::create_only,"vcmi_memory",intpr::read_write);
-		smo.truncate(sizeof(ServerReady));
-		mr = new intpr::mapped_region(smo,intpr::read_write);
-		sr = new(mr->get_address())ServerReady();
+		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
 #endif
 
 
 	boost::system::error_code error;
 	boost::system::error_code error;
-	logNetwork->info("Listening for connections at port %d", acceptor->local_endpoint().port());
-	auto s = new boost::asio::ip::tcp::socket(acceptor->get_io_service());
-	boost::thread acc(std::bind(vaccept,acceptor,s,&error));
-#ifndef VCMI_ANDROID
-	sr->setToTrueAndNotify();
-	delete mr;
+	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
 #else
-	{ // 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");
-	}
+			if(shared)
+			{
+				shared->sr->setToReadyAndNotify(port);
+			}
 #endif
 #endif
 
 
-	acc.join();
-	if (error)
-	{
-		logNetwork->warnStream() << "Got connection but there is an error " << error;
-		return;
-	}
-	logNetwork->info("We've accepted someone... ");
-	firstConnection = new CConnection(s, NAME);
-	logNetwork->info("Got connection!");
-	while (!end2)
-	{
-		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();
+			acc.join();
+			if (error)
+			{
+				logNetwork->warnStream()<<"Got connection but there is an error " << error;
+				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;
 			break;
 		}
 		}
+		catch(std::exception& e)
+		{
+			vstd::clear_pointer(firstConnection);
+			logNetwork->info("I guess it was just my imagination!");
+		}
 	}
 	}
 }
 }
 
 
@@ -507,7 +542,11 @@ static void handleCommandOptions(int argc, char *argv[])
 	opts.add_options()
 	opts.add_options()
 		("help,h", "display help and exit")
 		("help,h", "display help and exit")
 		("version,v", "display version information and exit")
 		("version,v", "display version information and exit")
-		("port", po::value<int>()->default_value(3030), "port at which server will listen to connections from client")
+		("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")
 		("resultsFile", po::value<std::string>()->default_value("./results.txt"), "file to which the battle result will be appended. Used only in the DUEL mode.");
 		("resultsFile", po::value<std::string>()->default_value("./results.txt"), "file to which the battle result will be appended. Used only in the DUEL mode.");
 
 
 	if(argc > 1)
 	if(argc > 1)
@@ -584,12 +623,7 @@ int main(int argc, char** argv)
 	logConfig.configureDefault();
 	logConfig.configureDefault();
 	logGlobal->info(NAME);
 	logGlobal->info(NAME);
 
 
-
 	handleCommandOptions(argc, argv);
 	handleCommandOptions(argc, argv);
-	if (cmdLineOptions.count("port"))
-		port = cmdLineOptions["port"].as<int>();
-	logNetwork->info("Port %d will be used.", port);
-
 	preinitDLL(console);
 	preinitDLL(console);
 	settings.init();
 	settings.init();
 	logConfig.configure();
 	logConfig.configure();
@@ -603,7 +637,7 @@ int main(int argc, char** argv)
 
 
 		try
 		try
 		{
 		{
-			while (!end2)
+			while(!serverShuttingDown)
 			{
 			{
 				server.start();
 				server.start();
 			}
 			}
@@ -612,7 +646,7 @@ int main(int argc, char** argv)
 		catch (boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
 		catch (boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
 		{
 		{
 			logNetwork->error(e.what());
 			logNetwork->error(e.what());
-			end2 = true;
+			serverShuttingDown = true;
 		}
 		}
 		catch (...)
 		catch (...)
 		{
 		{
@@ -630,11 +664,9 @@ int main(int argc, char** argv)
 	CAndroidVMHelper envHelper;
 	CAndroidVMHelper envHelper;
 	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
 	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
 #endif
 #endif
-	delete VLC;
-	VLC = nullptr;
+	vstd::clear_pointer(VLC);
 	CResourceHandler::clear();
 	CResourceHandler::clear();
-
-  return 0;
+	return 0;
 }
 }
 
 
 #ifdef VCMI_ANDROID
 #ifdef VCMI_ANDROID

+ 3 - 1
server/CVCMIServer.h

@@ -17,6 +17,7 @@ class CMapInfo;
 class CConnection;
 class CConnection;
 struct CPackForSelectionScreen;
 struct CPackForSelectionScreen;
 class CGameHandler;
 class CGameHandler;
+struct SharedMemory;
 
 
 namespace boost
 namespace boost
 {
 {
@@ -43,8 +44,10 @@ typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::s
 
 
 class CVCMIServer
 class CVCMIServer
 {
 {
+	ui16 port;
 	boost::asio::io_service *io;
 	boost::asio::io_service *io;
 	TAcceptor * acceptor;
 	TAcceptor * acceptor;
+	SharedMemory * shared;
 
 
 	CConnection *firstConnection;
 	CConnection *firstConnection;
 public:
 public:
@@ -73,7 +76,6 @@ public:
 	std::list<CPackForSelectionScreen*> toAnnounce;
 	std::list<CPackForSelectionScreen*> toAnnounce;
 	boost::recursive_mutex mx;
 	boost::recursive_mutex mx;
 
 
-	//std::vector<CMapInfo> maps;
 	TAcceptor *acceptor;
 	TAcceptor *acceptor;
 	TSocket *upcomingConnection;
 	TSocket *upcomingConnection;
 
 

+ 11 - 2
server/NetPacksServer.cpp

@@ -65,6 +65,12 @@ bool CloseServer::applyGh( CGameHandler *gh )
 	return true;
 	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;
@@ -277,8 +283,11 @@ bool CastAdvSpell::applyGh( CGameHandler *gh )
 
 
 bool PlayerMessage::applyGh( CGameHandler *gh )
 bool PlayerMessage::applyGh( CGameHandler *gh )
 {
 {
-	ERROR_IF_NOT(player);
-	if(gh->getPlayerAt(c) != player) ERROR_AND_RETURN;
+	if(!player.isSpectator()) // TODO: clearly not a great way to verify permissions
+	{
+		ERROR_IF_NOT(player);
+		if(gh->getPlayerAt(c) != player) ERROR_AND_RETURN;
+	}
 	gh->playerMessage(player,text, currObj);
 	gh->playerMessage(player,text, currObj);
 	return true;
 	return true;
 }
 }

+ 3 - 2
test/CMapEditManagerTest.cpp

@@ -115,9 +115,10 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
 	logGlobal->info("CMapEditManager_DrawTerrain_View start");
 	logGlobal->info("CMapEditManager_DrawTerrain_View start");
 	try
 	try
 	{
 	{
+		const ResourceID testMap("test/TerrainViewTest", EResType::MAP);
 		// Load maps and json config
 		// Load maps and json config
-		const auto originalMap = CMapService::loadMap("test/TerrainViewTest");
-		auto map = CMapService::loadMap("test/TerrainViewTest");
+		const auto originalMap = CMapService::loadMap(testMap);
+		auto map = CMapService::loadMap(testMap);
 		logGlobal->info("Loaded test map successfully.");
 		logGlobal->info("Loaded test map successfully.");
 
 
 		// Validate edit manager
 		// Validate edit manager