瀏覽代碼

Merge remote-tracking branch 'remotes/origin/develop' into feature/VCMIMapFormat1

Conflicts:
	lib/VCMI_lib.cbp
	lib/filesystem/CZipLoader.cpp
	lib/filesystem/CZipLoader.h
	lib/mapObjects/CGTownInstance.cpp
AlexVinS 9 年之前
父節點
當前提交
6e205a58b4
共有 100 個文件被更改,包括 1542 次插入682 次删除
  1. 1 0
      .gitignore
  2. 1 0
      AI/BattleAI/BattleAI.cbp
  3. 1 1
      AI/BattleAI/StdInc.cpp
  4. 1 0
      AI/EmptyAI/EmptyAI.cbp
  5. 1 1
      AI/EmptyAI/StdInc.cpp
  6. 1 1
      AI/EmptyAI/exp_funcs.cpp
  7. 1 0
      AI/FuzzyLite/FuzzyLite.cbp
  8. 1 1
      AI/StupidAI/StdInc.cpp
  9. 1 0
      AI/StupidAI/StupidAI.cbp
  10. 1 1
      AI/VCAI/AIUtility.cpp
  11. 1 1
      AI/VCAI/Goals.cpp
  12. 2 1
      AI/VCAI/VCAI.cbp
  13. 2 2
      AI/VCAI/VCAI.cpp
  14. 1 0
      client/CMT.cpp
  15. 1 1
      client/CPlayerInterface.cpp
  16. 9 9
      client/CPreGame.cpp
  17. 37 35
      client/Client.cpp
  18. 4 1
      client/VCMI_client.cbp
  19. 13 2
      client/battle/CBattleInterface.cpp
  20. 1 1
      client/windows/CAdvmapInterface.cpp
  21. 31 31
      client/windows/CSpellWindow.cpp
  22. 5 1
      config/creatures/special.json
  23. 3 1
      config/factions/castle.json
  24. 1 0
      config/factions/conflux.json
  25. 1 0
      config/factions/dungeon.json
  26. 3 1
      config/factions/fortress.json
  27. 1 0
      config/factions/inferno.json
  28. 2 1
      config/factions/necropolis.json
  29. 1 0
      config/factions/rampart.json
  30. 2 1
      config/factions/stronghold.json
  31. 1 0
      config/factions/tower.json
  32. 5 0
      config/schemas/faction.json
  33. 1 1
      editor/Editor.vcxproj
  34. 10 1
      launcher/StdInc.h
  35. 103 0
      launcher/VCMI_launcher.cbp
  36. 2 2
      launcher/jsonutils.cpp
  37. 1 0
      launcher/modManager/cmodlist.h
  38. 2 2
      launcher/modManager/cmodlistview_moc.h
  39. 5 5
      launcher/modManager/cmodmanager.cpp
  40. 4 2
      launcher/settingsView/csettingsview_moc.h
  41. 8 8
      lib/BattleState.cpp
  42. 34 29
      lib/CBattleCallback.cpp
  43. 6 5
      lib/CConfigHandler.cpp
  44. 4 3
      lib/CGameInfoCallback.cpp
  45. 2 3
      lib/CGameInfoCallback.h
  46. 5 5
      lib/CGameState.cpp
  47. 1 0
      lib/CMakeLists.txt
  48. 3 2
      lib/CModHandler.cpp
  49. 2 3
      lib/CObstacleInstance.cpp
  50. 2 2
      lib/CPathfinder.cpp
  51. 14 1
      lib/CTownHandler.cpp
  52. 15 1
      lib/CTownHandler.h
  53. 6 5
      lib/Connection.cpp
  54. 9 8
      lib/Connection.h
  55. 4 0
      lib/GameConstants.cpp
  56. 6 6
      lib/GameConstants.h
  57. 5 5
      lib/HeroBonus.h
  58. 1 1
      lib/Interprocess.h
  59. 19 2
      lib/NetPacksLib.cpp
  60. 1 1
      lib/ResourceSet.cpp
  61. 1 1
      lib/StdInc.cpp
  62. 1 1
      lib/StdInc.h
  63. 1 1
      lib/VCMIDirs.h
  64. 7 4
      lib/VCMI_lib.cbp
  65. 2 0
      lib/VCMI_lib.vcxproj
  66. 6 0
      lib/VCMI_lib.vcxproj.filters
  67. 6 6
      lib/filesystem/AdapterLoaders.cpp
  68. 84 84
      lib/filesystem/AdapterLoaders.h
  69. 7 21
      lib/filesystem/CFileInputStream.cpp
  70. 8 19
      lib/filesystem/CFileInputStream.h
  71. 4 4
      lib/filesystem/CFilesystemLoader.cpp
  72. 1 1
      lib/filesystem/CFilesystemLoader.h
  73. 27 27
      lib/filesystem/CZipLoader.cpp
  74. 9 11
      lib/filesystem/CZipLoader.h
  75. 364 0
      lib/filesystem/FileStream.cpp
  76. 53 0
      lib/filesystem/FileStream.h
  77. 5 5
      lib/filesystem/ISimpleResourceLoader.h
  78. 4 3
      lib/logging/CLogger.h
  79. 15 1
      lib/mapObjects/CGHeroInstance.cpp
  80. 5 4
      lib/mapObjects/CGHeroInstance.h
  81. 2 2
      lib/mapObjects/CGTownInstance.cpp
  82. 1 1
      lib/mapObjects/CGTownInstance.h
  83. 1 1
      lib/mapObjects/CObjectHandler.cpp
  84. 1 1
      lib/mapObjects/CObjectHandler.h
  85. 68 66
      lib/mapObjects/CQuest.cpp
  86. 16 2
      lib/mapObjects/CQuest.h
  87. 126 55
      lib/mapObjects/CRewardableObject.cpp
  88. 22 7
      lib/mapObjects/CRewardableObject.h
  89. 34 17
      lib/mapObjects/MiscObjects.cpp
  90. 4 0
      lib/mapObjects/MiscObjects.h
  91. 64 63
      lib/mapping/CDrawRoadsOperation.cpp
  92. 1 1
      lib/mapping/CMap.cpp
  93. 1 1
      lib/mapping/CMap.h
  94. 3 3
      lib/mapping/MapFormatH3M.cpp
  95. 2 2
      lib/minizip/ioapi.c
  96. 1 1
      lib/rmg/CRmgTemplateZone.cpp
  97. 35 29
      lib/spells/CDefaultSpellMechanics.cpp
  98. 1 1
      lib/spells/SpellMechanics.h
  99. 131 36
      server/CGameHandler.cpp
  100. 6 3
      server/CGameHandler.h

+ 1 - 0
.gitignore

@@ -14,6 +14,7 @@
 *.pro.user
 *.pro.user.*
 *.swp
+*.h.gch
 *~
 /CMakeLists.txt.user
 CMakeCache.txt

+ 1 - 0
AI/BattleAI/BattleAI.cbp

@@ -51,6 +51,7 @@
 			<Add option="-pedantic" />
 			<Add option="-Wextra" />
 			<Add option="-Wall" />
+			<Add option="-std=gnu++11" />
 			<Add option="-fexceptions" />
 			<Add option="-Wpointer-arith" />
 			<Add option="-Wno-switch" />

+ 1 - 1
AI/BattleAI/StdInc.cpp

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

+ 1 - 0
AI/EmptyAI/EmptyAI.cbp

@@ -49,6 +49,7 @@
 		<Compiler>
 			<Add option="-Wextra" />
 			<Add option="-Wall" />
+			<Add option="-std=gnu++11" />
 			<Add option="-fexceptions" />
 			<Add option="-Wpointer-arith" />
 			<Add option="-Wno-switch" />

+ 1 - 1
AI/EmptyAI/StdInc.cpp

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

+ 1 - 1
AI/EmptyAI/exp_funcs.cpp

@@ -16,4 +16,4 @@ extern "C" DLL_EXPORT void GetAiName(char* name)
 extern "C" DLL_EXPORT void GetNewAI(std::shared_ptr<CGlobalAI> &out)
 {
 	out = std::make_shared<CEmptyAI>();
-}
+}

+ 1 - 0
AI/FuzzyLite/FuzzyLite.cbp

@@ -52,6 +52,7 @@
 		<Compiler>
 			<Add option="-Wextra" />
 			<Add option="-Wall" />
+			<Add option="-std=gnu++11" />
 			<Add option="-fexceptions" />
 			<Add option="-Wpointer-arith" />
 			<Add option="-Wno-switch" />

+ 1 - 1
AI/StupidAI/StdInc.cpp

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

+ 1 - 0
AI/StupidAI/StupidAI.cbp

@@ -35,6 +35,7 @@
 			<Add option="-pedantic" />
 			<Add option="-Wextra" />
 			<Add option="-Wall" />
+			<Add option="-std=gnu++11" />
 			<Add option="-fexceptions" />
 			<Add option="-Wpointer-arith" />
 			<Add option="-Wno-switch" />

+ 1 - 1
AI/VCAI/AIUtility.cpp

@@ -360,7 +360,7 @@ bool canBeEmbarkmentPoint(const TerrainTile *t, bool fromWater)
 int3 whereToExplore(HeroPtr h)
 {
 	TimeCheck tc ("where to explore");
-	int radius = h->getSightRadious();
+	int radius = h->getSightRadius();
 	int3 hpos = h->visitablePos();
 
 	auto sm = ai->getCachedSectorMap(h);

+ 1 - 1
AI/VCAI/Goals.cpp

@@ -275,7 +275,7 @@ TSubgoal Win::whatToDoToAchieve()
 					// 0.85 -> radius now 2 tiles
 					// 0.95 -> 1 tile radius, position is fully known
 					// AFAIK H3 AI does something like this
-					int3 grailPos = cb->getGrailPos(ratio);
+					int3 grailPos = cb->getGrailPos(&ratio);
 					if(ratio > 0.99)
 					{
 						return sptr (Goals::DigAtTile(grailPos));

+ 2 - 1
AI/VCAI/VCAI.cbp

@@ -3,7 +3,7 @@
 	<FileVersion major="1" minor="6" />
 	<Project>
 		<Option title="VCAI" />
-		<Option pch_mode="2" />
+		<Option pch_mode="0" />
 		<Option compiler="gcc" />
 		<Build>
 			<Target title="Debug-win32">
@@ -57,6 +57,7 @@
 			<Add option="-pedantic" />
 			<Add option="-Wextra" />
 			<Add option="-Wall" />
+			<Add option="-std=gnu++11" />
 			<Add option="-fexceptions" />
 			<Add option="-Wpointer-arith" />
 			<Add option="-Wno-switch" />

+ 2 - 2
AI/VCAI/VCAI.cpp

@@ -2554,7 +2554,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h)
 
 int3 VCAI::explorationNewPoint(HeroPtr h)
 {
-	int radius = h->getSightRadious();
+	int radius = h->getSightRadius();
 	CCallback * cbp = cb.get();
 	const CGHeroInstance * hero = h.get();
 
@@ -2603,7 +2603,7 @@ int3 VCAI::explorationNewPoint(HeroPtr h)
 int3 VCAI::explorationDesperate(HeroPtr h)
 {
 	auto sm = getCachedSectorMap(h);
-	int radius = h->getSightRadious();
+	int radius = h->getSightRadius();
 
 	std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
 	tiles.resize(radius);

+ 1 - 0
client/CMT.cpp

@@ -7,6 +7,7 @@
 #include "mapHandler.h"
 
 #include "../lib/filesystem/Filesystem.h"
+#include "../lib/filesystem/FileStream.h"
 #include "CPreGame.h"
 #include "windows/CCastleInterface.h"
 #include "../lib/CConsoleHandler.h"

+ 1 - 1
client/CPlayerInterface.cpp

@@ -2170,7 +2170,7 @@ void CPlayerInterface::showPuzzleMap()
 
 	//TODO: interface should not know the real position of Grail...
 	double ratio = 0;
-	int3 grailPos = cb->getGrailPos(ratio);
+	int3 grailPos = cb->getGrailPos(&ratio);
 
 	GH.pushInt(new CPuzzleWindow(grailPos, ratio));
 }

+ 9 - 9
client/CPreGame.cpp

@@ -307,10 +307,10 @@ void CMenuScreen::switchToTab(size_t index)
 //funciton for std::string -> std::function conversion for main menu
 static std::function<void()> genCommand(CMenuScreen* menu, std::vector<std::string> menuType, const std::string &string)
 {
-	static const std::vector<std::string> commandType  = 
+	static const std::vector<std::string> commandType  =
 		{"to", "campaigns", "start", "load", "exit", "highscores"};
 
-	static const std::vector<std::string> gameType = 
+	static const std::vector<std::string> gameType =
 		{"single", "multi", "campaign", "tutorial"};
 
 	std::list<std::string> commands;
@@ -554,8 +554,8 @@ CGPreGame *CGPreGame::create()
 {
 	if(!CGP)
 		CGP = new CGPreGame();
-		
-	GH.terminate_cond.set(false);		
+
+	GH.terminate_cond.set(false);
 	return CGP;
 }
 
@@ -1151,7 +1151,7 @@ void SelectionTab::parseGames(const std::unordered_set<ResourceID> &files, bool
 			lf >> *(mapInfo.mapHeader.get()) >> mapInfo.scenarioOpts;
 			mapInfo.fileURI = file.getName();
 			mapInfo.countPlayers();
-			std::time_t time = CFileInfo(*CResourceHandler::get()->getResourceName(file)).getDate();
+			std::time_t time = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(file));
 			mapInfo.date = std::asctime(std::localtime(&time));
 
 			// If multi mode then only multi games, otherwise single
@@ -1362,9 +1362,9 @@ void SelectionTab::select( int position )
 
 	if(txt)
 	{
-		std::string filename = *CResourceHandler::get("local")->getResourceName(
+		auto filename = *CResourceHandler::get("local")->getResourceName(
 								   ResourceID(curItems[py]->fileURI, EResType::CLIENT_SAVEGAME));
-		txt->setText(CFileInfo(filename).getBaseName());
+		txt->setText(filename.stem().string());
 	}
 
 	onSelect(curItems[py]);
@@ -1487,8 +1487,8 @@ void SelectionTab::printMaps(SDL_Surface *to)
 		}
 		else
 		{
-			name = CFileInfo(*CResourceHandler::get("local")->getResourceName(
-								 ResourceID(currentItem->fileURI, EResType::CLIENT_SAVEGAME))).getBaseName();
+			name = CResourceHandler::get("local")->getResourceName(
+								 ResourceID(currentItem->fileURI, EResType::CLIENT_SAVEGAME))->stem().string();
 		}
 
 		//print name

+ 37 - 35
client/Client.cpp

@@ -59,8 +59,8 @@ template <typename T> class CApplyOnCL;
 class CBaseForCLApply
 {
 public:
-	virtual void applyOnClAfter(CClient *cl, void *pack) const =0; 
-	virtual void applyOnClBefore(CClient *cl, void *pack) const =0; 
+	virtual void applyOnClAfter(CClient *cl, void *pack) const =0;
+	virtual void applyOnClBefore(CClient *cl, void *pack) const =0;
 	virtual ~CBaseForCLApply(){}
 
 	template<typename U> static CBaseForCLApply *getApplier(const U * t=nullptr)
@@ -144,7 +144,7 @@ void CClient::waitForMoveAndSend(PlayerColor color)
 		{
 			logNetwork->traceStream() << "Send battle action to server: " << ba;
 			MakeAction temp_action(ba);
-			sendRequest(&temp_action, color);			
+			sendRequest(&temp_action, color);
 		}
 		return;
 	}
@@ -169,8 +169,8 @@ void CClient::run()
 		while(!terminate)
 		{
 			CPack *pack = serv->retreivePack(); //get the package from the server
-			
-			if (terminate) 
+
+			if (terminate)
 			{
 				vstd::clear_pointer(pack);
 				break;
@@ -178,10 +178,10 @@ void CClient::run()
 
 			handlePack(pack);
 		}
-	} 
+	}
 	//catch only asio exceptions
 	catch (const boost::system::system_error& e)
-	{	
+	{
         logNetwork->errorStream() << "Lost connection to server, ending listening thread!";
         logNetwork->errorStream() << e.what();
 		if(!terminate) //rethrow (-> boom!) only if closing connection was unexpected
@@ -219,18 +219,20 @@ void CClient::endGame( bool closeConnection /*= true*/ )
 	GH.curInt = nullptr;
 	{
 		boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
-        logNetwork->infoStream() << "Ending current game!";
+		logNetwork->infoStream() << "Ending current game!";
 		if(GH.topInt())
+		{
 			GH.topInt()->deactivate();
+		}
 		GH.listInt.clear();
 		GH.objsToBlit.clear();
 		GH.statusbar = nullptr;
-        logNetwork->infoStream() << "Removed GUI.";
+		logNetwork->infoStream() << "Removed GUI.";
 
 		vstd::clear_pointer(const_cast<CGameInfo*>(CGI)->mh);
 		vstd::clear_pointer(gs);
 
-        logNetwork->infoStream() << "Deleted mapHandler and gameState.";
+		logNetwork->infoStream() << "Deleted mapHandler and gameState.";
 		LOCPLINT = nullptr;
 	}
 
@@ -238,9 +240,9 @@ void CClient::endGame( bool closeConnection /*= true*/ )
 	battleints.clear();
 	callbacks.clear();
 	battleCallbacks.clear();
-    logNetwork->infoStream() << "Deleted playerInts.";
-
-    logNetwork->infoStream() << "Client stopped.";
+	CGObelisk::reset();
+	logNetwork->infoStream() << "Deleted playerInts.";
+	logNetwork->infoStream() << "Client stopped.";
 }
 
 #if 1
@@ -267,8 +269,8 @@ void CClient::loadGame(const std::string & fname, const bool server, const std::
 	std::unique_ptr<CLoadFile> loader;
 	try
 	{
-		std::string clientSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::CLIENT_SAVEGAME));
-		std::string controlServerSaveName;
+		boost::filesystem::path clientSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::CLIENT_SAVEGAME));
+		boost::filesystem::path controlServerSaveName;
 
 		if (CResourceHandler::get("local")->existsResource(ResourceID(fname, EResType::SERVER_SAVEGAME)))
 		{
@@ -276,8 +278,8 @@ void CClient::loadGame(const std::string & fname, const bool server, const std::
 		}
 		else// create entry for server savegame. Triggered if save was made after launch and not yet present in res handler
 		{
-			controlServerSaveName = clientSaveName.substr(0, clientSaveName.find_last_of(".")) + ".vsgm1";
-			CResourceHandler::get("local")->createResource(controlServerSaveName, true);
+			controlServerSaveName = boost::filesystem::path(clientSaveName).replace_extension(".vsgm1");
+			CResourceHandler::get("local")->createResource(controlServerSaveName.string(), true);
 		}
 
 		if(clientSaveName.empty())
@@ -320,7 +322,7 @@ void CClient::loadGame(const std::string & fname, const bool server, const std::
          *serv << ui8(3) << ui8(loadNumPlayers); //load game; one client if single-player
          *serv << fname;
          *serv >> pom8;
-         if(pom8) 
+         if(pom8)
               throw std::runtime_error("Server cannot open the savegame!");
          else
               logNetwork->infoStream() << "Server opened savegame properly.";
@@ -376,7 +378,7 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 {
 	enum {SINGLE, HOST, GUEST} networkMode = SINGLE;
 
-	if (con == nullptr) 
+	if (con == nullptr)
 	{
 		CServerHandler sh;
 		serv = sh.connectToServer();
@@ -459,7 +461,7 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 				logNetwork->infoStream() << boost::format("Player %s will be lead by %s") % color % AiToGive;
 				installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color);
 			}
-			else 
+			else
 			{
 				installNewPlayerInterface(std::make_shared<CPlayerInterface>(color), color);
 				humanPlayers++;
@@ -502,7 +504,7 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 // 		nm->giveActionCB(this);
 // 		nm->giveInfoCB(this);
 // 		nm->init();
-// 
+//
 // 		erm = nm; //something tells me that there'll at most one module and it'll be ERM
 // 	}
 }
@@ -510,7 +512,7 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 void CClient::serialize(COSer & h, const int version)
 {
 	assert(h.saving);
-	h & hotSeat;	
+	h & hotSeat;
 	{
 		ui8 players = playerint.size();
 		h & players;
@@ -520,7 +522,7 @@ void CClient::serialize(COSer & h, const int version)
 			LOG_TRACE_PARAMS(logGlobal, "Saving player %s interface", i->first);
 			assert(i->first == i->second->playerID);
 			h & i->first & i->second->dllName & i->second->human;
-			i->second->saveGame(h, version); 			
+			i->second->saveGame(h, version);
 		}
 	}
 }
@@ -536,7 +538,7 @@ void CClient::serialize(CISer & h, const int version)
 		for(int i=0; i < players; i++)
 		{
 			std::string dllname;
-			PlayerColor pid; 
+			PlayerColor pid;
 			bool isHuman = false;
 
 			h & pid & dllname & isHuman;
@@ -548,7 +550,7 @@ void CClient::serialize(CISer & h, const int version)
 				if(pid == PlayerColor::NEUTRAL)
 				{
 					installNewBattleInterface(CDynLibHandler::getNewBattleAI(dllname), pid);
-					//TODO? consider serialization 
+					//TODO? consider serialization
 					continue;
 				}
 				else
@@ -589,7 +591,7 @@ void CClient::serialize(COSer & h, const int version, const std::set<PlayerColor
 			LOG_TRACE_PARAMS(logGlobal, "Saving player %s interface", i->first);
 			assert(i->first == i->second->playerID);
 			h & i->first & i->second->dllName & i->second->human;
-			i->second->saveGame(h, version); 			
+			i->second->saveGame(h, version);
 		}
 	}
 }
@@ -605,7 +607,7 @@ void CClient::serialize(CISer & h, const int version, const std::set<PlayerColor
 		for(int i=0; i < players; i++)
 		{
 			std::string dllname;
-			PlayerColor pid; 
+			PlayerColor pid;
 			bool isHuman = false;
 
 			h & pid & dllname & isHuman;
@@ -618,7 +620,7 @@ void CClient::serialize(CISer & h, const int version, const std::set<PlayerColor
 				{
                     if(playerIDs.count(pid))
                        installNewBattleInterface(CDynLibHandler::getNewBattleAI(dllname), pid);
-					//TODO? consider serialization 
+					//TODO? consider serialization
 					continue;
 				}
 				else
@@ -640,7 +642,7 @@ void CClient::serialize(CISer & h, const int version, const std::set<PlayerColor
             if(playerIDs.count(pid))
                  installNewPlayerInterface(nInt, pid);
 
-            nInt->loadGame(h, version);       
+            nInt->loadGame(h, version);
 		}
 
 		if(playerIDs.count(PlayerColor::NEUTRAL))
@@ -714,7 +716,7 @@ void CClient::battleStarted(const BattleInfo * info)
 {
 	for(auto &battleCb : battleCallbacks)
 	{
-		if(vstd::contains_if(info->sides, [&](const SideInBattle& side) {return side.color == battleCb.first; })  
+		if(vstd::contains_if(info->sides, [&](const SideInBattle& side) {return side.color == battleCb.first; })
 			||  battleCb.first >= PlayerColor::PLAYER_LIMIT)
 		{
 			battleCb.second->setBattle(info);
@@ -742,7 +744,7 @@ void CClient::battleStarted(const BattleInfo * info)
 	{
 		boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
 		auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
-			Rect((screen->w - 800)/2, 
+			Rect((screen->w - 800)/2,
 			     (screen->h - 600)/2, 800, 600), att, def);
 
 		GH.pushInt(bi);
@@ -805,7 +807,7 @@ void CClient::commenceTacticPhaseForInt(std::shared_ptr<CBattleGameInterface> ba
 	catch(...)
 	{
 		handleException();
-	}	
+	}
 }
 
 void CClient::invalidatePaths()
@@ -889,7 +891,7 @@ void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> ba
 	boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
 	PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);
 
-	if(!color) 
+	if(!color)
 		privilagedBattleEventReceivers.push_back(battleInterface);
 
 	battleints[colorUsed] = battleInterface;
@@ -961,7 +963,7 @@ CConnection * CServerHandler::connectToServer()
 #endif
 
 	th.update(); //put breakpoint here to attach to server before it does something stupid
-    
+
 	CConnection *ret = justConnectToServer(settings["server"]["server"].String(), port);
 
 	if(verbose)
@@ -1033,7 +1035,7 @@ CConnection * CServerHandler::justConnectToServer(const std::string &host, const
 		try
 		{
             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,
 									NAME);
 		}

+ 4 - 1
client/VCMI_client.cbp

@@ -3,7 +3,7 @@
 	<FileVersion major="1" minor="6" />
 	<Project>
 		<Option title="VCMI_client" />
-		<Option pch_mode="2" />
+		<Option pch_mode="0" />
 		<Option compiler="gcc" />
 		<Build>
 			<Target title="Debug-win32">
@@ -62,6 +62,7 @@
 		<Compiler>
 			<Add option="-Wextra" />
 			<Add option="-Wall" />
+			<Add option="-std=gnu++11" />
 			<Add option="-fexceptions" />
 			<Add option="-Wpointer-arith" />
 			<Add option="-Wno-switch" />
@@ -70,7 +71,9 @@
 			<Add option="-Wno-overloaded-virtual" />
 			<Add option="-fpermissive" />
 			<Add option="-DBOOST_THREAD_USE_LIB" />
+			<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
 			<Add option="-D_WIN32_WINNT=0x0501" />
+			<Add option="-D_WIN32" />
 			<Add directory="$(#boost.include)" />
 			<Add directory="../include" />
 			<Add directory="../client" />

+ 13 - 2
client/battle/CBattleInterface.cpp

@@ -2163,7 +2163,15 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case RISE_DEMONS:
 				if (shere && ourStack && !shere->alive())
-					legalAction = true;
+				{
+					if(!(shere->hasBonusOfType(Bonus::UNDEAD) 
+						|| shere->hasBonusOfType(Bonus::NON_LIVING) 
+						|| vstd::contains(shere->state, EBattleStackState::SUMMONED)
+						|| vstd::contains(shere->state, EBattleStackState::CLONED)
+						|| shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
+						))
+						legalAction = true;
+				}					
 				break;
 		}
 		if (legalAction)
@@ -2320,7 +2328,10 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case RISE_DEMONS:
 				cursorType = ECursor::SPELLBOOK;
-				realizeAction = [=]{ giveCommand(Battle::DAEMON_SUMMONING, myNumber, activeStack->ID); };
+				realizeAction = [=]
+				{ 
+					giveCommand(Battle::DAEMON_SUMMONING, myNumber, activeStack->ID); 
+				};
 				break;
 			case CATAPULT:
 				cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;

+ 1 - 1
client/windows/CAdvmapInterface.cpp

@@ -1069,7 +1069,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
 			LOCPLINT->proposeLoadingGame();
 		return;
 	case SDLK_s:
-		if(isActive())
+		if(isActive() && key.type == SDL_KEYUP)
 			GH.pushInt(new CSavingScreen(CPlayerInterface::howManyPeople > 1));
 		return;
 	case SDLK_d:

+ 31 - 31
client/windows/CSpellWindow.cpp

@@ -6,6 +6,7 @@
 #include "CAdvmapInterface.h"
 #include "GUIClasses.h"
 #include "InfoWindows.h"
+#include "CCastleInterface.h"
 
 #include "../CBitmapHandler.h"
 #include "../CDefHandler.h"
@@ -109,7 +110,7 @@ CSpellWindow::CSpellWindow(const SDL_Rect &, const CGHeroInstance * _myHero, CPl
 		Uint8 *sitesPerOurTab = s.combatSpell ? sitesPerTabBattle : sitesPerTabAdv;
 
 		++sitesPerOurTab[4];
-		
+
 		s.forEachSchool([&sitesPerOurTab](const SpellSchoolInfo & school, bool & stop)
 		{
 			++sitesPerOurTab[(ui8)school.id];
@@ -156,9 +157,9 @@ CSpellWindow::CSpellWindow(const SDL_Rect &, const CGHeroInstance * _myHero, CPl
 
 	leftCorner = BitmapHandler::loadBitmap("SpelTrnL.bmp", true);
 	rightCorner = BitmapHandler::loadBitmap("SpelTrnR.bmp", true);
-	
+
 	spells = new CAnimation("Spells.def");
-		
+
 	spellTab = CDefHandler::giveDef("SpelTab.def");
 	schools = CDefHandler::giveDef("Schools.def");
 	schoolBorders[0] = CDefHandler::giveDef("SplevA.def");
@@ -380,9 +381,9 @@ public:
 		if(A.level<B.level)
 			return true;
 		if(A.level>B.level)
-			return false;		
-		
-		
+			return false;
+
+
 		for(ui8 schoolId = 0; schoolId < 4; schoolId++)
 		{
 			if(A.school.at((ESpellSchool)schoolId) && !B.school.at((ESpellSchool)schoolId))
@@ -390,7 +391,7 @@ public:
 			if(!A.school.at((ESpellSchool)schoolId) && B.school.at((ESpellSchool)schoolId))
 				return false;
 		}
-		
+
 		return A.name < B.name;
 	}
 } spellsorter;
@@ -400,8 +401,8 @@ void CSpellWindow::computeSpellsPerArea()
 	std::vector<SpellID> spellsCurSite;
 	for(const SpellID & spellID : mySpells)
 	{
-		CSpell * s = spellID.toSpell(); 
-		
+		CSpell * s = spellID.toSpell();
+
 		if(s->combatSpell ^ !battleSpellsOnly
 			&& ((selectedTab == 4) || (s->school[(ESpellSchool)selectedTab]))
 			)
@@ -533,7 +534,7 @@ void CSpellWindow::keyPressed(const SDL_KeyboardEvent & key)
 	{
 		fexitb();
 		return;
-	}		
+	}
 
 	if(key.state == SDL_PRESSED)
 	{
@@ -622,10 +623,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 			owner->myInt->showInfoDialog(std::string(msgBuf));
 			return;
 		}
-
 		//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
 		if((sp->isCombatSpell() && !owner->myInt->battleInt)
-			|| (sp->isAdventureSpell() && owner->myInt->battleInt))
+		   || (sp->isAdventureSpell() && (owner->myInt->battleInt || owner->myInt->castleInt)))
 		{
 			std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell, 0));
 			LOCPLINT->showInfoDialog(sp->getLevelInfo(schoolLevel).description, hlp);
@@ -705,26 +705,26 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 		{
 			const CGHeroInstance *h = owner->myHero;
 			GH.popInt(owner);
-			
-			auto guard = vstd::makeScopeGuard([this]								
+
+			auto guard = vstd::makeScopeGuard([this]
 			{
 				(LOCPLINT->battleInt ? owner->myInt->spellbookSettings.spellbookLastTabBattle : owner->myInt->spellbookSettings.spellbookLastTabAdvmap) = owner->selectedTab;
-				(LOCPLINT->battleInt ? owner->myInt->spellbookSettings.spellbookLastPageBattle : owner->myInt->spellbookSettings.spellbokLastPageAdvmap) = owner->currentPage;				
+				(LOCPLINT->battleInt ? owner->myInt->spellbookSettings.spellbookLastPageBattle : owner->myInt->spellbookSettings.spellbokLastPageAdvmap) = owner->currentPage;
 				delete owner;
-			}); 
+			});
 
 			if(mySpell == SpellID::TOWN_PORTAL)
 			{
 				//special case
 				//todo: move to mechanics
-				
+
 				std::vector <int> availableTowns;
 				std::vector <const CGTownInstance*> Towns = LOCPLINT->cb->getTownsInfo(false);
 
 				vstd::erase_if(Towns, [this](const CGTownInstance * t)
 				{
-					const auto relations = owner->myInt->cb->getPlayerRelations(t->tempOwner, owner->myInt->playerID);	
-					return relations == PlayerRelations::ENEMIES; 				
+					const auto relations = owner->myInt->cb->getPlayerRelations(t->tempOwner, owner->myInt->playerID);
+					return relations == PlayerRelations::ENEMIES;
 				});
 
 				if (Towns.empty())
@@ -776,13 +776,13 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 							availableTowns.push_back(t->id.getNum());//add to the list
 						}
 					}
-					
+
 					auto castTownPortal = [h](int townId)
 					{
 						const CGTownInstance * dest = LOCPLINT->cb->getTown(ObjectInstanceID(townId));
-						LOCPLINT->cb->castSpell(h, SpellID::TOWN_PORTAL, dest->visitablePos());					
+						LOCPLINT->cb->castSpell(h, SpellID::TOWN_PORTAL, dest->visitablePos());
 					};
-					
+
 					if (availableTowns.empty())
 						LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
 					else
@@ -791,13 +791,13 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 							CGI->generaltexth->jktexts[40], CGI->generaltexth->jktexts[41],
 							castTownPortal));
 				}
-				return;	
+				return;
 			}
-			
+
 			if(mySpell == SpellID::SUMMON_BOAT)
 			{
 				//special case
-				//todo: move to mechanics				
+				//todo: move to mechanics
 				int3 pos = h->bestLocation();
 				if(pos.x < 0)
 				{
@@ -805,10 +805,10 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 					return;
 				}
 			}
-			
+
 			if(sp->getTargetType() == CSpell::LOCATION)
 			{
-				adventureInt->enterCastingMode(sp);		
+				adventureInt->enterCastingMode(sp);
 			}
 			else if(sp->getTargetType() == CSpell::NO_TARGET)
 			{
@@ -817,7 +817,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 			else
 			{
 				logGlobal->error("Invalid spell target type");
-			}			
+			}
 		}
 	}
 }
@@ -865,11 +865,11 @@ void CSpellWindow::SpellArea::showAll(SDL_Surface * to)
 	if(mySpell < 0)
 		return;
 
-	const CSpell * spell = mySpell.toSpell();	
+	const CSpell * spell = mySpell.toSpell();
 	owner->spells->load(mySpell);
-	
+
 	IImage * icon = owner->spells->getImage(mySpell,0,false);
-	
+
 	if(icon != nullptr)
 		icon->draw(to, pos.x, pos.y);
 	else

+ 5 - 1
config/creatures/special.json

@@ -112,7 +112,11 @@
 		"index": 149,
 		"level": 0,
 		"faction": "neutral",
-		"abilities": { "shooter" : { "type" : "SHOOTER" } },
+		"abilities":
+		{
+			"shooter" : { "type" : "SHOOTER" },
+			"ignoreDefence" : { "type" : "ENEMY_DEFENCE_REDUCTION", "val" : 100 }
+		},
 		"graphics" :
 		{
 			"animation": "CLCBOW.DEF" // needed to pass validation, never used

+ 3 - 1
config/factions/castle.json

@@ -146,7 +146,9 @@
 			"mageGuild" : 4,
 			"warMachine" : "ballista",
 			"moatDamage" : 70,
-			"primaryResource": "ore",
+			"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
+			// primaryResource not specified so town get both Wood and Ore for resource bonus
+
 			"buildings" :
 			{
 				"mageGuild1":     { "id" : 0 },

+ 1 - 0
config/factions/conflux.json

@@ -151,6 +151,7 @@
 			"primaryResource" : "mercury",
 			"warMachine" : "ballista",
 			"moatDamage" : 70,
+			"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
 
 			"buildings" :
 			{

+ 1 - 0
config/factions/dungeon.json

@@ -146,6 +146,7 @@
 			"primaryResource" : "sulfur",
 			"warMachine" : "ballista",
 			"moatDamage" : 90,
+			"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
 
 			"buildings" :
 			{

+ 3 - 1
config/factions/fortress.json

@@ -146,7 +146,9 @@
 			"mageGuild" : 3,
 			"warMachine" : "firstAidTent",
 			"moatDamage" : 90,
-			"primaryResource":"ore",
+			"moatHexes" : [ 10, 11, 27, 28, 43, 44, 60, 61, 76, 77, 94, 110, 111, 128, 129, 145, 146, 163, 164, 180, 181 ],
+			// primaryResource not specified so town get both Wood and Ore for resource bonus
+
 			"buildings" :
 			{
 				"mageGuild1":     { "id" : 0 },

+ 1 - 0
config/factions/inferno.json

@@ -147,6 +147,7 @@
 			"primaryResource" : "mercury",
 			"warMachine" : "ammoCart",
 			"moatDamage" : 90,
+			"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
 
 			"buildings" :
 			{

+ 2 - 1
config/factions/necropolis.json

@@ -150,7 +150,8 @@
 			"mageGuild" : 5,
 			"warMachine" : "firstAidTent",
 			"moatDamage" : 70,
-			"primaryResource": "ore",
+			"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
+			// primaryResource not specified so town get both Wood and Ore for resource bonus
 
 			"buildings" :
 			{

+ 1 - 0
config/factions/rampart.json

@@ -151,6 +151,7 @@
 			"primaryResource" : "crystal",
 			"warMachine" : "firstAidTent",
 			"moatDamage" : 70,
+			"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
 
 			"buildings" :
 			{

+ 2 - 1
config/factions/stronghold.json

@@ -118,7 +118,6 @@
 			"defaultTavern" : 5,
 			"tavernVideo" : "TAVERN.BIK",
 			"guildBackground" : "TPMAGE.bmp",
-			"primaryResource": "ore",
 			"townBackground": "TBSTBACK.bmp",
 			"guildWindow": "TPMAGEST.bmp",
 			"buildingsIcons": "HALLSTRN.DEF",
@@ -145,6 +144,8 @@
 			"mageGuild" : 3,
 			"warMachine" : "ammoCart",
 			"moatDamage" : 70,
+			"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
+			// primaryResource not specified so town get both Wood and Ore for resource bonus
 
 			"buildings" :
 			{

+ 1 - 0
config/factions/tower.json

@@ -146,6 +146,7 @@
 			"mageGuild" : 5,
 			"warMachine" : "ammoCart",
 			"moatDamage" : 0, //TODO: minefield
+			"moatHexes" : [ 11, 28, 44, 61, 77, 111, 129, 146, 164, 181 ],
 
 			"buildings" :
 			{

+ 5 - 0
config/schemas/faction.json

@@ -230,6 +230,11 @@
 					"type":"number",
 					"description": "Damage dealt to creature that entered town moat during siege"
 				},
+				"moatHexes": {
+					"type" : "array",
+					"description" : "Numbers of battlefield hexes affected by moat during siege",
+					"items" : { "type" : "number" }
+				},
 				"musicTheme": {
 					"type":"string",
 					"description": "Path to town music theme",

+ 1 - 1
editor/Editor.vcxproj

@@ -73,7 +73,7 @@
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
       <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
       <PrecompiledHeader>Use</PrecompiledHeader>
-      <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+      <PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
       <PrecompiledHeaderOutputFile>$(IntDir)$(TargetName).pch</PrecompiledHeaderOutputFile>
     </ClCompile>
     <Link>

+ 10 - 1
launcher/StdInc.h

@@ -17,4 +17,13 @@ inline QString pathToQString(const boost::filesystem::path & path)
 #else
 	return QString::fromStdString(path.string());
 #endif
-}
+}
+
+inline boost::filesystem::path qstringToPath(const QString & path)
+{
+#ifdef VCMI_WINDOWS
+	return boost::filesystem::path(path.toStdWString());
+#else
+	return boost::filesystem::path(path.toUtf8().data());
+#endif
+}

+ 103 - 0
launcher/VCMI_launcher.cbp

@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<CodeBlocks_project_file>
+	<FileVersion major="1" minor="6" />
+	<Project>
+		<Option title="VCMI_launcher" />
+		<Option pch_mode="0" />
+		<Option compiler="gcc" />
+		<Build>
+			<Target title="Debug Win32">
+				<Option output="../VCMI_launcher" prefix_auto="1" extension_auto="1" />
+				<Option working_dir="../" />
+				<Option object_output=".objs/debug" />
+				<Option type="0" />
+				<Option compiler="gcc" />
+				<Compiler>
+					<Add option="-g" />
+				</Compiler>
+			</Target>
+			<Target title="Release Win32">
+				<Option output="../VCMI_launcher" prefix_auto="1" extension_auto="1" />
+				<Option working_dir="../" />
+				<Option object_output=".objs/release" />
+				<Option type="0" />
+				<Option compiler="gcc" />
+				<Compiler>
+					<Add option="-O3" />
+					<Add option="-flto" />
+				</Compiler>
+				<Linker>
+					<Add option="-s" />
+					<Add option="-flto" />
+				</Linker>
+			</Target>
+		</Build>
+		<Compiler>
+			<Add option="-Wextra" />
+			<Add option="-Wall" />
+			<Add option="-std=gnu++11" />
+			<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
+			<Add option="-D_WIN32" />
+			<Add directory="." />
+			<Add directory="../include" />
+			<Add directory="$(#boost.include)" />
+			<Add directory="$(#qt.include)" />
+			<Add directory="$(#qt.include)/QtGui" />
+			<Add directory="$(#qt.include)/QtCore" />
+			<Add directory="$(#qt.include)/QtWidgets" />
+		</Compiler>
+		<Linker>
+			<Add option="-lVCMI_lib" />
+			<Add option="-lQt5Core" />
+			<Add option="-lQt5Gui" />
+			<Add option="-lQt5Widgets" />
+			<Add option="-lQt5Network" />
+			<Add option="-lboost_filesystem$(#boost.libsuffix)" />
+			<Add option="-lboost_system$(#boost.libsuffix)" />
+			<Add directory="../" />
+			<Add directory="$(#qt.lib)" />
+			<Add directory="$(#boost.lib)" />
+		</Linker>
+		<Unit filename="StdInc.cpp" />
+		<Unit filename="StdInc.h">
+			<Option compile="1" />
+			<Option weight="0" />
+		</Unit>
+		<Unit filename="VCMI_launcher.rc">
+			<Option compilerVar="WINDRES" />
+		</Unit>
+		<Unit filename="jsonutils.cpp" />
+		<Unit filename="jsonutils.h" />
+		<Unit filename="launcherdirs.cpp" />
+		<Unit filename="launcherdirs.h" />
+		<Unit filename="main.cpp" />
+		<Unit filename="mainwindow_moc.cpp" />
+		<Unit filename="mainwindow_moc.h" />
+		<Unit filename="modManager/cdownloadmanager_moc.cpp" />
+		<Unit filename="modManager/cdownloadmanager_moc.h" />
+		<Unit filename="modManager/cmodlist.cpp" />
+		<Unit filename="modManager/cmodlist.h" />
+		<Unit filename="modManager/cmodlistmodel_moc.cpp" />
+		<Unit filename="modManager/cmodlistmodel_moc.h" />
+		<Unit filename="modManager/cmodlistview_moc.cpp" />
+		<Unit filename="modManager/cmodlistview_moc.h" />
+		<Unit filename="modManager/cmodmanager.cpp" />
+		<Unit filename="modManager/cmodmanager.h" />
+		<Unit filename="modManager/imageviewer_moc.cpp" />
+		<Unit filename="modManager/imageviewer_moc.h" />
+		<Unit filename="modManager/qrc_cdownloadmanager_moc.cpp" />
+		<Unit filename="modManager/qrc_cmodlistmodel_moc.cpp" />
+		<Unit filename="modManager/qrc_cmodlistview_moc.cpp" />
+		<Unit filename="modManager/qrc_imageviewer_moc.cpp" />
+		<Unit filename="qrc_mainwindow_moc.cpp" />
+		<Unit filename="settingsView/csettingsview_moc.cpp" />
+		<Unit filename="settingsView/csettingsview_moc.h" />
+		<Unit filename="settingsView/qrc_csettingsview_moc.cpp" />
+		<Extensions>
+			<code_completion />
+			<envvars />
+			<debugger />
+			<lib_finder disable_auto="1" />
+		</Extensions>
+	</Project>
+</CodeBlocks_project_file>

+ 2 - 2
launcher/jsonutils.cpp

@@ -1,5 +1,6 @@
 #include "StdInc.h"
 #include "jsonutils.h"
+#include "../lib/filesystem/FileStream.h"
 
 static QVariantMap JsonToMap(const JsonMap & json)
 {
@@ -96,8 +97,7 @@ JsonNode toJson(QVariant object)
 
 void JsonToFile(QString filename, QVariant object)
 {
-	std::ofstream file(filename.toUtf8().data(), std::ofstream::binary);
-
+	FileStream file(qstringToPath(filename), std::ios::out | std::ios_base::binary);
 	file << toJson(object);
 }
 

+ 1 - 0
launcher/modManager/cmodlist.h

@@ -2,6 +2,7 @@
 
 #include <QVariantMap>
 #include <QVariant>
+#include <QVector>
 
 class JsonNode;
 

+ 2 - 2
launcher/modManager/cmodlistview_moc.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../../Global.h"
+#include "../StdInc.h"
 #include "../../lib/CConfigHandler.h"
 
 namespace Ui {
@@ -55,7 +55,7 @@ class CModListView : public QWidget
 public:
 	explicit CModListView(QWidget *parent = 0);
 	~CModListView();
-	
+
 	void showModInfo();
 	void hideModInfo();
 	void loadScreenshots();

+ 5 - 5
launcher/modManager/cmodmanager.cpp

@@ -11,7 +11,7 @@
 
 static QString detectModArchive(QString path, QString modName)
 {
-	auto files = ZipArchive::listFiles(path.toUtf8().data());
+	auto files = ZipArchive::listFiles(qstringToPath(path));
 
 	QString modDirName;
 
@@ -69,8 +69,8 @@ void CModManager::loadMods()
 		ResourceID resID(CModInfo::getModFile(modname));
 		if (CResourceHandler::get()->existsResource(resID))
 		{
-			std::string name = *CResourceHandler::get()->getResourceName(resID);
-			auto mod = JsonUtils::JsonFromFile(QString::fromUtf8(name.c_str()));
+			boost::filesystem::path name = *CResourceHandler::get()->getResourceName(resID);
+			auto mod = JsonUtils::JsonFromFile(pathToQString(name));
 			localMods.insert(QString::fromUtf8(modname.c_str()).toLower(), mod);
 		}
 	}
@@ -243,7 +243,7 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
 	if (!modDirName.size())
 		return addError(modname, "Mod archive is invalid or corrupted");
 
-	if (!ZipArchive::extract(archivePath.toUtf8().data(), destDir.toUtf8().data()))
+	if (!ZipArchive::extract(qstringToPath(archivePath), qstringToPath(destDir)))
 	{
 		QDir(destDir + modDirName).removeRecursively();
 		return addError(modname, "Failed to extract mod data");
@@ -262,7 +262,7 @@ bool CModManager::doUninstallMod(QString modname)
 {
 	ResourceID resID(std::string("Mods/") + modname.toUtf8().data(), EResType::DIRECTORY);
 	// Get location of the mod, in case-insensitive way
-	QString modDir = QString::fromUtf8(CResourceHandler::get()->getResourceName(resID)->c_str());
+	QString modDir = pathToQString(*CResourceHandler::get()->getResourceName(resID));
 
 	if (!QDir(modDir).exists())
 		return addError(modname, "Data with this mod was not found");

+ 4 - 2
launcher/settingsView/csettingsview_moc.h

@@ -1,5 +1,7 @@
 #pragma once
 
+#include "../StdInc.h"
+
 namespace Ui {
 	class CSettingsView;
 }
@@ -7,13 +9,13 @@ namespace Ui {
 class CSettingsView : public QWidget
 {
 	Q_OBJECT
-	
+
 public:
 	explicit CSettingsView(QWidget *parent = 0);
 	~CSettingsView();
 
 	void loadSettings();
-	
+
 private slots:
 	void on_comboBoxResolution_currentIndexChanged(const QString &arg1);
 

+ 8 - 8
lib/BattleState.cpp

@@ -469,7 +469,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 		auto handleWarMachine= [&](int side, ArtifactPosition artslot, CreatureID cretype, BattleHex hex)
 		{
 			if(heroes[side] && heroes[side]->getArt(artslot))
-				stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cretype, 1), !side, SlotID(255), hex));
+				stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cretype, 1), !side, SlotID::WAR_MACHINES_SLOT, hex));
 		};
 
 		handleWarMachine(0, ArtifactPosition::MACH1, CreatureID::BALLISTA, 52);
@@ -526,15 +526,15 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 	if (curB->town && curB->town->fortLevel() >= CGTownInstance::CITADEL)
 	{
 		// keep tower
-		CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID(255), -2);
+		CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID::ARROW_TOWERS_SLOT, -2);
 		stacks.push_back(stack);
 
 		if (curB->town->fortLevel() >= CGTownInstance::CASTLE)
 		{
 			// lower tower + upper tower
-			CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID(255), -4);
+			CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID::ARROW_TOWERS_SLOT, -4);
 			stacks.push_back(stack);
-			stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID(255), -3);
+			stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID::ARROW_TOWERS_SLOT, -3);
 			stacks.push_back(stack);
 		}
 
@@ -712,7 +712,7 @@ std::shared_ptr<CObstacleInstance> BattleInfo::getObstacleOnTile(BattleHex tile)
 
 BattlefieldBI::BattlefieldBI BattleInfo::battlefieldTypeToBI(BFieldType bfieldType)
 {
-	static const std::map<BFieldType, BattlefieldBI::BattlefieldBI> theMap = 
+	static const std::map<BFieldType, BattlefieldBI::BattlefieldBI> theMap =
 	{
 		{BFieldType::CLOVER_FIELD, BattlefieldBI::CLOVER_FIELD},
 		{BFieldType::CURSED_GROUND, BattlefieldBI::CURSED_GROUND},
@@ -1141,7 +1141,7 @@ bool CStack::ableToRetaliate() const //FIXME: crash after clone is killed
 
 ui8 CStack::counterAttacksTotal() const
 {
-	//after dispell bonus should remain during current round 
+	//after dispell bonus should remain during current round
 	ui8 val = 1 + valOfBonuses(Bonus::ADDITIONAL_RETALIATION);
 	vstd::amax(counterAttacksTotalCache, val);
 	return counterAttacksTotalCache;
@@ -1183,9 +1183,9 @@ ui32 CStack::calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) cons
 ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const
 {
 	int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id));
-	
+
 	vstd::abetween(skill, 0, 3);
-	
+
 	return skill;
 }
 

+ 34 - 29
lib/CBattleCallback.cpp

@@ -187,11 +187,11 @@ TStacks CBattleInfoEssentials::battleGetStacksIf(TStackFilter predicate, bool in
 {
 	TStacks ret;
 	RETURN_IF_NOT_BATTLE(ret);
-	
+
 	vstd::copy_if(getBattle()->stacks, std::back_inserter(ret), [=](const CStack * s){
 		return predicate(s) && (includeTurrets || !(s->type->idNumber == CreatureID::ARROW_TOWERS));
 	});
-	
+
 	return ret;
 }
 
@@ -784,23 +784,23 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const CStack
 bool CBattleInfoCallback::battleCanAttack(const CStack * stack, const CStack * target, BattleHex dest) const
 {
 	RETURN_IF_NOT_BATTLE(false);
-	
+
 	if(battleTacticDist())
 		return false;
-	
+
 	if (!stack || !target)
 		return false;
-	
+
 	if (stack->owner == target->owner)
 		return false;
-	
+
 	auto &id = stack->getCreature()->idNumber;
 	if (id == CreatureID::FIRST_AID_TENT || id == CreatureID::CATAPULT)
 		return false;
-	
+
 	if (!target->alive())
 		return false;
-	
+
 	return true;
 }
 
@@ -997,7 +997,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 	const bool distPenalty = !info.attackerBonuses->hasBonusOfType(Bonus::NO_DISTANCE_PENALTY) && battleHasDistancePenalty(info.attackerBonuses, info.attackerPosition, info.defenderPosition);
 	const bool obstaclePenalty = battleHasWallPenalty(info.attackerBonuses, info.attackerPosition, info.defenderPosition);
 
-	if (info.shooting)
+	if(info.shooting)
 	{
 		if (distPenalty || info.defenderBonuses->hasBonus(isAdvancedAirShield))
 		{
@@ -1013,9 +1013,14 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 		multBonus *= 0.5;
 	}
 
+	// psychic elementals versus mind immune units 50%
+	if(attackerType->idNumber == CreatureID::PSYCHIC_ELEMENTAL
+	   && info.defenderBonuses->hasBonusOfType(Bonus::MIND_IMMUNITY))
+	{
+		multBonus *= 0.5;
+	}
 
 	// TODO attack on petrified unit 50%
-	// psychic elementals versus mind immune units 50%
 	// blinded unit retaliates
 
 	minDmg *= additiveBonus * multBonus;
@@ -1265,8 +1270,8 @@ std::pair<const CStack *, BattleHex> CBattleInfoCallback::getNearestStack(const
 	// I hate std::pairs with their undescriptive member names first / second
 	struct DistStack
 	{
-		int distanceToPred;	
-		BattleHex destination;	
+		int distanceToPred;
+		BattleHex destination;
 		const CStack *stack;
 	};
 
@@ -1276,7 +1281,7 @@ std::pair<const CStack *, BattleHex> CBattleInfoCallback::getNearestStack(const
 	{
 		return s != closest && s->alive() && (boost::logic::indeterminate(attackerOwned) || s->attackerOwned == attackerOwned);
 	}, false);
-	
+
 	for(const CStack * st : possibleStacks)
 		for(BattleHex hex : avHexes)
 			if(CStack::isMeleeAttackPossible(closest, st, hex))
@@ -1628,9 +1633,9 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
 
 	const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCast(this, player);
-	
+
 	if(specificProblem != ESpellCastProblem::OK)
-		return specificProblem;	
+		return specificProblem;
 
 	if(spell->isNegative() || spell->hasEffects())
 	{
@@ -1667,7 +1672,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 			{
 				bool immune =  ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
 				bool casterStack = stack->owner == caster->getOwner();
-				
+
                 if(!immune)
                 {
 					switch (spell->positiveness)
@@ -1716,12 +1721,12 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
 		{
 			const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO
 			const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell));
-			
+
 			for(const CStack * stack : battleAliveStacks())
 			{
 				bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
 				bool casterStack = stack->owner == caster->getOwner();
-				
+
 				if(!immune)
 					switch (spell->positiveness)
 					{
@@ -1784,7 +1789,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	{
 		logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpellHere: no spellcaster.";
 		return ESpellCastProblem::INVALID;
-	}	
+	}
 	const PlayerColor player = caster->getOwner();
 	ESpellCastProblem::ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell(caster, spell, mode);
 	if(moreGeneralProblem != ESpellCastProblem::OK)
@@ -1894,19 +1899,19 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
 	RETURN_IF_NOT_BATTLE(SpellID::NONE);
 	//This is complete list. No spells from mods.
 	//todo: this should be Spellbook of caster Stack
-	static const std::set<SpellID> allPossibleSpells = 
+	static const std::set<SpellID> allPossibleSpells =
 	{
 		SpellID::AIR_SHIELD,
 		SpellID::ANTI_MAGIC,
-		SpellID::BLESS,		
+		SpellID::BLESS,
 		SpellID::BLOODLUST,
 		SpellID::COUNTERSTRIKE,
 		SpellID::CURE,
-		SpellID::FIRE_SHIELD,		
+		SpellID::FIRE_SHIELD,
 		SpellID::FORTUNE,
 		SpellID::HASTE,
 		SpellID::MAGIC_MIRROR,
-		SpellID::MIRTH,				
+		SpellID::MIRTH,
 		SpellID::PRAYER,
 		SpellID::PRECISION,
 		SpellID::PROTECTION_FROM_AIR,
@@ -1918,7 +1923,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
 		SpellID::STONE_SKIN
 	};
 	std::vector<SpellID> beneficialSpells;
-	
+
 	auto getAliveEnemy = [=](const std::function<bool(const CStack * )> & pred)
 	{
 		return getStackIf([=](const CStack * stack)
@@ -1938,7 +1943,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
 		{
 		case SpellID::SHIELD:
 		case SpellID::FIRE_SHIELD: // not if all enemy units are shooters
-			{				
+			{
 				auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
 				{
 					return !stack->shots;
@@ -1963,7 +1968,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
 		case SpellID::PROTECTION_FROM_AIR:
 		case SpellID::PROTECTION_FROM_EARTH:
 		case SpellID::PROTECTION_FROM_FIRE:
-		case SpellID::PROTECTION_FROM_WATER:				
+		case SpellID::PROTECTION_FROM_WATER:
 			{
 				const ui8 enemySide = (ui8)subject->attackerOwned;
 				//todo: only if enemy has spellbook
@@ -2006,7 +2011,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
 			}
 			break;
 		}
-		beneficialSpells.push_back(spellID);		
+		beneficialSpells.push_back(spellID);
 	}
 
 	if(!beneficialSpells.empty())
@@ -2190,13 +2195,13 @@ TStacks CPlayerBattleCallback::battleGetStacks(EStackOwnership whose /*= MINE_AN
 	{
 		ASSERT_IF_CALLED_WITH_PLAYER
 	}
-	
+
 	return battleGetStacksIf([=](const CStack * s){
 		const bool ownerMatches = (whose == MINE_AND_ENEMY)
 			|| (whose == ONLY_MINE && s->owner == player)
 			|| (whose == ONLY_ENEMY && s->owner != player);
 		const bool alivenessMatches = s->alive()  ||  !onlyAlive;
-		return ownerMatches && alivenessMatches;		
+		return ownerMatches && alivenessMatches;
 	});
 }
 

+ 6 - 5
lib/CConfigHandler.cpp

@@ -2,6 +2,7 @@
 #include "CConfigHandler.h"
 
 #include "../lib/filesystem/Filesystem.h"
+#include "../lib/filesystem/FileStream.h"
 #include "../lib/GameConstants.h"
 #include "../lib/VCMIDirs.h"
 
@@ -80,7 +81,7 @@ void SettingsStorage::invalidateNode(const std::vector<std::string> &changedPath
 	savedConf.Struct().erase("session");
 	JsonUtils::minimize(savedConf, "vcmi:settings");
 
-	std::ofstream file(*CResourceHandler::get()->getResourceName(ResourceID("config/settings.json")), std::ofstream::trunc);
+	FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/settings.json")), std::ofstream::out | std::ofstream::trunc);
 	file << savedConf;
 }
 
@@ -173,7 +174,7 @@ JsonNode& Settings::operator [](std::string value)
 {
 	return node[value];
 }
-// 
+//
 // template DLL_LINKAGE struct SettingsStorage::NodeAccessor<SettingsListener>;
 // template DLL_LINKAGE struct SettingsStorage::NodeAccessor<Settings>;
 
@@ -214,14 +215,14 @@ void config::CConfigHandler::init()
 	const JsonNode config(ResourceID("config/resolutions.json"));
 	const JsonVector &guisettings_vec = config["GUISettings"].Vector();
 
-	for(const JsonNode &g : guisettings_vec) 
+	for(const JsonNode &g : guisettings_vec)
 	{
 		std::pair<int,int> curRes(g["resolution"]["x"].Float(), g["resolution"]["y"].Float());
 		GUIOptions *current = &conf.guiOptions[curRes];
-		
+
 		current->ac.inputLineLength = g["InGameConsole"]["maxInputPerLine"].Float();
 		current->ac.outputLineLength = g["InGameConsole"]["maxOutputPerLine"].Float();
-		
+
 		current->ac.advmapX = g["AdvMap"]["x"].Float();
 		current->ac.advmapY = g["AdvMap"]["y"].Float();
 		current->ac.advmapW = g["AdvMap"]["width"].Float();

+ 4 - 3
lib/CGameInfoCallback.cpp

@@ -727,15 +727,16 @@ int CPlayerSpecificInfoCallback::getHeroSerial(const CGHeroInstance * hero, bool
 	return -1;
 }
 
-int3 CPlayerSpecificInfoCallback::getGrailPos( double &outKnownRatio )
+int3 CPlayerSpecificInfoCallback::getGrailPos( double *outKnownRatio )
 {
 	if (!player || CGObelisk::obeliskCount == 0)
 	{
-		outKnownRatio = 0.0;
+		*outKnownRatio = 0.0;
 	}
 	else
 	{
-		outKnownRatio = static_cast<double>(CGObelisk::visited[gs->getPlayerTeam(*player)->id]) / CGObelisk::obeliskCount;
+		*outKnownRatio = static_cast<double>(CGObelisk::visited[gs->getPlayerTeam(*player)->id])
+			/ CGObelisk::obeliskCount;
 	}
 	return gs->map->grailPos;
 }

+ 2 - 3
lib/CGameInfoCallback.h

@@ -125,7 +125,7 @@ class DLL_LINKAGE CPlayerSpecificInfoCallback : public CGameInfoCallback
 public:
 	int howManyTowns() const;
 	int howManyHeroes(bool includeGarrisoned = true) const;
-	int3 getGrailPos(double &outKnownRatio);
+	int3 getGrailPos(double *outKnownRatio);
 	boost::optional<PlayerColor> getMyColor() const;
 
 	std::vector <const CGTownInstance *> getTownsInfo(bool onlyOur = true) const; //true -> only owned; false -> all visible
@@ -139,7 +139,7 @@ public:
 
 	int getResourceAmount(Res::ERes type) const;
 	TResources getResourceAmount() const;
-	const std::vector< std::vector< std::vector<ui8> > > & getVisibilityMap()const; //returns visibility map 
+	const std::vector< std::vector< std::vector<ui8> > > & getVisibilityMap()const; //returns visibility map
 	const PlayerSettings * getPlayerSettings(PlayerColor color) const;
 };
 
@@ -154,4 +154,3 @@ public:
 
 	virtual void showInfoDialog(const std::string &msg, PlayerColor player);
 };
-

+ 5 - 5
lib/CGameState.cpp

@@ -988,10 +988,10 @@ void CGameState::initGrailPosition()
 {
 	logGlobal->debugStream() << "\tPicking grail position";
 	//pick grail location
-	if(map->grailPos.x < 0 || map->grailRadious) //grail not set or set within a radius around some place
+	if(map->grailPos.x < 0 || map->grailRadius) //grail not set or set within a radius around some place
 	{
-		if(!map->grailRadious) //radius not given -> anywhere on map
-			map->grailRadious = map->width * 2;
+		if(!map->grailRadius) //radius not given -> anywhere on map
+			map->grailRadius = map->width * 2;
 
 		std::vector<int3> allowedPos;
 		static const int BORDER_WIDTH = 9; // grail must be at least 9 tiles away from border
@@ -1008,7 +1008,7 @@ void CGameState::initGrailPosition()
 						&& !t.visitable
 						&& t.terType != ETerrainType::WATER
 						&& t.terType != ETerrainType::ROCK
-						&& map->grailPos.dist2dSQ(int3(i, j, k)) <= (map->grailRadious * map->grailRadious))
+						&& map->grailPos.dist2dSQ(int3(i, j, k)) <= (map->grailRadius * map->grailRadius))
 						allowedPos.push_back(int3(i,j,k));
 				}
 			}
@@ -1638,7 +1638,7 @@ void CGameState::initFogOfWar()
 			if(!obj || !vstd::contains(elem.second.players, obj->tempOwner)) continue; //not a flagged object
 
 			std::unordered_set<int3, ShashInt3> tiles;
-			getTilesInRange(tiles, obj->getSightCenter(), obj->getSightRadious(), obj->tempOwner, 1);
+			getTilesInRange(tiles, obj->getSightCenter(), obj->getSightRadius(), obj->tempOwner, 1);
 			for(int3 tile : tiles)
 			{
 				elem.second.fogOfWarMap[tile.x][tile.y][tile.z] = 1;

+ 1 - 0
lib/CMakeLists.txt

@@ -19,6 +19,7 @@ set(lib_SRCS
 		filesystem/CZipLoader.cpp
                 filesystem/CZipSaver.cpp
 		filesystem/Filesystem.cpp
+		filesystem/FileStream.cpp
 		filesystem/ResourceID.cpp
                 filesystem/MinizipExtensions.cpp
 

+ 3 - 2
lib/CModHandler.cpp

@@ -2,6 +2,7 @@
 #include "CModHandler.h"
 #include "mapObjects/CObjectClassesHandler.h"
 #include "JsonNode.h"
+#include "filesystem/FileStream.h"
 #include "filesystem/Filesystem.h"
 #include "filesystem/AdapterLoaders.h"
 #include "filesystem/CFilesystemLoader.h"
@@ -554,7 +555,7 @@ void CModHandler::loadConfigFromFile (std::string name)
 	std::string paths;
 	for(auto& p : CResourceHandler::get()->getResourceNames(ResourceID("config/" + name)))
 	{
-		paths += p + ", ";
+		paths += p.string() + ", ";
 	}
 	paths = paths.substr(0, paths.size() - 2);
 	logGlobal->debugStream() << "Loading hardcoded features settings from [" << paths << "], result:";
@@ -902,7 +903,7 @@ void CModHandler::afterLoad()
 	}
 	modSettings["core"] = coreMod.saveLocalData();
 
-	std::ofstream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc);
+	FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc);
 	file << modSettings;
 }
 

+ 2 - 3
lib/CObstacleInstance.cpp

@@ -1,6 +1,7 @@
 #include "StdInc.h"
 #include "CObstacleInstance.h"
 #include "CHeroHandler.h"
+#include "CTownHandler.h"
 #include "VCMI_Lib.h"
 #include "spells/CSpellHandler.h"
 
@@ -145,7 +146,5 @@ void SpellCreatedObstacle::battleTurnPassed()
 
 std::vector<BattleHex> MoatObstacle::getAffectedTiles() const
 {
-	//rrr... need initializer lists
-	static const BattleHex moatHexes[] = {11, 28, 44, 61, 77, 111, 129, 146, 164, 181};
-	return std::vector<BattleHex>(moatHexes, moatHexes + ARRAY_COUNT(moatHexes));
+	return VLC->townh->factions[ID]->town->moatHexes;
 }

+ 2 - 2
lib/CPathfinder.cpp

@@ -646,10 +646,10 @@ void CPathfinder::initializePatrol()
 	auto state = PATROL_NONE;
 	if(hero->patrol.patrolling && !getPlayer(hero->tempOwner)->human)
 	{
-		if(hero->patrol.patrolRadious)
+		if(hero->patrol.patrolRadius)
 		{
 			state = PATROL_RADIUS;
-			gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadious, boost::optional<PlayerColor>(), 0, true);
+			gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadius, boost::optional<PlayerColor>(), 0, true);
 		}
 		else
 			state = PATROL_LOCKED;

+ 14 - 1
lib/CTownHandler.cpp

@@ -13,6 +13,7 @@
 #include "filesystem/Filesystem.h"
 #include "mapObjects/CObjectClassesHandler.h"
 #include "mapObjects/CObjectHandler.h"
+#include "BattleHex.h"
 
 /*
  * CTownHandler.cpp, part of VCMI engine
@@ -85,6 +86,12 @@ CTown::~CTown()
 		str.dellNull();
 }
 
+std::vector<BattleHex> CTown::defaultMoatHexes()
+{
+	static const std::vector<BattleHex> moatHexes = {11, 28, 44, 61, 77, 111, 129, 146, 164, 181};
+	return moatHexes;
+}
+
 CTownHandler::CTownHandler()
 {
 	VLC->townh = this;
@@ -542,7 +549,13 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source)
 
 	town.moatDamage = source["moatDamage"].Float();
 
-	
+	// Compatability for <= 0.98f mods
+	if(source["moatHexes"].isNull())
+	{
+		town.moatHexes = CTown::defaultMoatHexes();
+	}
+	else
+		town.moatHexes = source["moatHexes"].convertTo<std::vector<BattleHex> >();
 
 	town.mageLevel = source["mageGuild"].Float();
 	town.names = source["names"].convertTo<std::vector<std::string> >();

+ 15 - 1
lib/CTownHandler.h

@@ -6,6 +6,7 @@
 #include "GameConstants.h"
 #include "IHandlerBase.h"
 #include "LogicalExpression.h"
+#include "BattleHex.h"
 
 /*
  * CTownHandler.h, part of VCMI engine
@@ -21,6 +22,7 @@ class CLegacyConfigParser;
 class JsonNode;
 class CTown;
 class CFaction;
+struct BattleHex;
 
 /// a typical building encountered in every castle ;]
 /// this is structure available to both client and server
@@ -136,6 +138,8 @@ class DLL_LINKAGE CTown
 public:
 	CTown();
 	~CTown();
+	// TODO: remove once save and mod compatability not needed
+	static std::vector<BattleHex> defaultMoatHexes();
 
 	CFaction * faction;
 	
@@ -156,6 +160,7 @@ public:
 	ui16 primaryRes;
 	ArtifactID warMachine;
 	si32 moatDamage;
+	std::vector<BattleHex> moatHexes;
 	// default chance for hero of specific class to appear in tavern, if field "tavern" was not set
 	// resulting chance = sqrt(town.chance * heroClass.chance)
 	ui32 defaultTavernChance;
@@ -205,7 +210,16 @@ public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & names & faction & creatures & dwellings & dwellingNames & buildings & hordeLvl & mageLevel
-			& primaryRes & warMachine & clientInfo & moatDamage & defaultTavernChance;
+			& primaryRes & warMachine & clientInfo & moatDamage;
+		if(version >= 758)
+		{
+			h & moatHexes;
+		}
+		else if(!h.saving)
+		{
+			moatHexes = defaultMoatHexes();
+		}
+		h & defaultTavernChance;
 
 		auto findNull = [](const std::pair<BuildingID, ConstTransitivePtr<CBuilding>> &building)
 		{ return building.second == nullptr; };

+ 6 - 5
lib/Connection.cpp

@@ -4,6 +4,7 @@
 #include "registerTypes/RegisterTypes.h"
 #include "mapping/CMap.h"
 #include "CGameState.h"
+#include "filesystem/FileStream.h"
 
 #include <boost/asio.hpp>
 
@@ -282,7 +283,7 @@ void CConnection::enableSmartVectorMemberSerializatoin()
 	CSerializer::smartVectorMembersSerialization = true;
 }
 
-CSaveFile::CSaveFile( const std::string &fname ): serializer(this)
+CSaveFile::CSaveFile( const boost::filesystem::path &fname ): serializer(this)
 {
 	registerTypes(serializer);
 	openNextFile(fname);
@@ -298,12 +299,12 @@ int CSaveFile::write( const void * data, unsigned size )
 	return size;
 }
 
-void CSaveFile::openNextFile(const std::string &fname)
+void CSaveFile::openNextFile(const boost::filesystem::path &fname)
 {
 	fName = fname;
 	try
 	{
-		sfile = make_unique<std::ofstream>(fname.c_str(), std::ios::binary);
+		sfile = make_unique<FileStream>(fname, std::ios::out | std::ios::binary);
 		sfile->exceptions(std::ifstream::failbit | std::ifstream::badbit); //we throw a lot anyway
 
 		if(!(*sfile))
@@ -364,7 +365,7 @@ void CLoadFile::openNextFile(const boost::filesystem::path & fname, int minimalV
 	try
 	{
 		fName = fname.string();
-		sfile = make_unique<boost::filesystem::ifstream>(fname, std::ios::binary);
+		sfile = make_unique<FileStream>(fname, std::ios::in | std::ios::binary);
 		sfile->exceptions(std::ifstream::failbit | std::ifstream::badbit); //we throw a lot anyway
 
 		if(!(*sfile))
@@ -569,7 +570,7 @@ void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib)
 	smartVectorMembersSerialization = true;
 }
 
-CLoadIntegrityValidator::CLoadIntegrityValidator( const std::string &primaryFileName, const std::string &controlFileName, int minimalVersion /*= version*/ )
+CLoadIntegrityValidator::CLoadIntegrityValidator( const boost::filesystem::path &primaryFileName, const boost::filesystem::path &controlFileName, int minimalVersion /*= version*/ )
 	: serializer(this), foundDesync(false)
 {
 	registerTypes(serializer);

+ 9 - 8
lib/Connection.h

@@ -27,7 +27,7 @@
 #include "mapping/CCampaignHandler.h" //for CCampaignState
 #include "rmg/CMapGenerator.h" // for CMapGenOptions
 
-const ui32 version = 756;
+const ui32 version = 758;
 const ui32 minSupportedVersion = 753;
 
 class CISer;
@@ -39,6 +39,7 @@ class CGameState;
 class CCreature;
 class LibClasses;
 class CHero;
+class FileStream;
 struct CPack;
 extern DLL_LINKAGE LibClasses * VLC;
 namespace mpl = boost::mpl;
@@ -1550,14 +1551,14 @@ public:
 
 	COSer serializer;
 
-	std::string fName;
-	std::unique_ptr<std::ofstream> sfile;
+	boost::filesystem::path fName;
+	std::unique_ptr<FileStream> sfile;
 
-	CSaveFile(const std::string &fname); //throws!
+	CSaveFile(const boost::filesystem::path &fname); //throws!
 	~CSaveFile();
 	int write(const void * data, unsigned size) override;
 
-	void openNextFile(const std::string &fname); //throws!
+	void openNextFile(const boost::filesystem::path &fname); //throws!
 	void clear();
     void reportState(CLogger * out) override;
 
@@ -1577,8 +1578,8 @@ class DLL_LINKAGE CLoadFile
 public:
 	CISer serializer;
 
-	std::string fName;
-	std::unique_ptr<boost::filesystem::ifstream> sfile;
+	boost::filesystem::path fName;
+	std::unique_ptr<FileStream> sfile;
 
 	CLoadFile(const boost::filesystem::path & fname, int minimalVersion = version); //throws!
 	~CLoadFile();
@@ -1606,7 +1607,7 @@ public:
 	std::unique_ptr<CLoadFile> primaryFile, controlFile;
 	bool foundDesync;
 
-	CLoadIntegrityValidator(const std::string &primaryFileName, const std::string &controlFileName, int minimalVersion = version); //throws!
+	CLoadIntegrityValidator(const boost::filesystem::path &primaryFileName, const boost::filesystem::path &controlFileName, int minimalVersion = version); //throws!
 
 	int read( void * data, unsigned size) override; //throws!
 	void checkMagicBytes(const std::string &text);

+ 4 - 0
lib/GameConstants.cpp

@@ -19,6 +19,10 @@
 #include "spells/CSpellHandler.h"
 
 const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
+const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);
+const SlotID SlotID::WAR_MACHINES_SLOT = SlotID(-4);
+const SlotID SlotID::ARROW_TOWERS_SLOT = SlotID(-5);
+
 const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253);
 const PlayerColor PlayerColor::UNFLAGGABLE = PlayerColor(254);
 const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255);

+ 6 - 6
lib/GameConstants.h

@@ -236,6 +236,9 @@ class SlotID : public BaseForID<SlotID, si32>
 	friend class CNonConstInfoCallback;
 
 	DLL_LINKAGE static const SlotID COMMANDER_SLOT_PLACEHOLDER;
+	DLL_LINKAGE static const SlotID SUMMONED_SLOT_PLACEHOLDER; ///<for all summoned creatures, only during battle
+	DLL_LINKAGE static const SlotID WAR_MACHINES_SLOT; ///<for all war machines during battle
+	DLL_LINKAGE static const SlotID ARROW_TOWERS_SLOT; ///<for all arrow towers during battle
 
 	bool validSlot() const
 	{
@@ -443,11 +446,11 @@ namespace ESpellCastProblem
 
 namespace ECastingMode
 {
-	enum ECastingMode 
+	enum ECastingMode
 	{
 		HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
 		MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING,
-		SPELL_LIKE_ATTACK, 
+		SPELL_LIKE_ATTACK,
 		PASSIVE_CASTING//f.e. opening battle spells
 	};
 }
@@ -965,6 +968,7 @@ public:
 		WATER_ELEMENTAL = 115,
 		GOLD_GOLEM = 116,
 		DIAMOND_GOLEM = 117,
+		PSYCHIC_ELEMENTAL = 120,
 		CATAPULT = 145,
 		BALLISTA = 146,
 		FIRST_AID_TENT = 147,
@@ -1050,7 +1054,3 @@ typedef int TRmgTemplateZoneId;
 #undef ID_LIKE_OPERATORS_INTERNAL
 #undef INSTID_LIKE_CLASS_COMMON
 #undef OP_DECL_INT
-
-
-
-

+ 5 - 5
lib/HeroBonus.h

@@ -33,7 +33,7 @@ class CSelector : std::function<bool(const Bonus*)>
 public:
 	CSelector() {}
 	template<typename T>
-	CSelector(const T &t,	//SFINAE trick -> include this c-tor in overload resolution only if parameter is class 
+	CSelector(const T &t,	//SFINAE trick -> include this c-tor in overload resolution only if parameter is class
 							//(includes functors, lambdas) or function. Without that VC is going mad about ambiguities.
 		typename std::enable_if < boost::mpl::or_ < std::is_class<T>, std::is_function<T >> ::value>::type *dummy = nullptr)
 		: TBase(t)
@@ -79,7 +79,7 @@ public:
 	BONUS_NAME(MORALE) \
 	BONUS_NAME(LUCK) \
 	BONUS_NAME(PRIMARY_SKILL) /*uses subtype to pick skill; additional info if set: 1 - only melee, 2 - only distance*/  \
-	BONUS_NAME(SIGHT_RADIOUS) \
+	BONUS_NAME(SIGHT_RADIOUS) /*the correct word is RADIUS, but this one's already used in mods */\
 	BONUS_NAME(MANA_REGENERATION) /*points per turn apart from normal (1 + mysticism)*/  \
 	BONUS_NAME(FULL_MANA_REGENERATION) /*all mana points are replenished every day*/  \
 	BONUS_NAME(NONEVIL_ALIGNMENT_MIX) /*good and neutral creatures can be mixed without morale penalty*/  \
@@ -219,7 +219,7 @@ public:
 	BONUS_NAME(VISIONS) /* subtype - spell level */\
 	BONUS_NAME(NO_TERRAIN_PENALTY) /* subtype - terrain type */\
 	/* end of list */
-	
+
 
 #define BONUS_SOURCE_LIST \
 	BONUS_SOURCE(ARTIFACT)\
@@ -345,7 +345,7 @@ struct DLL_LINKAGE Bonus
 	static bool NTurns(const Bonus *hb)
 	{
 		return hb->duration & Bonus::N_TURNS;
-	}	
+	}
 	static bool OneDay(const Bonus *hb)
 	{
 		return hb->duration & Bonus::ONE_DAY;
@@ -754,7 +754,7 @@ public:
 		: ptr(Ptr)
 	{
 	}
-	
+
 	CSelector operator()(const T &valueToCompareAgainst) const
 	{
 		auto ptr2 = ptr; //We need a COPY because we don't want to reference this (might be outlived by lambda)

+ 1 - 1
lib/Interprocess.h

@@ -55,4 +55,4 @@ struct SharedMem
 		delete mr;
 		boost::interprocess::shared_memory_object::remove("vcmi_memory");
 	}
-};
+};

+ 19 - 2
lib/NetPacksLib.cpp

@@ -223,7 +223,7 @@ DLL_LINKAGE void FoWChange::applyGs( CGameState *gs )
 				case Obj::TOWN:
 				case Obj::ABANDONED_MINE:
 					if(vstd::contains(team->players, o->tempOwner)) //check owned observators
-						gs->getTilesInRange(tilesRevealed, o->getSightCenter(), o->getSightRadious(), o->tempOwner, 1);
+						gs->getTilesInRange(tilesRevealed, o->getSightCenter(), o->getSightRadius(), o->tempOwner, 1);
 					break;
 				}
 			}
@@ -1057,6 +1057,23 @@ DLL_LINKAGE void NewTurn::applyGs( CGameState *gs )
 	for(NewTurn::Hero h : heroes) //give mana/movement point
 	{
 		CGHeroInstance *hero = gs->getHero(h.id);
+		if(!hero)
+		{
+			// retreated or surrendered hero who has not been reset yet
+			for(auto& hp : gs->hpool.heroesPool)
+			{
+				if(hp.second->id == h.id)
+				{
+					hero = hp.second;
+					break;
+				}
+			}
+		}
+		if(!hero)
+		{
+			logGlobal->errorStream() << "Hero " << h.id << " not found in NewTurn::applyGs";
+			continue;
+		}
 		hero->movement = h.move;
 		hero->mana = h.mana;
 	}
@@ -1616,7 +1633,7 @@ DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
 	}
 
 	CStackBasicDescriptor csbd(creID, amount);
-	CStack * addedStack = gs->curB->generateNewStack(csbd, attacker, SlotID(255), pos); //TODO: netpacks?
+	CStack * addedStack = gs->curB->generateNewStack(csbd, attacker, SlotID::SUMMONED_SLOT_PLACEHOLDER, pos); //TODO: netpacks?
 	if (summoned)
 		addedStack->state.insert(EBattleStackState::SUMMONED);
 

+ 1 - 1
lib/ResourceSet.cpp

@@ -113,4 +113,4 @@ Res::ResourceSet::nziterator::nziterator(const ResourceSet &RS)
 
 	if(!valid())
 		advance();
-}
+}

+ 1 - 1
lib/StdInc.cpp

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

+ 1 - 1
lib/StdInc.h

@@ -4,4 +4,4 @@
 
 // This header should be treated as a pre compiled header file(PCH) in the compiler building settings.
 
-// Here you can add specific libraries and macros which are specific to this project.
+// Here you can add specific libraries and macros which are specific to this project.

+ 1 - 1
lib/VCMIDirs.h

@@ -55,4 +55,4 @@ class DLL_LINKAGE IVCMIDirs
 namespace VCMIDirs
 {
 	extern DLL_LINKAGE const IVCMIDirs& get();
-}
+}

+ 7 - 4
lib/VCMI_lib.cbp

@@ -4,7 +4,7 @@
 	<Project>
 		<Option title="VCMI_lib" />
 		<Option execution_dir="D:/projects/vcmi/engine/VCMI_lib/" />
-		<Option pch_mode="2" />
+		<Option pch_mode="0" />
 		<Option compiler="gcc" />
 		<Build>
 			<Target title="Debug-win32">
@@ -17,8 +17,8 @@
 				<Option run_host_application_in_terminal="1" />
 				<Option createStaticLib="1" />
 				<Compiler>
-					<Add option="-Og" />
 					<Add option="-g" />
+					<Add option="-Og" />
 					<Add directory="$(#zlib.include)" />
 				</Compiler>
 				<Linker>
@@ -62,7 +62,6 @@
 					<Add option="-lboost_locale$(#boost.libsuffix)" />
 					<Add option="-lboost_date_time$(#boost.libsuffix)" />
 					<Add option="-liconv" />
-					<Add option="-ldbghelp" />
 					<Add directory="$(#sdl2.lib)" />
 					<Add directory="$(#boost.lib32)" />
 					<Add directory="$(#zlib.lib)" />
@@ -103,6 +102,7 @@
 		<Compiler>
 			<Add option="-Wextra" />
 			<Add option="-Wall" />
+			<Add option="-std=gnu++11" />
 			<Add option="-fexceptions" />
 			<Add option="-Wpointer-arith" />
 			<Add option="-Wno-switch" />
@@ -112,7 +112,9 @@
 			<Add option="-Wno-unused-local-typedefs" />
 			<Add option="-DVCMI_DLL" />
 			<Add option="-DBOOST_THREAD_USE_LIB" />
+			<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
 			<Add option="-D_WIN32_WINNT=0x0501" />
+			<Add option="-D_WIN32" />
 			<Add directory="$(#boost.include)" />
 			<Add directory="../include" />
 			<Add directory="../lib" />
@@ -200,7 +202,6 @@
 		<Unit filename="StartInfo.h" />
 		<Unit filename="StdInc.h">
 			<Option weight="0" />
-			<Option target="Debug-win64" />
 		</Unit>
 		<Unit filename="StringConstants.h" />
 		<Unit filename="UnlockGuard.h" />
@@ -234,6 +235,8 @@
 		<Unit filename="filesystem/CZipLoader.h" />
 		<Unit filename="filesystem/CZipSaver.cpp" />
 		<Unit filename="filesystem/CZipSaver.h" />
+		<Unit filename="filesystem/FileStream.cpp" />
+		<Unit filename="filesystem/FileStream.h" />
 		<Unit filename="filesystem/Filesystem.cpp" />
 		<Unit filename="filesystem/Filesystem.h" />
 		<Unit filename="filesystem/ISimpleResourceLoader.h" />

+ 2 - 0
lib/VCMI_lib.vcxproj

@@ -188,6 +188,7 @@
     <ClCompile Include="CThreadHelper.cpp" />
     <ClCompile Include="CTownHandler.cpp" />
     <ClCompile Include="CRandomGenerator.cpp" />
+    <ClCompile Include="filesystem\FileStream.cpp" />
     <ClCompile Include="spells\CSpellHandler.cpp" />
     <ClCompile Include="spells\ISpellMechanics.cpp" />
     <ClCompile Include="spells\AdventureSpellMechanics.cpp" />
@@ -310,6 +311,7 @@
     <ClInclude Include="filesystem\CInputStream.h" />
     <ClInclude Include="filesystem\CMemoryStream.h" />
     <ClInclude Include="filesystem\CZipLoader.h" />
+    <ClInclude Include="filesystem\FileStream.h" />
     <ClInclude Include="filesystem\Filesystem.h" />
     <ClInclude Include="filesystem\ISimpleResourceLoader.h" />
     <ClInclude Include="filesystem\ResourceID.h" />

+ 6 - 0
lib/VCMI_lib.vcxproj.filters

@@ -233,6 +233,9 @@
     <ClCompile Include="registerTypes\TypesMapObjects3.cpp">
       <Filter>registerTypes</Filter>
     </ClCompile>
+    <ClCompile Include="filesystem\FileStream.cpp">
+      <Filter>filesystem</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="CCreatureSet.h">
@@ -574,5 +577,8 @@
     <ClInclude Include="mapping\CDrawRoadsOperation.h">
       <Filter>mapping</Filter>
     </ClInclude>
+    <ClInclude Include="filesystem\FileStream.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>

+ 6 - 6
lib/filesystem/AdapterLoaders.cpp

@@ -27,7 +27,7 @@ std::string CMappedFileLoader::getMountPoint() const
 	return ""; // does not have any meaning with this type of data source
 }
 
-boost::optional<std::string> CMappedFileLoader::getResourceName(const ResourceID & resourceName) const
+boost::optional<boost::filesystem::path> CMappedFileLoader::getResourceName(const ResourceID & resourceName) const
 {
 	return CResourceHandler::get()->getResourceName(fileList.at(resourceName));
 }
@@ -80,22 +80,22 @@ std::string CFilesystemList::getMountPoint() const
 	return "";
 }
 
-boost::optional<std::string> CFilesystemList::getResourceName(const ResourceID & resourceName) const
+boost::optional<boost::filesystem::path> CFilesystemList::getResourceName(const ResourceID & resourceName) const
 {
 	if (existsResource(resourceName))
 		return getResourcesWithName(resourceName).back()->getResourceName(resourceName);
-	return boost::optional<std::string>();
+	return boost::optional<boost::filesystem::path>();
 }
 
-std::set<std::string> CFilesystemList::getResourceNames(const ResourceID & resourceName) const
+std::set<boost::filesystem::path> CFilesystemList::getResourceNames(const ResourceID & resourceName) const
 {
-	std::set<std::string> paths;
+	std::set<boost::filesystem::path> paths;
 	for(auto& loader : getResourcesWithName(resourceName))
 	{
 		auto rn = loader->getResourceName(resourceName);
 		if(rn)
 		{
-			paths.insert(*rn);
+			paths.insert(rn->string());
 		}
 	}
 	return std::move(paths);

+ 84 - 84
lib/filesystem/AdapterLoaders.h

@@ -1,87 +1,87 @@
-#pragma once
-
-/*
- * AdapterLoaders.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-
-#include "ISimpleResourceLoader.h"
-#include "ResourceID.h"
-
-class CFileInfo;
-class CInputStream;
-class JsonNode;
-
-/**
- * Class that implements file mapping (aka *nix symbolic links)
- * Uses json file as input, content is map:
- * "fileA.txt" : "fileB.txt"
- * Note that extension is necessary, but used only to determine type
- *
- * fileA - file which will be replaced
- * fileB - file which will be used as replacement
- */
-class DLL_LINKAGE CMappedFileLoader : public ISimpleResourceLoader
-{
-public:
-	/**
-	 * Ctor.
-	 *
-	 * @param config Specifies filesystem configuration
-	 */
-	explicit CMappedFileLoader(const std::string &mountPoint, const JsonNode & config);
-
-	/// Interface implementation
-	/// @see ISimpleResourceLoader
-	std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const override;
-	bool existsResource(const ResourceID & resourceName) const override;
-	std::string getMountPoint() const override;
-	boost::optional<std::string> getResourceName(const ResourceID & resourceName) const override;
-	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
-
-private:
-	/** A list of files in this map
-	 * key = ResourceID for resource loader
-	 * value = ResourceID to which file this request will be redirected
-	*/
-	std::unordered_map<ResourceID, ResourceID> fileList;
-};
-
-class DLL_LINKAGE CFilesystemList : public ISimpleResourceLoader
-{
-	std::vector<std::unique_ptr<ISimpleResourceLoader> > loaders;
-
-	std::set<ISimpleResourceLoader *> writeableLoaders;
-
-	//FIXME: this is only compile fix, should be removed in the end
+#pragma once
+
+/*
+ * AdapterLoaders.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "ISimpleResourceLoader.h"
+#include "ResourceID.h"
+
+class CFileInfo;
+class CInputStream;
+class JsonNode;
+
+/**
+ * Class that implements file mapping (aka *nix symbolic links)
+ * Uses json file as input, content is map:
+ * "fileA.txt" : "fileB.txt"
+ * Note that extension is necessary, but used only to determine type
+ *
+ * fileA - file which will be replaced
+ * fileB - file which will be used as replacement
+ */
+class DLL_LINKAGE CMappedFileLoader : public ISimpleResourceLoader
+{
+public:
+	/**
+	 * Ctor.
+	 *
+	 * @param config Specifies filesystem configuration
+	 */
+	explicit CMappedFileLoader(const std::string &mountPoint, const JsonNode & config);
+
+	/// Interface implementation
+	/// @see ISimpleResourceLoader
+	std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const override;
+	bool existsResource(const ResourceID & resourceName) const override;
+	std::string getMountPoint() const override;
+	boost::optional<boost::filesystem::path> getResourceName(const ResourceID & resourceName) const override;
+	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
+
+private:
+	/** A list of files in this map
+	 * key = ResourceID for resource loader
+	 * value = ResourceID to which file this request will be redirected
+	*/
+	std::unordered_map<ResourceID, ResourceID> fileList;
+};
+
+class DLL_LINKAGE CFilesystemList : public ISimpleResourceLoader
+{
+	std::vector<std::unique_ptr<ISimpleResourceLoader> > loaders;
+
+	std::set<ISimpleResourceLoader *> writeableLoaders;
+
+	//FIXME: this is only compile fix, should be removed in the end
 	CFilesystemList(CFilesystemList &) = delete;
 	CFilesystemList &operator=(CFilesystemList &) = delete;
 
-public:
-	CFilesystemList();
-	~CFilesystemList();
-	/// Interface implementation
-	/// @see ISimpleResourceLoader
-	std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const override;
-	bool existsResource(const ResourceID & resourceName) const override;
-	std::string getMountPoint() const override;
-	boost::optional<std::string> getResourceName(const ResourceID & resourceName) const override;
-	std::set<std::string> getResourceNames(const ResourceID & resourceName) const override;
-	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
-	bool createResource(std::string filename, bool update = false) override;
-	std::vector<const ISimpleResourceLoader *> getResourcesWithName(const ResourceID & resourceName) const override;
-
-	/**
-	 * Adds a resource loader to the loaders list
-	 * Passes loader ownership to this object
-	 *
-	 * @param loader The simple resource loader object to add
-	 * @param writeable - resource shall be treated as writeable
-	 */
-	void addLoader(ISimpleResourceLoader * loader, bool writeable);
-};
+public:
+	CFilesystemList();
+	~CFilesystemList();
+	/// Interface implementation
+	/// @see ISimpleResourceLoader
+	std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const override;
+	bool existsResource(const ResourceID & resourceName) const override;
+	std::string getMountPoint() const override;
+	boost::optional<boost::filesystem::path> getResourceName(const ResourceID & resourceName) const override;
+	std::set<boost::filesystem::path> getResourceNames(const ResourceID & resourceName) const override;
+	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
+	bool createResource(std::string filename, bool update = false) override;
+	std::vector<const ISimpleResourceLoader *> getResourcesWithName(const ResourceID & resourceName) const override;
+
+	/**
+	 * Adds a resource loader to the loaders list
+	 * Passes loader ownership to this object
+	 *
+	 * @param loader The simple resource loader object to add
+	 * @param writeable - resource shall be treated as writeable
+	 */
+	void addLoader(ISimpleResourceLoader * loader, bool writeable);
+};

+ 7 - 21
lib/filesystem/CFileInputStream.cpp

@@ -4,39 +4,25 @@
 #include "CFileInfo.h"
 
 CFileInputStream::CFileInputStream(const boost::filesystem::path & file, si64 start, si64 size)
+  : dataStart{start},
+	dataSize{size},
+	fileStream{file, std::ios::in | std::ios::binary}
 {
-	open(file, start, size);
-}
-
-CFileInputStream::CFileInputStream(const CFileInfo & file, si64 start, si64 size)
-{
-	open(file.getName(), start, size);
-}
-
-CFileInputStream::~CFileInputStream()
-{
-	fileStream.close();
-}
-
-void CFileInputStream::open(const boost::filesystem::path & file, si64 start, si64 size)
-{
-	fileStream.open(file, std::ios::in | std::ios::binary);
-
 	if (fileStream.fail())
 		throw std::runtime_error("File " + file.string() + " isn't available.");
 
-	dataStart = start;
-	dataSize = size;
-
 	if (dataSize == 0)
 	{
 		fileStream.seekg(0, std::ios::end);
 		dataSize = tell();
 	}
 
-	fileStream.seekg(start, std::ios::beg);
+	fileStream.seekg(dataStart, std::ios::beg);
 }
 
+CFileInputStream::CFileInputStream(const CFileInfo & file, si64 start, si64 size)
+	: CFileInputStream{file.getName(), start, size} {}
+
 si64 CFileInputStream::read(ui8 * data, si64 size)
 {
 	si64 origin = tell();

+ 8 - 19
lib/filesystem/CFileInputStream.h

@@ -11,6 +11,7 @@
  */
 
 #include "CInputStream.h"
+#include "FileStream.h"
 
 class CFileInfo;
 
@@ -23,22 +24,21 @@ public:
 	/**
 	 * C-tor. Opens the specified file.
 	 *
-	 * @see CFileInputStream::open
+	 * @param file Path to the file.
+	 * @param start - offset from file start where real data starts (e.g file on archive)
+	 * @param size - size of real data in file (e.g file on archive) or 0 to use whole file
+	 *
+	 * @throws std::runtime_error if file wasn't found
 	 */
 	CFileInputStream(const boost::filesystem::path & file, si64 start = 0, si64 size = 0);
 
 	/**
 	 * C-tor. Opens the specified file.
 	 *
-	 * @see CFileInputStream::open
+	 * @see CFileInputStream::CFileInputStream(const boost::filesystem::path &, si64, si64)
 	 */
 	CFileInputStream(const CFileInfo & file, si64 start=0, si64 size=0);
 
-	/**
-	 * D-tor. Calls the close method implicitely, if the file is still opened.
-	 */
-	~CFileInputStream();
-
 	/**
 	 * Reads n bytes from the stream into the data buffer.
 	 *
@@ -79,20 +79,9 @@ public:
 	si64 getSize() override;
 
 private:
-	/**
-	 * Opens a file. If a file is currently opened, it will be closed.
-	 *
-	 * @param file Path to the file.
-	 * @param start - offset from file start where real data starts (e.g file on archive)
-	 * @param size - size of real data in file (e.g file on archive) or 0 to use whole file
-	 *
-	 * @throws std::runtime_error if file wasn't found
-	 */
-	void open(const boost::filesystem::path & file, si64 start, si64 size);
-
 	si64 dataStart;
 	si64 dataSize;
 
 	/** Native c++ input file stream object. */
-	boost::filesystem::ifstream fileStream;
+	FileStream fileStream;
 };

+ 4 - 4
lib/filesystem/CFilesystemLoader.cpp

@@ -3,6 +3,7 @@
 
 #include "CFileInfo.h"
 #include "CFileInputStream.h"
+#include "FileStream.h"
 
 namespace bfs = boost::filesystem;
 
@@ -32,11 +33,11 @@ std::string CFilesystemLoader::getMountPoint() const
 	return mountPoint;
 }
 
-boost::optional<std::string> CFilesystemLoader::getResourceName(const ResourceID & resourceName) const
+boost::optional<boost::filesystem::path> CFilesystemLoader::getResourceName(const ResourceID & resourceName) const
 {
 	assert(existsResource(resourceName));
 
-	return (baseDirectory / fileList.at(resourceName)).string();
+	return baseDirectory / fileList.at(resourceName);
 }
 
 std::unordered_set<ResourceID> CFilesystemLoader::getFilteredFiles(std::function<bool(const ResourceID &)> filter) const
@@ -68,8 +69,7 @@ bool CFilesystemLoader::createResource(std::string filename, bool update)
 
 	if (!update)
 	{
-		bfs::ofstream newfile(baseDirectory / filename);
-		if (!newfile.good())
+		if (!FileStream::CreateFile(baseDirectory / filename))
 			return false;
 	}
 	fileList[resID] = filename;

+ 1 - 1
lib/filesystem/CFilesystemLoader.h

@@ -38,7 +38,7 @@ public:
 	bool existsResource(const ResourceID & resourceName) const override;
 	std::string getMountPoint() const override;
 	bool createResource(std::string filename, bool update = false) override;
-	boost::optional<std::string> getResourceName(const ResourceID & resourceName) const override;
+	boost::optional<boost::filesystem::path> getResourceName(const ResourceID & resourceName) const override;
 	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
 
 private:

+ 27 - 27
lib/filesystem/CZipLoader.cpp

@@ -1,5 +1,6 @@
 #include "StdInc.h"
 #include "CZipLoader.h"
+#include "FileStream.h"
 
 #include "../ScopeGuard.h"
 
@@ -13,16 +14,15 @@
  *
  */
 
-///CZipStream
-CZipStream::CZipStream(std::shared_ptr<CIOApi> api, const std::string & archive, unz_file_pos filepos)
+CZipStream::CZipStream(const boost::filesystem::path & archive, unz64_file_pos filepos)
 {
 	zlib_filefunc64_def zlibApi;
 	
 	zlibApi = api->getApiStructure();
 	
 	file = unzOpen2_64(archive.c_str(), &zlibApi);
-	unzGoToFilePos(file, &filepos);
-	unzOpenCurrentFile(file);	
+	unzGoToFilePos64(file, &filepos);
+	unzOpenCurrentFile(file);
 }
 
 CZipStream::~CZipStream()
@@ -38,20 +38,20 @@ si64 CZipStream::readMore(ui8 * data, si64 size)
 
 si64 CZipStream::getSize()
 {
-	unz_file_info info;
-	unzGetCurrentFileInfo (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
+	unz_file_info64 info;
+	unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
 	return info.uncompressed_size;
 }
 
 ui32 CZipStream::calculateCRC32()
 {
-	unz_file_info info;
-	unzGetCurrentFileInfo (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
+	unz_file_info64 info;
+	unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
 	return info.crc;
 }
 
 ///CZipLoader
-CZipLoader::CZipLoader(const std::string & mountPoint, const std::string & archive, std::shared_ptr<CIOApi> api):
+CZipLoader::CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive, std::shared_ptr<CIOApi> api):
 	ioApi(api),
     zlibApi(ioApi->getApiStructure()),	
     archiveName(archive),
@@ -61,9 +61,9 @@ CZipLoader::CZipLoader(const std::string & mountPoint, const std::string & archi
 	logGlobal->traceStream() << "Zip archive loaded, " << files.size() << " files found";
 }
 
-std::unordered_map<ResourceID, unz_file_pos> CZipLoader::listFiles(const std::string & mountPoint, const std::string & archive)
+std::unordered_map<ResourceID, unz64_file_pos> CZipLoader::listFiles(const std::string & mountPoint, const boost::filesystem::path & archive)
 {
-	std::unordered_map<ResourceID, unz_file_pos> ret;
+	std::unordered_map<ResourceID, unz64_file_pos> ret;
 
 	unzFile file = unzOpen2_64(archive.c_str(), &zlibApi);
 	
@@ -74,17 +74,17 @@ std::unordered_map<ResourceID, unz_file_pos> CZipLoader::listFiles(const std::st
 	{
 		do
 		{
-			unz_file_info info;
+			unz_file_info64 info;
 			std::vector<char> filename;
 			// Fill unz_file_info structure with current file info
-			unzGetCurrentFileInfo (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
+			unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
 
 			filename.resize(info.size_filename);
 			// Get name of current file. Contrary to docs "info" parameter can't be null
-			unzGetCurrentFileInfo (file, &info, filename.data(), filename.size(), nullptr, 0, nullptr, 0);
+			unzGetCurrentFileInfo64 (file, &info, filename.data(), filename.size(), nullptr, 0, nullptr, 0);
 
 			std::string filenameString(filename.data(), filename.size());
-			unzGetFilePos(file, &ret[ResourceID(mountPoint + filenameString)]);
+			unzGetFilePos64(file, &ret[ResourceID(mountPoint + filenameString)]);
 		}
 		while (unzGoToNextFile(file) == UNZ_OK);
 	}
@@ -150,24 +150,24 @@ static bool extractCurrent(unzFile file, std::ostream & where)
 	return false;
 }
 
-std::vector<std::string> ZipArchive::listFiles(std::string filename)
+std::vector<std::string> ZipArchive::listFiles(boost::filesystem::path filename)
 {
 	std::vector<std::string> ret;
 
-	unzFile file = unzOpen(filename.c_str());
+	unzFile file = unzOpen2_64(filename.c_str(), FileStream::GetMinizipFilefunc());
 
 	if (unzGoToFirstFile(file) == UNZ_OK)
 	{
 		do
 		{
-			unz_file_info info;
+			unz_file_info64 info;
 			std::vector<char> filename;
 
-			unzGetCurrentFileInfo (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
+			unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
 
 			filename.resize(info.size_filename);
 			// Get name of current file. Contrary to docs "info" parameter can't be null
-			unzGetCurrentFileInfo (file, &info, filename.data(), filename.size(), nullptr, 0, nullptr, 0);
+			unzGetCurrentFileInfo64 (file, &info, filename.data(), filename.size(), nullptr, 0, nullptr, 0);
 
 			ret.push_back(std::string(filename.data(), filename.size()));
 		}
@@ -178,29 +178,29 @@ std::vector<std::string> ZipArchive::listFiles(std::string filename)
 	return ret;
 }
 
-bool ZipArchive::extract(std::string from, std::string where)
+bool ZipArchive::extract(boost::filesystem::path from, boost::filesystem::path where)
 {
 	// Note: may not be fast enough for large archives (should NOT happen with mods)
 	// because locating each file by name may be slow. Unlikely slower than decompression though
 	return extract(from, where, listFiles(from));
 }
 
-bool ZipArchive::extract(std::string from, std::string where, std::vector<std::string> what)
+bool ZipArchive::extract(boost::filesystem::path from, boost::filesystem::path where, std::vector<std::string> what)
 {
-	unzFile archive = unzOpen(from.c_str());
+	unzFile archive = unzOpen2_64(from.c_str(), FileStream::GetMinizipFilefunc());
 
 	auto onExit = vstd::makeScopeGuard([&]()
 	{
 		unzClose(archive);
 	});
 
-	for (std::string & file : what)
+	for (const std::string & file : what)
 	{
 		if (unzLocateFile(archive, file.c_str(), 1) != UNZ_OK)
 			return false;
 
-		std::string fullName = where + '/' + file;
-		std::string fullPath = fullName.substr(0, fullName.find_last_of("/"));
+		const boost::filesystem::path fullName = where / file;
+		const boost::filesystem::path fullPath = fullName.parent_path();
 
 		boost::filesystem::create_directories(fullPath);
 		// directory. No file to extract
@@ -208,7 +208,7 @@ bool ZipArchive::extract(std::string from, std::string where, std::vector<std::s
 		if (boost::algorithm::ends_with(file, "/"))
 			continue;
 
-		std::ofstream destFile(fullName, std::ofstream::binary);
+		FileStream destFile(fullName, std::ios::out | std::ios::binary);
 		if (!destFile.good())
 			return false;
 

+ 9 - 11
lib/filesystem/CZipLoader.h

@@ -28,8 +28,7 @@ public:
 	 * @param archive path to archive to open
 	 * @param filepos position of file to open
 	 */
-	CZipStream(std::shared_ptr<CIOApi> api, const std::string & archive, unz_file_pos filepos);
-		
+	CZipStream(std::shared_ptr<CIOApi> api, const boost::filesystem::path & archive, unz_file_pos filepos);
 	~CZipStream();
 
 	si64 getSize() override;
@@ -43,16 +42,15 @@ class DLL_LINKAGE CZipLoader : public ISimpleResourceLoader
 {
 	std::shared_ptr<CIOApi> ioApi;
 	zlib_filefunc64_def zlibApi;
-	
-	std::string archiveName;
+	boost::filesystem::path archiveName;
 	std::string mountPoint;
 
-	std::unordered_map<ResourceID, unz_file_pos> files;
+	std::unordered_map<ResourceID, unz64_file_pos> files;
 
-	std::unordered_map<ResourceID, unz_file_pos> listFiles(const std::string & mountPoint, const std::string &archive);
+	std::unordered_map<ResourceID, unz64_file_pos> listFiles(const std::string & mountPoint, const boost::filesystem::path &archive);
 public:
-	CZipLoader(const std::string & mountPoint, const std::string & archive, std::shared_ptr<CIOApi> api = std::shared_ptr<CIOApi>(new CDefaultIOApi()));
-	
+	CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive, std::shared_ptr<CIOApi> api = std::shared_ptr<CIOApi>(new CDefaultIOApi()));
+
 	/// Interface implementation
 	/// @see ISimpleResourceLoader
 	std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const override;
@@ -64,11 +62,11 @@ public:
 namespace ZipArchive
 {
 	/// List all files present in archive
-	std::vector<std::string> DLL_LINKAGE listFiles(std::string filename);
+	std::vector<std::string> DLL_LINKAGE listFiles(boost::filesystem::path filename);
 
 	/// extracts all files from archive "from" into destination directory "where". Directory must exist
-	bool DLL_LINKAGE extract(std::string from, std::string where);
+	bool DLL_LINKAGE extract(boost::filesystem::path from, boost::filesystem::path where);
 
 	///same as above, but extracts only files mentioned in "what" list
-	bool DLL_LINKAGE extract(std::string from, std::string where, std::vector<std::string> what);
+	bool DLL_LINKAGE extract(boost::filesystem::path from, boost::filesystem::path where, std::vector<std::string> what);
 }

+ 364 - 0
lib/filesystem/FileStream.cpp

@@ -0,0 +1,364 @@
+#include "StdInc.h"
+#include "FileStream.h"
+
+#ifdef USE_SYSTEM_MINIZIP
+#include <minizip/unzip.h>
+#else
+#include "../minizip/unzip.h"
+#endif
+
+#include <cstdio>
+
+///copied from ioapi.c due to linker issues on MSVS
+
+#include "../minizip/ioapi.h"
+
+#if defined(__APPLE__) || defined(IOAPI_NO_64)
+// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
+#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
+#define FTELLO_FUNC(stream) ftello(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
+#else
+#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
+#define FTELLO_FUNC(stream) ftello64(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
+#endif
+
+voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)
+{
+	if (pfilefunc->zfile_func64.zopen64_file != NULL)
+		return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode);
+	else
+	{
+		return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode);
+	}
+}
+
+long call_zseek64(const zlib_filefunc64_32_def* pfilefunc, voidpf filestream, ZPOS64_T offset, int origin)
+{
+	if (pfilefunc->zfile_func64.zseek64_file != NULL)
+		return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque, filestream, offset, origin);
+	else
+	{
+		uLong offsetTruncated = (uLong)offset;
+		if (offsetTruncated != offset)
+			return -1;
+		else
+			return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque, filestream, offsetTruncated, origin);
+	}
+}
+
+ZPOS64_T call_ztell64(const zlib_filefunc64_32_def* pfilefunc, voidpf filestream)
+{
+	if (pfilefunc->zfile_func64.zseek64_file != NULL)
+		return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque, filestream);
+	else
+	{
+		uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque, filestream);
+		if ((tell_uLong) == MAXU32)
+			return (ZPOS64_T)-1;
+		else
+			return tell_uLong;
+	}
+}
+
+static voidpf  ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode));
+static uLong   ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+static uLong   ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size));
+static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream));
+static long    ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+static int     ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream));
+static int     ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream));
+
+static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode)
+{
+	FILE* file = NULL;
+	const char* mode_fopen = NULL;
+	if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
+		mode_fopen = "rb";
+	else
+		if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+			mode_fopen = "r+b";
+		else
+			if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+				mode_fopen = "wb";
+
+	if ((filename != NULL) && (mode_fopen != NULL))
+		file = fopen(filename, mode_fopen);
+	return file;
+}
+
+static voidpf ZCALLBACK fopen64_file_func(voidpf opaque, const void* filename, int mode)
+{
+	FILE* file = NULL;
+	const char* mode_fopen = NULL;
+	if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
+		mode_fopen = "rb";
+	else
+		if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+			mode_fopen = "r+b";
+		else
+			if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+				mode_fopen = "wb";
+
+	if ((filename != NULL) && (mode_fopen != NULL))
+		file = FOPEN_FUNC((const char*)filename, mode_fopen);
+	return file;
+}
+
+
+static uLong ZCALLBACK fread_file_func(voidpf opaque, voidpf stream, void* buf, uLong size)
+{
+	uLong ret;
+	ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream);
+	return ret;
+}
+
+static uLong ZCALLBACK fwrite_file_func(voidpf opaque, voidpf stream, const void* buf, uLong size)
+{
+	uLong ret;
+	ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream);
+	return ret;
+}
+
+static long ZCALLBACK ftell_file_func(voidpf opaque, voidpf stream)
+{
+	long ret;
+	ret = ftell((FILE *)stream);
+	return ret;
+}
+
+
+static ZPOS64_T ZCALLBACK ftell64_file_func(voidpf opaque, voidpf stream)
+{
+	ZPOS64_T ret;
+	ret = FTELLO_FUNC((FILE *)stream);
+	return ret;
+}
+
+static long ZCALLBACK fseek_file_func(voidpf  opaque, voidpf stream, uLong offset, int origin)
+{
+	int fseek_origin = 0;
+	long ret;
+	switch (origin)
+	{
+	case ZLIB_FILEFUNC_SEEK_CUR:
+		fseek_origin = SEEK_CUR;
+		break;
+	case ZLIB_FILEFUNC_SEEK_END:
+		fseek_origin = SEEK_END;
+		break;
+	case ZLIB_FILEFUNC_SEEK_SET:
+		fseek_origin = SEEK_SET;
+		break;
+	default: return -1;
+	}
+	ret = 0;
+	if (fseek((FILE *)stream, offset, fseek_origin) != 0)
+		ret = -1;
+	return ret;
+}
+
+static long ZCALLBACK fseek64_file_func(voidpf  opaque, voidpf stream, ZPOS64_T offset, int origin)
+{
+	int fseek_origin = 0;
+	long ret;
+	switch (origin)
+	{
+	case ZLIB_FILEFUNC_SEEK_CUR:
+		fseek_origin = SEEK_CUR;
+		break;
+	case ZLIB_FILEFUNC_SEEK_END:
+		fseek_origin = SEEK_END;
+		break;
+	case ZLIB_FILEFUNC_SEEK_SET:
+		fseek_origin = SEEK_SET;
+		break;
+	default: return -1;
+	}
+	ret = 0;
+
+	if (FSEEKO_FUNC((FILE *)stream, offset, fseek_origin) != 0)
+		ret = -1;
+
+	return ret;
+}
+
+
+static int ZCALLBACK fclose_file_func(voidpf opaque, voidpf stream)
+{
+	int ret;
+	ret = fclose((FILE *)stream);
+	return ret;
+}
+
+static int ZCALLBACK ferror_file_func(voidpf opaque, voidpf stream)
+{
+	int ret;
+	ret = ferror((FILE *)stream);
+	return ret;
+}
+
+///end of ioapi.c
+
+//extern MINIZIP_EXPORT void fill_fopen64_filefunc(zlib_filefunc64_def*  pzlib_filefunc_def);
+
+#ifdef VCMI_WINDOWS
+	#ifndef _CRT_SECURE_NO_WARNINGS
+		#define _CRT_SECURE_NO_WARNINGS
+	#endif
+	#include <cwchar>
+	#define CHAR_LITERAL(s) L##s
+	using CharType = wchar_t;
+#else
+	#define CHAR_LITERAL(s) s
+	using CharType = char;
+#endif
+
+inline FILE* do_open(const CharType* name, const CharType* mode)
+{
+	#ifdef VCMI_WINDOWS
+		return _wfopen(name, mode);
+	#else
+		return std::fopen(name, mode);
+	#endif
+}
+
+#define GETFILE static_cast<std::FILE*>(filePtr)
+
+voidpf ZCALLBACK MinizipOpenFunc(voidpf opaque, const void* filename, int mode)
+{
+    const CharType* mode_fopen = [mode]() -> const CharType*
+    {
+		if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
+			return CHAR_LITERAL("rb");
+		else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+			return CHAR_LITERAL("r+b");
+		else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+			return CHAR_LITERAL("wb");
+		return nullptr;
+    }();
+
+    if (filename != nullptr && mode_fopen != nullptr)
+        return do_open(static_cast<const CharType*>(filename), mode_fopen);
+	else
+		return nullptr;
+}
+
+
+void fill_fopen64_filefunc(zlib_filefunc64_def*  pzlib_filefunc_def)
+{
+	pzlib_filefunc_def->zopen64_file = fopen64_file_func;
+	pzlib_filefunc_def->zread_file = fread_file_func;
+	pzlib_filefunc_def->zwrite_file = fwrite_file_func;
+	pzlib_filefunc_def->ztell64_file = ftell64_file_func;
+	pzlib_filefunc_def->zseek64_file = fseek64_file_func;
+	pzlib_filefunc_def->zclose_file = fclose_file_func;
+	pzlib_filefunc_def->zerror_file = ferror_file_func;
+	pzlib_filefunc_def->opaque = NULL;
+}
+
+
+zlib_filefunc64_def* FileStream::GetMinizipFilefunc()
+{
+	static zlib_filefunc64_def MinizipFilefunc;
+	static bool initialized = false;
+	if (!initialized)
+	{
+		fill_fopen64_filefunc(&MinizipFilefunc);
+		MinizipFilefunc.zopen64_file = &MinizipOpenFunc;
+		initialized = true;
+	}
+	return &MinizipFilefunc;
+}
+
+template class boost::iostreams::stream<FileBuf>;
+
+/*static*/
+bool FileStream::CreateFile(const boost::filesystem::path& filename)
+{
+	FILE* f = do_open(filename.c_str(), CHAR_LITERAL("wb"));
+	bool result = (f != nullptr);
+	fclose(f);
+	return result;
+}
+
+FileBuf::FileBuf(const boost::filesystem::path& filename, std::ios_base::openmode mode)
+{
+	auto openmode = [mode]() -> std::basic_string<CharType>
+	{
+		using namespace std;
+		switch (mode & (~ios_base::ate & ~ios_base::binary))
+		{
+		case (ios_base::in):
+			return CHAR_LITERAL("r");
+		case (ios_base::out):
+		case (ios_base::out | ios_base::trunc):
+			return CHAR_LITERAL("w");
+		case (ios_base::app):
+		case (ios_base::out | ios_base::app):
+			return CHAR_LITERAL("a");
+		case (ios_base::out | ios_base::in):
+			return CHAR_LITERAL("r+");
+		case (ios_base::out | ios_base::in | ios_base::trunc):
+			return CHAR_LITERAL("w+");
+		case (ios_base::out | ios_base::in | ios_base::app):
+		case (ios_base::in | ios_base::app):
+			return CHAR_LITERAL("a+");
+		default:
+			throw std::ios_base::failure("invalid open mode");
+		}
+	}();
+
+	if (mode & std::ios_base::binary)
+		openmode += CHAR_LITERAL('b');
+
+	filePtr = do_open(filename.c_str(), openmode.c_str());
+
+	if (filePtr == nullptr)
+		throw std::ios_base::failure("could not open file");
+
+	if (mode & std::ios_base::ate) {
+		if (std::fseek(GETFILE, 0, SEEK_END)) {
+			fclose(GETFILE);
+			throw std::ios_base::failure("could not open file");
+		}
+	}
+}
+
+void FileBuf::close()
+{
+    std::fclose(GETFILE);
+}
+
+std::streamsize FileBuf::read(char* s, std::streamsize n)
+{
+	return static_cast<std::streamsize>(std::fread(s, 1, n, GETFILE));
+}
+
+std::streamsize FileBuf::write(const char* s, std::streamsize n)
+{
+	return static_cast<std::streamsize>(std::fwrite(s, 1, n, GETFILE));
+}
+
+std::streamoff FileBuf::seek(std::streamoff off, std::ios_base::seekdir way)
+{
+	const auto src = [way]() -> int
+	{
+		switch(way)
+		{
+		case std::ios_base::beg:
+			return SEEK_SET;
+		case std::ios_base::cur:
+			return SEEK_CUR;
+		case std::ios_base::end:
+			return SEEK_END;
+		default:
+			throw std::ios_base::failure("bad seek direction");
+		}
+	}();
+	if(std::fseek(GETFILE, off, src))
+		throw std::ios_base::failure("bad seek offset");
+
+	return static_cast<std::streamsize>(std::ftell(GETFILE));
+}

+ 53 - 0
lib/filesystem/FileStream.h

@@ -0,0 +1,53 @@
+#pragma once
+
+/*
+ * FileStream.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include <boost/iostreams/categories.hpp>
+#include <boost/iostreams/stream.hpp>
+
+class DLL_LINKAGE FileBuf
+{
+public:
+	typedef char char_type;
+	typedef struct category_ :
+		boost::iostreams::seekable_device_tag,
+		boost::iostreams::closable_tag
+		{} category;
+
+	FileBuf(const boost::filesystem::path& filename, std::ios_base::openmode mode);
+
+	std::streamsize read(char* s, std::streamsize n);
+	std::streamsize write(const char* s, std::streamsize n);
+	std::streamoff  seek(std::streamoff off, std::ios_base::seekdir way);
+
+	void close();
+private:
+	void* filePtr;
+};
+
+struct zlib_filefunc64_def_s;
+typedef zlib_filefunc64_def_s zlib_filefunc64_def;
+
+#ifdef VCMI_DLL
+extern template class DLL_LINKAGE boost::iostreams::stream<FileBuf>;
+#endif
+
+class DLL_LINKAGE FileStream : public boost::iostreams::stream<FileBuf>
+{
+public:
+	FileStream() = default;
+	explicit FileStream(const boost::filesystem::path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
+		: boost::iostreams::stream<FileBuf>(p, mode) {}
+
+	static bool CreateFile(const boost::filesystem::path& filename);
+
+	static zlib_filefunc64_def* GetMinizipFilefunc();
+};

+ 5 - 5
lib/filesystem/ISimpleResourceLoader.h

@@ -48,9 +48,9 @@ public:
 	 *
 	 * @return path or empty optional if file can't be accessed independently (e.g. file in archive)
 	 */
-	virtual boost::optional<std::string> getResourceName(const ResourceID & resourceName) const
+	virtual boost::optional<boost::filesystem::path> getResourceName(const ResourceID & resourceName) const
 	{
-		return boost::optional<std::string>();
+		return boost::optional<boost::filesystem::path>();
 	}
 
 	/**
@@ -58,13 +58,13 @@ public:
 	 *
 	 * @return std::set with names.
 	 */
-	virtual std::set<std::string> getResourceNames(const ResourceID & resourceName) const
+	virtual std::set<boost::filesystem::path> getResourceNames(const ResourceID & resourceName) const
 	{
-		std::set<std::string> result;
+		std::set<boost::filesystem::path> result;
 		auto rn = getResourceName(resourceName);
 		if(rn)
 		{
-			result.insert(*rn);
+			result.insert(rn->string());
 		}
 		return result;
 	}

+ 4 - 3
lib/logging/CLogger.h

@@ -12,6 +12,7 @@
 #pragma once
 
 #include "../CConsoleHandler.h"
+#include "../filesystem/FileStream.h"
 
 class CLogger;
 struct LogRecord;
@@ -147,7 +148,7 @@ private:
 
 /// Macros for tracing the control flow of the application conveniently. If the LOG_TRACE macro is used it should be
 /// the first statement in the function. Logging traces via this macro have almost no impact when the trace is disabled.
-/// 
+///
 #define RAII_TRACE(logger, onEntry, onLeave)			\
 	std::unique_ptr<CTraceLogger> ctl00;						\
 	if(logger->isTraceEnabled())						\
@@ -217,7 +218,7 @@ public:
 	CLogFormatter(CLogFormatter && move);
 
 	CLogFormatter(const std::string & pattern);
-	
+
 	CLogFormatter & operator=(const CLogFormatter & copy);
 	CLogFormatter & operator=(CLogFormatter && move);
 
@@ -302,7 +303,7 @@ public:
 	void write(const LogRecord & record) override;
 
 private:
-	boost::filesystem::ofstream file;
+	FileStream file;
 	CLogFormatter formatter;
 	mutable boost::mutex mx;
 };

+ 15 - 1
lib/mapObjects/CGHeroInstance.cpp

@@ -1067,7 +1067,7 @@ int3 CGHeroInstance::getSightCenter() const
 	return getPosition(false);
 }*/
 
-int CGHeroInstance::getSightRadious() const
+int CGHeroInstance::getSightRadius() const
 {
 	return 5 + getSecSkillLevel(SecondarySkill::SCOUTING) + valOfBonuses(Bonus::SIGHT_RADIOUS); //default + scouting
 }
@@ -1080,6 +1080,20 @@ si32 CGHeroInstance::manaRegain() const
 	return 1 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 8) + valOfBonuses(Bonus::MANA_REGENERATION); //1 + Mysticism level
 }
 
+si32 CGHeroInstance::getManaNewTurn() const
+{
+	if(visitedTown && visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1))
+	{
+		//if hero starts turn in town with mage guild - restore all mana
+		return std::max(mana, manaLimit());
+	}
+	si32 res = mana + manaRegain();
+	res = std::min(res, manaLimit());
+	res = std::max(res, mana);
+	res = std::max(res, 0);
+	return res;
+}
+
 // /**
 //  * Places an artifact in hero's backpack. If it's a big artifact equips it
 //  * or discards it if it cannot be equipped.

+ 5 - 4
lib/mapObjects/CGHeroInstance.h

@@ -76,10 +76,10 @@ public:
 
 	struct DLL_LINKAGE Patrol
 	{
-		Patrol(){patrolling=false;initialPos=int3();patrolRadious=-1;};
+		Patrol(){patrolling=false;initialPos=int3();patrolRadius=-1;};
 		bool patrolling;
 		int3 initialPos;
-		ui32 patrolRadious;
+		ui32 patrolRadius;
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
 			h & patrolling;
@@ -92,7 +92,7 @@ public:
 				patrolling = false;
 				initialPos = int3();
 			}
-			h & patrolRadious;
+			h & patrolRadius;
 		}
 	} patrol;
 
@@ -134,7 +134,7 @@ public:
 	}
 
 	//int3 getSightCenter() const; //"center" tile from which the sight distance is calculated
-	int getSightRadious() const override; //sight distance (should be used if player-owned structure)
+	int getSightRadius() const override; //sight distance (should be used if player-owned structure)
 	//////////////////////////////////////////////////////////////////////////
 
 	int getBoatType() const override; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral
@@ -151,6 +151,7 @@ public:
 	ui32 getLowestCreatureSpeed() const;
 	int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation'
 	si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day
+	si32 getManaNewTurn() const; //calculate how much mana this hero is going to have the next day
 	int getCurrentLuck(int stack=-1, bool town=false) const;
 	int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored
 

+ 2 - 2
lib/mapObjects/CGTownInstance.cpp

@@ -325,7 +325,7 @@ void CGDwelling::readJsonOptions(const JsonNode& json)
 	CGObjectInstance::readOwner(json);
 }
 
-int CGTownInstance::getSightRadious() const //returns sight distance
+int CGTownInstance::getSightRadius() const //returns sight distance
 {
 	if (subID == ETownType::TOWER)
 	{
@@ -1121,7 +1121,7 @@ void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResu
 		FoWChange fw;
 		fw.player = hero->tempOwner;
 		fw.mode = 1;
-		cb->getTilesInRange(fw.tiles, getSightCenter(), getSightRadious(), tempOwner, 1);
+		cb->getTilesInRange(fw.tiles, getSightCenter(), getSightRadius(), tempOwner, 1);
 		cb->sendAndApply (&fw);
 	}
 }

+ 1 - 1
lib/mapObjects/CGTownInstance.h

@@ -210,7 +210,7 @@ public:
 
 	bool passableFor(PlayerColor color) const override;
 	//int3 getSightCenter() const override; //"center" tile from which the sight distance is calculated
-	int getSightRadious() const override; //returns sight distance
+	int getSightRadius() const override; //returns sight distance
 	int getBoatType() const override; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral
 	void getOutOffsets(std::vector<int3> &offsets) const override; //offsets to obj pos when we boat can be placed. Parameter will be cleared
 	int getMarketEfficiency() const override; //=market count

+ 1 - 1
lib/mapObjects/CObjectHandler.cpp

@@ -257,7 +257,7 @@ int3 CGObjectInstance::getSightCenter() const
 	return visitablePos();
 }
 
-int CGObjectInstance::getSightRadious() const
+int CGObjectInstance::getSightRadius() const
 {
 	return 3;
 }

+ 1 - 1
lib/mapObjects/CObjectHandler.h

@@ -148,7 +148,7 @@ public:
 	/// Returns true if player can pass through visitable tiles of this object
 	virtual bool passableFor(PlayerColor color) const;
 	/// Range of revealed map around this object, counting from getSightCenter()
-	virtual int getSightRadious() const;
+	virtual int getSightRadius() const;
 	/// returns (x,y,0) offset to a visitable tile of object
 	virtual int3 getVisitableOffset() const;
 	/// Called mostly during map randomization to turn random object into a regular one (e.g. "Random Monster" into "Pikeman")

+ 68 - 66
lib/mapObjects/CQuest.cpp

@@ -45,20 +45,20 @@ static std::string & visitedTxt(const bool visited)
 	return VLC->generaltexth->allTexts[id];
 }
 
-bool CQuest::checkQuest (const CGHeroInstance * h) const
+bool CQuest::checkQuest(const CGHeroInstance * h) const
 {
 	switch (missionType)
 	{
 		case MISSION_NONE:
 			return true;
 		case MISSION_LEVEL:
-			if (m13489val <= h->level)
+			if(m13489val <= h->level)
 				return true;
 			return false;
 		case MISSION_PRIMARY_STAT:
-			for (int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
+			for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
 			{
-				if (h->getPrimSkillLevel(static_cast<PrimarySkill::PrimarySkill>(i)) < m2stats[i])
+				if(h->getPrimSkillLevel(static_cast<PrimarySkill::PrimarySkill>(i)) < m2stats[i])
 					return false;
 			}
 			return true;
@@ -68,9 +68,9 @@ bool CQuest::checkQuest (const CGHeroInstance * h) const
 				return true;
 			return false;
 		case MISSION_ART:
-			for (auto & elem : m5arts)
+			for(auto & elem : m5arts)
 			{
-				if (h->hasArt(elem, false, true))
+				if(h->hasArt(elem, false, true))
 					continue;
 				return false; //if the artifact was not found
 			}
@@ -80,31 +80,31 @@ bool CQuest::checkQuest (const CGHeroInstance * h) const
 				std::vector<CStackBasicDescriptor>::const_iterator cre;
 				TSlots::const_iterator it;
 				ui32 count;
-				for (cre = m6creatures.begin(); cre != m6creatures.end(); ++cre)
+				for(cre = m6creatures.begin(); cre != m6creatures.end(); ++cre)
 				{
-					for (count = 0, it = h->Slots().begin(); it !=  h->Slots().end(); ++it)
+					for(count = 0, it = h->Slots().begin(); it !=  h->Slots().end(); ++it)
 					{
-						if (it->second->type == cre->type)
+						if(it->second->type == cre->type)
 							count += it->second->count;
 					}
-					if (count < cre->count) //not enough creatures of this kind
+					if(count < cre->count) //not enough creatures of this kind
 						return false;
 				}
 			}
 			return true;
 		case MISSION_RESOURCES:
-			for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, +1)) //including Mithril ?
+			for(Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, +1)) //including Mithril ?
 			{	//Quest has no direct access to callback
-				if (h->cb->getResource (h->tempOwner, i) < m7resources[i])
+				if(h->cb->getResource (h->tempOwner, i) < m7resources[i])
 					return false;
 			}
 			return true;
 		case MISSION_HERO:
-			if (m13489val == h->type->ID.getNum())
+			if(m13489val == h->type->ID.getNum())
 				return true;
 			return false;
 		case MISSION_PLAYER:
-			if (m13489val == h->getOwner().getNum())
+			if(m13489val == h->getOwner().getNum())
 				return true;
 			return false;
 		default:
@@ -112,17 +112,17 @@ bool CQuest::checkQuest (const CGHeroInstance * h) const
 	}
 }
 
-void CQuest::getVisitText (MetaString &iwText, std::vector<Component> &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const
+void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const
 {
 	std::string text;
 	bool failRequirements = (h ? !checkQuest(h) : true);
 
-	if (firstVisit)
+	if(firstVisit)
 	{
 		isCustom = isCustomFirst;
 		iwText << (text = firstVisitText);
 	}
-	else if (failRequirements)
+	else if(failRequirements)
 	{
 		isCustom = isCustomNext;
 		iwText << (text = nextVisitText);
@@ -131,15 +131,15 @@ void CQuest::getVisitText (MetaString &iwText, std::vector<Component> &component
 	{
 		case MISSION_LEVEL:
 			components.push_back(Component (Component::EXPERIENCE, 0, m13489val, 0));
-			if (!isCustom)
+			if(!isCustom)
 				iwText.addReplacement(m13489val);
 			break;
 		case MISSION_PRIMARY_STAT:
 		{
 			MetaString loot;
-			for (int i = 0; i < 4; ++i)
+			for(int i = 0; i < 4; ++i)
 			{
-				if (m2stats[i])
+				if(m2stats[i])
 				{
 					components.push_back(Component (Component::PRIM_SKILL, i, m2stats[i], 0));
 					loot << "%d %s";
@@ -153,19 +153,19 @@ void CQuest::getVisitText (MetaString &iwText, std::vector<Component> &component
 			break;
 		case MISSION_KILL_HERO:
 			components.push_back(Component(Component::HERO_PORTRAIT, heroPortrait, 0, 0));
-			if (!isCustom)
+			if(!isCustom)
 				addReplacements(iwText, text);
 			break;
 		case MISSION_HERO:
 			//FIXME: portrait may not match hero, if custom portrait was set in map editor
 			components.push_back(Component (Component::HERO_PORTRAIT, VLC->heroh->heroes[m13489val]->imageIndex, 0, 0));
-			if (!isCustom)
+			if(!isCustom)
 				iwText.addReplacement(VLC->heroh->heroes[m13489val]->name);
 			break;
 		case MISSION_KILL_CREATURE:
 			{
 				components.push_back(Component(stackToKill));
-				if (!isCustom)
+				if(!isCustom)
 				{
 					addReplacements(iwText, text);
 				}
@@ -174,35 +174,35 @@ void CQuest::getVisitText (MetaString &iwText, std::vector<Component> &component
 		case MISSION_ART:
 		{
 			MetaString loot;
-			for (auto & elem : m5arts)
+			for(auto & elem : m5arts)
 			{
 				components.push_back(Component (Component::ARTIFACT, elem, 0, 0));
 				loot << "%s";
 				loot.addReplacement(MetaString::ART_NAMES, elem);
 			}
-			if (!isCustom)
+			if(!isCustom)
 				iwText.addReplacement(loot.buildList());
 		}
 			break;
 		case MISSION_ARMY:
 		{
 			MetaString loot;
-			for (auto & elem : m6creatures)
+			for(auto & elem : m6creatures)
 			{
 				components.push_back(Component(elem));
 				loot << "%s";
 				loot.addReplacement(elem);
 			}
-			if (!isCustom)
+			if(!isCustom)
 				iwText.addReplacement(loot.buildList());
 		}
 			break;
 		case MISSION_RESOURCES:
 		{
 			MetaString loot;
-			for (int i = 0; i < 7; ++i)
+			for(int i = 0; i < 7; ++i)
 			{
-				if (m7resources[i])
+				if(m7resources[i])
 				{
 					components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0));
 					loot << "%d %s";
@@ -210,29 +210,29 @@ void CQuest::getVisitText (MetaString &iwText, std::vector<Component> &component
 					loot.addReplacement(MetaString::RES_NAMES, i);
 				}
 			}
-			if (!isCustom)
+			if(!isCustom)
 				iwText.addReplacement(loot.buildList());
 		}
 			break;
 		case MISSION_PLAYER:
 			components.push_back(Component (Component::FLAG, m13489val, 0, 0));
-			if (!isCustom)
+			if(!isCustom)
 				iwText.addReplacement(VLC->generaltexth->colors[m13489val]);
 			break;
 	}
 }
 
-void CQuest::getRolloverText (MetaString &ms, bool onHover) const
+void CQuest::getRolloverText(MetaString &ms, bool onHover) const
 {
 	// Quests with MISSION_NONE type don't have a text for them
 	assert(missionType != MISSION_NONE);
 
-	if (onHover)
+	if(onHover)
 		ms << "\n\n";
 
 	ms << VLC->generaltexth->quests[missionType-1][onHover ? 3 : 4][textOption];
 
-	switch (missionType)
+	switch(missionType)
 	{
 		case MISSION_LEVEL:
 			ms.addReplacement(boost::lexical_cast<std::string>(m13489val));
@@ -306,10 +306,10 @@ void CQuest::getRolloverText (MetaString &ms, bool onHover) const
 	}
 }
 
-void CQuest::getCompletionText (MetaString &iwText, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
+void CQuest::getCompletionText(MetaString &iwText, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
 {
 	iwText << completedText;
-	switch (missionType)
+	switch(missionType)
 	{
 		case CQuest::MISSION_LEVEL:
 			if (!isCustomComplete)
@@ -398,14 +398,14 @@ CGSeerHut::CGSeerHut() : IQuestObject()
 
 void CGSeerHut::setObjToKill()
 {
-	if (quest->missionType == CQuest::MISSION_KILL_CREATURE)
+	if(quest->missionType == CQuest::MISSION_KILL_CREATURE)
 	{
 		quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :?
 		assert(quest->stackToKill.type);
 		quest->stackToKill.count = 0; //no count in info window
 		quest->stackDirection = checkDirection();
 	}
-	else if (quest->missionType == CQuest::MISSION_KILL_HERO)
+	else if(quest->missionType == CQuest::MISSION_KILL_HERO)
 	{
 		quest->heroName = getHeroToKill(false)->name;
 		quest->heroPortrait = getHeroToKill(false)->portrait;
@@ -416,6 +416,7 @@ void CGSeerHut::init()
 {
 	seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, cb->gameState()->getRandomGenerator());
 	quest->textOption = cb->gameState()->getRandomGenerator().nextInt(2);
+	quest->completedOption = cb->gameState()->getRandomGenerator().nextInt(1, 3);
 }
 
 void CGSeerHut::initObj()
@@ -423,39 +424,39 @@ void CGSeerHut::initObj()
 	init();
 
 	quest->progress = CQuest::NOT_ACTIVE;
-	if (quest->missionType)
+	if(quest->missionType)
 	{
-		if (!quest->isCustomFirst)
+		if(!quest->isCustomFirst)
 			quest->firstVisitText = VLC->generaltexth->quests[quest->missionType-1][0][quest->textOption];
-		if (!quest->isCustomNext)
+		if(!quest->isCustomNext)
 			quest->nextVisitText = VLC->generaltexth->quests[quest->missionType-1][1][quest->textOption];
-		if (!quest->isCustomComplete)
+		if(!quest->isCustomComplete)
 			quest->completedText = VLC->generaltexth->quests[quest->missionType-1][2][quest->textOption];
 	}
 	else
 	{
 		quest->progress = CQuest::COMPLETE;
-		quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->textOption];
+		quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->completedOption];
 	}
 }
 
-void CGSeerHut::getRolloverText (MetaString &text, bool onHover) const
+void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
 {
 	quest->getRolloverText (text, onHover);//TODO: simplify?
-	if (!onHover)
+	if(!onHover)
 		text.addReplacement(seerName);
 }
 
 std::string CGSeerHut::getHoverText(PlayerColor player) const
 {
 	std::string hoverName = getObjectName();
-	if (ID == Obj::SEER_HUT && quest->progress != CQuest::NOT_ACTIVE)
+	if(ID == Obj::SEER_HUT && quest->progress != CQuest::NOT_ACTIVE)
 	{
 		hoverName = VLC->generaltexth->allTexts[347];
-		boost::algorithm::replace_first(hoverName,"%s", seerName);
+		boost::algorithm::replace_first(hoverName, "%s", seerName);
 	}
 
-	if (quest->progress & quest->missionType) //rollover when the quest is active
+	if(quest->progress & quest->missionType) //rollover when the quest is active
 	{
 		MetaString ms;
 		getRolloverText (ms, true);
@@ -494,7 +495,7 @@ void IQuestObject::getVisitText (MetaString &text, std::vector<Component> &compo
 void CGSeerHut::getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
 {
 	quest->getCompletionText (text, components, isCustom, h);
-	switch (rewardType)
+	switch(rewardType)
 	{
 		case EXPERIENCE: components.push_back(Component (Component::EXPERIENCE, 0, h->calculateXp(rVal), 0));
 			break;
@@ -521,7 +522,7 @@ void CGSeerHut::getCompletionText(MetaString &text, std::vector<Component> &comp
 
 void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
 {
-	switch (what)
+	switch(what)
 	{
 		case 10:
 			quest->progress = static_cast<CQuest::Eprogress>(val);
@@ -531,44 +532,44 @@ void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
 
 void CGSeerHut::newTurn() const
 {
-	if (quest->lastDay >= 0 && quest->lastDay < cb->getDate()-1) //time is up
+	if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up
 	{
-		cb->setObjProperty (id, 10, CQuest::COMPLETE);
+		cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE);
 	}
 }
 
-void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
+void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
 {
 	InfoWindow iw;
 	iw.player = h->getOwner();
-	if (quest->progress < CQuest::COMPLETE)
+	if(quest->progress < CQuest::COMPLETE)
 	{
 		bool firstVisit = !quest->progress;
 		bool failRequirements = !checkQuest(h);
-		bool isCustom=false;
+		bool isCustom = false;
 
-		if (firstVisit)
+		if(firstVisit)
 		{
 			isCustom = quest->isCustomFirst;
-			cb->setObjProperty (id, 10, CQuest::IN_PROGRESS);
+			cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::IN_PROGRESS);
 
 			AddQuest aq;
 			aq.quest = QuestInfo (quest, this, visitablePos());
 			aq.player = h->tempOwner;
-			cb->sendAndApply (&aq); //TODO: merge with setObjProperty?
+			cb->sendAndApply(&aq); //TODO: merge with setObjProperty?
 		}
-		else if (failRequirements)
+		else if(failRequirements)
 		{
 			isCustom = quest->isCustomNext;
 		}
 
-		if (firstVisit || failRequirements)
+		if(firstVisit || failRequirements)
 		{
 			getVisitText (iw.text, iw.components, isCustom, firstVisit, h);
 
 			cb->showInfoDialog(&iw);
 		}
-		if (!failRequirements) // propose completion, also on first visit
+		if(!failRequirements) // propose completion, also on first visit
 		{
 			BlockingDialog bd (true, false);
 			bd.player = h->getOwner();
@@ -582,7 +583,7 @@ void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
 	}
 	else
 	{
-		iw.text << VLC->generaltexth->seerEmpty[quest->textOption];
+		iw.text << VLC->generaltexth->seerEmpty[quest->completedOption];
 		if (ID == Obj::SEER_HUT)
 			iw.text.addReplacement(seerName);
 		cb->showInfoDialog(&iw);
@@ -657,7 +658,7 @@ void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const
 			default:
 				break;
 		}
-		cb->setObjProperty (id, 10, CQuest::COMPLETE); //mission complete
+		cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete
 		completeQuest(h); //make sure to remove QuestGuard at the very end
 	}
 }
@@ -745,6 +746,7 @@ void CGQuestGuard::init()
 {
 	blockVisit = true;
 	quest->textOption = cb->gameState()->getRandomGenerator().nextInt(3, 5);
+	quest->completedOption = cb->gameState()->getRandomGenerator().nextInt(4, 5);
 }
 
 void CGQuestGuard::completeQuest(const CGHeroInstance *h) const
@@ -816,12 +818,12 @@ void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const
 		text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER);
 }
 
-bool CGBorderGuard::checkQuest (const CGHeroInstance * h) const
+bool CGBorderGuard::checkQuest(const CGHeroInstance * h) const
 {
 	return wasMyColorVisited (h->tempOwner);
 }
 
-void CGBorderGuard::onHeroVisit( const CGHeroInstance * h ) const
+void CGBorderGuard::onHeroVisit(const CGHeroInstance * h) const
 {
 	if (wasMyColorVisited (h->getOwner()) )
 	{
@@ -849,7 +851,7 @@ void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answ
 		cb->removeObject(this);
 }
 
-void CGBorderGate::onHeroVisit( const CGHeroInstance * h ) const //TODO: passability
+void CGBorderGate::onHeroVisit(const CGHeroInstance * h) const //TODO: passability
 {
 	if (!wasMyColorVisited (h->getOwner()) )
 	{

+ 16 - 2
lib/mapObjects/CQuest.h

@@ -37,8 +37,11 @@ public:
 	std::vector<CStackBasicDescriptor> m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant
 	std::vector<ui32> m7resources; //TODO: use resourceset?
 
-	//following field are used only for kill creature/hero missions, the original objects became inaccessible after their removal, so we need to store info needed for messages / hover text
+	// following fields are used only for kill creature/hero missions, the original
+	// objects became inaccessible after their removal, so we need to store info
+	// needed for messages / hover text
 	ui8 textOption;
+	ui8 completedOption;
 	CStackBasicDescriptor stackToKill;
 	ui8 stackDirection;
 	std::string heroName; //backup of hero name
@@ -66,7 +69,16 @@ public:
 	{
 		h & qid & missionType & progress & lastDay & m13489val & m2stats & m5arts & m6creatures & m7resources
 			& textOption & stackToKill & stackDirection & heroName & heroPortrait
-			& firstVisitText & nextVisitText & completedText & isCustomFirst & isCustomNext & isCustomComplete;
+			& firstVisitText & nextVisitText & completedText & isCustomFirst
+			& isCustomNext & isCustomComplete;
+		if(version >= 757)
+		{
+			h & completedOption;
+		}
+		else if(!h.saving)
+		{
+			completedOption = 1;
+		}
 	}
 };
 
@@ -118,6 +130,8 @@ public:
 		h & rewardType & rID & rVal & seerName;
 	}
 protected:
+	static const int OBJPROP_VISITED = 10;
+
 	void setPropertyDer(ui8 what, ui32 val) override;
 };
 

+ 126 - 55
lib/mapObjects/CRewardableObject.cpp

@@ -23,13 +23,13 @@
 
 bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const
 {
-	if (dayOfWeek != 0)
+	if(dayOfWeek != 0)
 	{
 		if (IObjectInterface::cb->getDate(Date::DAY_OF_WEEK) != dayOfWeek)
 			return false;
 	}
 
-	for (auto & reqStack : creatures)
+	for(auto & reqStack : creatures)
 	{
 		size_t count = 0;
 		for (auto slot : hero->Slots())
@@ -42,25 +42,25 @@ bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const
 			return false;
 	}
 
-	if (!IObjectInterface::cb->getPlayer(hero->tempOwner)->resources.canAfford(resources))
+	if(!IObjectInterface::cb->getPlayer(hero->tempOwner)->resources.canAfford(resources))
 		return false;
 
-	if (minLevel > hero->level)
+	if(minLevel > hero->level)
 		return false;
 
-	for (size_t i=0; i<primary.size(); i++)
+	for(size_t i=0; i<primary.size(); i++)
 	{
 		if (primary[i] > hero->getPrimSkillLevel(PrimarySkill::PrimarySkill(i)))
 			return false;
 	}
 
-	for (auto & skill : secondary)
+	for(auto & skill : secondary)
 	{
 		if (skill.second > hero->getSecSkillLevel(skill.first))
 			return false;
 	}
 
-	for (auto & art : artifacts)
+	for(auto & art : artifacts)
 	{
 		if (!hero->hasArt(art))
 			return false;
@@ -73,11 +73,11 @@ std::vector<ui32> CRewardableObject::getAvailableRewards(const CGHeroInstance *
 {
 	std::vector<ui32> ret;
 
-	for (size_t i=0; i<info.size(); i++)
+	for(size_t i=0; i<info.size(); i++)
 	{
 		const CVisitInfo & visit = info[i];
 
-		if ((visit.limiter.numOfGrants == 0 || visit.numOfGrants < visit.limiter.numOfGrants) // reward has unlimited uses or some are still available
+		if((visit.limiter.numOfGrants == 0 || visit.numOfGrants < visit.limiter.numOfGrants) // reward has unlimited uses or some are still available
 			&& visit.limiter.heroAllowed(hero))
 		{
 			logGlobal->debugStream() << "Reward " << i << " is allowed";
@@ -87,19 +87,25 @@ std::vector<ui32> CRewardableObject::getAvailableRewards(const CGHeroInstance *
 	return ret;
 }
 
+CVisitInfo CRewardableObject::getVisitInfo(int index, const CGHeroInstance *) const
+{
+	return info[index];
+}
+
 void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 {
 	auto grantRewardWithMessage = [&](int index) -> void
 	{
-		logGlobal->debugStream() << "Granting reward " << index << ". Message says: " << info[index].message.toString();
+		auto vi = getVisitInfo(index, h);
+		logGlobal->debugStream() << "Granting reward " << index << ". Message says: " << vi.message.toString();
 		// show message only if it is not empty
-		if (!info[index].message.toString().empty())
+		if (!vi.message.toString().empty())
 		{
 			InfoWindow iw;
 			iw.player = h->tempOwner;
 			iw.soundID = soundID;
-			iw.text = info[index].message;
-			info[index].reward.loadComponents(iw.components);
+			iw.text = vi.message;
+			vi.reward.loadComponents(iw.components, h);
 			cb->showInfoDialog(&iw);
 		}
 		// grant reward afterwards. Note that it may remove object
@@ -112,11 +118,11 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 		sd.soundID = soundID;
 		sd.text = onSelect;
 		for (auto index : rewards)
-			sd.components.push_back(info[index].reward.getDisplayedComponent());
+			sd.components.push_back(getVisitInfo(index, h).reward.getDisplayedComponent(h));
 		cb->showBlockingDialog(&sd);
 	};
 
-	if (!wasVisited(h))
+	if(!wasVisited(h))
 	{
 		auto rewards = getAvailableRewards(h);
 		logGlobal->debugStream() << "Visiting object with " << rewards.size() << " possible rewards";
@@ -175,15 +181,15 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
 
 void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const
 {
-	grantRewardAfterLevelup(info[selectedReward], hero);
+	grantRewardAfterLevelup(getVisitInfo(selectedReward, hero), hero);
 }
 
 void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
 {
-	if (answer == 0)
+	if(answer == 0)
 		return; // player refused
 
-	if (answer > 0 && answer-1 < info.size())
+	if(answer > 0 && answer-1 < info.size())
 	{
 		auto list = getAvailableRewards(hero);
 		grantReward(list[answer - 1], hero);
@@ -205,7 +211,7 @@ void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero)
 	cb->sendAndApply(&cov);
 	cb->setObjProperty(id, ObjProperty::REWARD_SELECT, rewardID);
 
-	grantRewardBeforeLevelup(info[rewardID], hero);
+	grantRewardBeforeLevelup(getVisitInfo(rewardID, hero), hero);
 }
 
 void CRewardableObject::grantRewardBeforeLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const
@@ -218,7 +224,7 @@ void CRewardableObject::grantRewardBeforeLevelup(const CVisitInfo & info, const
 
 	cb->giveResources(hero->tempOwner, info.reward.resources);
 
-	for (auto & entry : info.reward.secondary)
+	for(auto & entry : info.reward.secondary)
 	{
 		int current = hero->getSecSkillLevel(entry.first);
 		if( (current != 0 && current < entry.second) ||
@@ -235,11 +241,11 @@ void CRewardableObject::grantRewardBeforeLevelup(const CVisitInfo & info, const
 	si64 expToGive = 0;
 	expToGive += VLC->heroh->reqExp(hero->level+info.reward.gainedLevels) - VLC->heroh->reqExp(hero->level);
 	expToGive += hero->calculateXp(info.reward.gainedExp);
-	if (expToGive)
+	if(expToGive)
 		cb->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expToGive);
 
 	// hero is not blocked by levelup dialog - grant remainer immediately
-	if (!cb->isVisitCoveredByAnotherQuery(this, hero))
+	if(!cb->isVisitCoveredByAnotherQuery(this, hero))
 	{
 		grantRewardAfterLevelup(info, hero);
 	}
@@ -247,7 +253,7 @@ void CRewardableObject::grantRewardBeforeLevelup(const CVisitInfo & info, const
 
 void CRewardableObject::grantRewardAfterLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const
 {
-	if (info.reward.manaDiff || info.reward.manaPercentage >= 0)
+	if(info.reward.manaDiff || info.reward.manaPercentage >= 0)
 	{
 		si32 mana = hero->mana;
 		if (info.reward.manaPercentage >= 0)
@@ -269,7 +275,7 @@ void CRewardableObject::grantRewardAfterLevelup(const CVisitInfo & info, const C
 		cb->setMovePoints(&smp);
 	}
 
-	for (const Bonus & bonus : info.reward.bonuses)
+	for(const Bonus & bonus : info.reward.bonuses)
 	{
 		assert(bonus.source == Bonus::OBJECT);
 		assert(bonus.sid == ID);
@@ -280,16 +286,16 @@ void CRewardableObject::grantRewardAfterLevelup(const CVisitInfo & info, const C
 		cb->giveHeroBonus(&gb);
 	}
 
-	for (ArtifactID art : info.reward.artifacts)
+	for(ArtifactID art : info.reward.artifacts)
 		cb->giveHeroNewArtifact(hero, VLC->arth->artifacts[art],ArtifactPosition::FIRST_AVAILABLE);
 
-	if (!info.reward.spells.empty())
+	if(!info.reward.spells.empty())
 	{
 		std::set<SpellID> spellsToGive(info.reward.spells.begin(), info.reward.spells.end());
 		cb->changeSpells(hero, true, spellsToGive);
 	}
 
-	if (!info.reward.creatures.empty())
+	if(!info.reward.creatures.empty())
 	{
 		CCreatureSet creatures;
 		for (auto & crea : info.reward.creatures)
@@ -300,11 +306,11 @@ void CRewardableObject::grantRewardAfterLevelup(const CVisitInfo & info, const C
 
 	onRewardGiven(hero);
 
-	if (info.reward.removeObject)
+	if(info.reward.removeObject)
 		cb->removeObject(this);
 }
 
-bool CRewardableObject::wasVisited (PlayerColor player) const
+bool CRewardableObject::wasVisited(PlayerColor player) const
 {
 	switch (visitMode)
 	{
@@ -326,7 +332,7 @@ bool CRewardableObject::wasVisited (PlayerColor player) const
 	}
 }
 
-bool CRewardableObject::wasVisited (const CGHeroInstance * h) const
+bool CRewardableObject::wasVisited(const CGHeroInstance * h) const
 {
 	switch (visitMode)
 	{
@@ -341,19 +347,24 @@ bool CRewardableObject::wasVisited (const CGHeroInstance * h) const
 	}
 }
 
-void CRewardInfo::loadComponents(std::vector<Component> & comps) const
+void CRewardInfo::loadComponents(std::vector<Component> & comps,
+                                 const CGHeroInstance * h) const
 {
 	for (auto comp : extraComponents)
 		comps.push_back(comp);
 
-	if (gainedExp)    comps.push_back(Component(Component::EXPERIENCE, 0, gainedExp, 0));
+	if (gainedExp)
+	{
+		comps.push_back(Component(
+			Component::EXPERIENCE, 0, h->calculateXp(gainedExp), 0));
+	}
 	if (gainedLevels) comps.push_back(Component(Component::EXPERIENCE, 0, gainedLevels, 0));
 
-	if (manaDiff)   comps.push_back(Component(Component::PRIM_SKILL, 5, manaDiff,   0));
+	if (manaDiff) comps.push_back(Component(Component::PRIM_SKILL, 5, manaDiff, 0));
 
 	for (size_t i=0; i<primary.size(); i++)
 	{
-		if (primary[i] !=0)
+		if (primary[i] != 0)
 			comps.push_back(Component(Component::PRIM_SKILL, i, primary[i], 0));
 	}
 
@@ -376,10 +387,10 @@ void CRewardInfo::loadComponents(std::vector<Component> & comps) const
 	}
 }
 
-Component CRewardInfo::getDisplayedComponent() const
+Component CRewardInfo::getDisplayedComponent(const CGHeroInstance * h) const
 {
 	std::vector<Component> comps;
-	loadComponents(comps);
+	loadComponents(comps, h);
 	assert(!comps.empty());
 	return comps.front();
 }
@@ -422,7 +433,7 @@ void CRewardableObject::setPropertyDer(ui8 what, ui32 val)
 
 void CRewardableObject::newTurn() const
 {
-	if (resetDuration != 0 && cb->getDate(Date::DAY) > 1 && cb->getDate(Date::DAY) % (resetDuration) == 1)
+	if (resetDuration != 0 && cb->getDate(Date::DAY) > 1 && (cb->getDate(Date::DAY) % resetDuration) == 1)
 		cb->setObjProperty(id, ObjProperty::REWARD_RESET, 0);
 }
 
@@ -728,28 +739,74 @@ void CGBonusingObject::initObj()
 		break;
 	case Obj::STABLES:
 		configureMessage(info[0], 137, 136, soundBase::STORE);
-
 		configureBonusDuration(info[0], Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, 400, 0);
 		info[0].reward.movePoints = 400;
-		//TODO: upgrade champions to cavaliers
-/*
-		bool someUpgradeDone = false;
+		break;
+	}
+}
 
-		for (auto i = h->Slots().begin(); i != h->Slots().end(); ++i)
+CVisitInfo CGBonusingObject::getVisitInfo(int index, const CGHeroInstance *h) const
+{
+	if(ID == Obj::STABLES)
+	{
+		assert(index == 0);
+		for(auto& slot : h->Slots())
 		{
-			if(i->second->type->idNumber == CreatureID::CAVALIER)
+			if(slot.second->type->idNumber == CreatureID::CAVALIER)
 			{
-				cb->changeStackType(StackLocation(h, i->first), VLC->creh->creatures[CreatureID::CHAMPION]);
-				someUpgradeDone = true;
+				CVisitInfo vi(info[0]);
+				vi.message.clear();
+				vi.message.addTxt(MetaString::ADVOB_TXT, 138);
+				vi.reward.extraComponents.push_back(Component(
+					Component::CREATURE, CreatureID::CHAMPION, 0, 1));
+				return std::move(vi);
 			}
 		}
-		if (someUpgradeDone)
+	}
+	return info[index];
+}
+
+void CGBonusingObject::onHeroVisit(const CGHeroInstance *h) const
+{
+	CRewardableObject::onHeroVisit(h);
+	if(ID == Obj::STABLES)
+	{
+		//regardless of whether this hero visited stables or not, cavaliers must be upgraded
+		for(auto& slot : h->Slots())
 		{
-			grantMessage.addTxt(MetaString::ADVOB_TXT, 138);
-			iw.components.push_back(Component(Component::CREATURE,11,0,1));
-		}*/
-		break;
+			if(slot.second->type->idNumber == CreatureID::CAVALIER)
+			{
+				cb->changeStackType(StackLocation(h, slot.first),
+									VLC->creh->creatures[CreatureID::CHAMPION]);
+			}
+		}
+	}
+}
+
+bool CGBonusingObject::wasVisited(const CGHeroInstance * h) const
+{
+	if(ID == Obj::STABLES)
+	{
+		for(auto& slot : h->Slots())
+		{
+			if(slot.second->type->idNumber == CreatureID::CAVALIER)
+			{
+				// always display the reward message if the hero got cavaliers
+				return false;
+			}
+		}
+	}
+	return CRewardableObject::wasVisited(h);
+}
+
+void CGBonusingObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
+{
+	if(ID == Obj::STABLES && CRewardableObject::wasVisited(hero))
+	{
+		// reward message has been displayed - do not give the actual bonus
+		return;
 	}
+	CRewardableObject::grantReward(rewardID, hero);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1003,16 +1060,30 @@ void CGVisitableOPW::initObj()
 		soundID = soundBase::GENIE;
 		onEmpty.addTxt(MetaString::ADVOB_TXT, 165);
 
-		info.resize(2);
-		info[0].limiter.dayOfWeek = 7; // double amount on sunday
-		info[0].reward.resources[Res::GOLD] = 1000;
-		info[1].reward.resources[Res::GOLD] = 500;
+		info.resize(1);
+		info[0].reward.resources[Res::GOLD] = 500;
 		info[0].message.addTxt(MetaString::ADVOB_TXT, 164);
-		info[1].message.addTxt(MetaString::ADVOB_TXT, 164);
 		break;
 	}
 }
 
+void CGVisitableOPW::setPropertyDer(ui8 what, ui32 val)
+{
+	if(ID == Obj::WATER_WHEEL && what == ObjProperty::REWARD_RESET)
+	{
+		auto& reward = info[0].reward.resources[Res::GOLD];
+		if(info[0].numOfGrants == 0)
+		{
+			reward = 1000;
+		}
+		else
+		{
+			reward = 500;
+		}
+	}
+	CRewardableObject::setPropertyDer(what, val);
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 void CGMagicSpring::initObj()

+ 22 - 7
lib/mapObjects/CRewardableObject.h

@@ -104,9 +104,10 @@ public:
 	/// if set to true, object will be removed after granting reward
 	bool removeObject;
 
-	/// Generates list of components that describes reward
-	virtual void loadComponents(std::vector<Component> & comps) const;
-	Component getDisplayedComponent() const;
+	/// Generates list of components that describes reward for a specific hero
+	virtual void loadComponents(std::vector<Component> & comps,
+	                            const CGHeroInstance * h) const;
+	Component getDisplayedComponent(const CGHeroInstance * h) const;
 
 	CRewardInfo() :
 		gainedExp(0),
@@ -163,6 +164,7 @@ class DLL_LINKAGE CRewardableObject : public CArmedInstance
 
 	/// grants reward to hero
 	void grantRewardBeforeLevelup(const CVisitInfo & reward, const CGHeroInstance * hero) const;
+
 protected:
 	/// controls selection of reward granted to player
 	enum ESelectMode
@@ -184,9 +186,11 @@ protected:
 	/// filters list of visit info and returns rewards that can be granted to current hero
 	virtual std::vector<ui32> getAvailableRewards(const CGHeroInstance * hero) const;
 
-	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const;
+	virtual void grantReward(ui32 rewardID, const CGHeroInstance * hero) const;
+
+	virtual CVisitInfo getVisitInfo(int index, const CGHeroInstance *h) const;
 
-	/// Rewars that can be granted by an object
+	/// Rewards that can be granted by an object
 	std::vector<CVisitInfo> info;
 
 	/// MetaString's that contain text for messages for specific situations
@@ -215,8 +219,8 @@ public:
 	std::string getHoverText(const CGHeroInstance * hero) const override;
 
 	/// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player)
-	bool wasVisited (PlayerColor player) const override;
-	bool wasVisited (const CGHeroInstance * h) const override;
+	bool wasVisited(PlayerColor player) const override;
+	bool wasVisited(const CGHeroInstance * h) const override;
 
 	/// gives reward to player or ask for choice in case of multiple rewards
 	void onHeroVisit(const CGHeroInstance *h) const override;
@@ -262,11 +266,20 @@ public:
 
 class DLL_LINKAGE CGBonusingObject : public CRewardableObject //objects giving bonuses to luck/morale/movement
 {
+protected:
+	CVisitInfo getVisitInfo(int index, const CGHeroInstance *h) const override;
+
+	void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override;
+
 public:
 	void initObj() override;
 
 	CGBonusingObject();
 
+	void onHeroVisit(const CGHeroInstance *h) const override;
+
+	bool wasVisited(const CGHeroInstance * h) const override;
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CRewardableObject&>(*this);
@@ -306,6 +319,8 @@ public:
 
 	CGVisitableOPW();
 
+	void setPropertyDer(ui8 what, ui32 val) override;
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CRewardableObject&>(*this);

+ 34 - 17
lib/mapObjects/MiscObjects.cpp

@@ -25,7 +25,7 @@
 #include "../CPlayerState.h"
 
 std::map <si32, std::vector<ObjectInstanceID> > CGMagi::eyelist;
-ui8 CGObelisk::obeliskCount; //how many obelisks are on map
+ui8 CGObelisk::obeliskCount = 0; //how many obelisks are on map
 std::map<TeamID, ui8> CGObelisk::visited; //map: team_id => how many obelisks has been visited
 
 ///helpers
@@ -61,7 +61,7 @@ static std::string & visitedTxt(const bool visited)
 
 void CPlayersVisited::setPropertyDer( ui8 what, ui32 val )
 {
-	if(what == 10)
+	if(what == CPlayersVisited::OBJPROP_VISITED)
 		players.insert(PlayerColor(val));
 }
 
@@ -1467,7 +1467,7 @@ void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const
 	iw.soundID = soundBase::gazebo;
 	iw.player = h->getOwner();
 	if(!wasVisited(h->tempOwner))
-		cb->setObjProperty(id, 10, h->tempOwner.getNum());
+		cb->setObjProperty(id, CGWitchHut::OBJPROP_VISITED, h->tempOwner.getNum());
 	ui32 txt_id;
 	if(h->getSecSkillLevel(SecondarySkill(ability))) //you already know this skill
 	{
@@ -1588,7 +1588,7 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
 	}
 
 	if(!wasVisited(h->tempOwner))
-		cb->setObjProperty(id, 10, h->tempOwner.getNum());
+		cb->setObjProperty(id, CGShrine::OBJPROP_VISITED, h->tempOwner.getNum());
 
 	InfoWindow iw;
 	iw.soundID = soundBase::temple;
@@ -2040,7 +2040,7 @@ void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answ
 		//water = 0; land = 1; underground = 2;
 		cb->getAllTiles (fw.tiles, hero->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles
 		cb->sendAndApply (&fw);
-		cb->setObjProperty (id, 10, hero->tempOwner.getNum());
+		cb->setObjProperty (id, CCartographer::OBJPROP_VISITED, hero->tempOwner.getNum());
 	}
 }
 
@@ -2062,11 +2062,16 @@ void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const
 		iw.text.addTxt(MetaString::ADVOB_TXT, 96);
 		cb->sendAndApply(&iw);
 
-		cb->setObjProperty(id, 20, h->tempOwner.getNum()); //increment general visited obelisks counter
+		// increment general visited obelisks counter
+		cb->setObjProperty(id, CGObelisk::OBJPROP_INC, team.getNum());
 
 		openWindow(OpenWindow::PUZZLE_MAP, h->tempOwner.getNum());
 
-		cb->setObjProperty(id, 10, h->tempOwner.getNum()); //mark that particular obelisk as visited
+		// mark that particular obelisk as visited for all players in the team
+		for (auto & color : ts->players)
+		{
+			cb->setObjProperty(id, CGObelisk::OBJPROP_VISITED, color.getNum());
+		}
 	}
 	else
 	{
@@ -2081,6 +2086,12 @@ void CGObelisk::initObj()
 	obeliskCount++;
 }
 
+void CGObelisk::reset()
+{
+	obeliskCount = 0;
+	visited.clear();
+}
+
 std::string CGObelisk::getHoverText(PlayerColor player) const
 {
 	return getObjectName() + " " + visitedTxt(wasVisited(player));
@@ -2088,20 +2099,26 @@ std::string CGObelisk::getHoverText(PlayerColor player) const
 
 void CGObelisk::setPropertyDer( ui8 what, ui32 val )
 {
-	CPlayersVisited::setPropertyDer(what, val);
 	switch(what)
 	{
-	case 20:
-		assert(val < PlayerColor::PLAYER_LIMIT_I);
-		visited[TeamID(val)]++;
+		case CGObelisk::OBJPROP_INC:
+			{
+				assert(val < PlayerColor::PLAYER_LIMIT_I);
+				auto progress = ++visited[TeamID(val)];
+				logGlobal->debugStream() << boost::format("Player %d: obelisk progress %d / %d")
+					% val % static_cast<int>(progress) % static_cast<int>(obeliskCount);
 
-		if(visited[TeamID(val)] > obeliskCount)
-		{
-            logGlobal->errorStream() << "Error: Visited " << visited[TeamID(val)] << "\t\t" << obeliskCount;
-			assert(0);
-		}
+				if(progress > obeliskCount)
+				{
+					logGlobal->errorStream() << "Error: Visited " << progress << "\t\t" << obeliskCount;
+					assert(0);
+				}
 
-		break;
+				break;
+			}
+		default:
+			CPlayersVisited::setPropertyDer(what, val);
+			break;
 	}
 }
 

+ 4 - 0
lib/mapObjects/MiscObjects.h

@@ -28,6 +28,8 @@ public:
 		h & static_cast<CGObjectInstance&>(*this);
 		h & players;
 	}
+
+	static const int OBJPROP_VISITED = 10;
 };
 
 class DLL_LINKAGE CGCreature : public CArmedInstance //creatures on map
@@ -481,12 +483,14 @@ class DLL_LINKAGE CGDenOfthieves : public CGObjectInstance
 class DLL_LINKAGE CGObelisk : public CPlayersVisited
 {
 public:
+	static const int OBJPROP_INC = 20;
 	static ui8 obeliskCount; //how many obelisks are on map
 	static std::map<TeamID, ui8> visited; //map: team_id => how many obelisks has been visited
 
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void initObj() override;
 	std::string getHoverText(PlayerColor player) const override;
+	static void reset();
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 64 - 63
lib/mapping/CDrawRoadsOperation.cpp

@@ -12,7 +12,7 @@
 #include "CDrawRoadsOperation.h"
 #include "CMap.h"
 
-const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::patterns = 
+const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::patterns =
 {
 	//single tile. fall-back pattern
 	{
@@ -31,7 +31,7 @@ const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::pattern
 		{
           "?","-","+",
           "-","+","+",
-          "+","+","?" 
+          "+","+","?"
         },
         {2,5},
         {-1,-1},
@@ -43,7 +43,7 @@ const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::pattern
 		{
           "?","-","?",
           "-","+","+",
-          "?","+","?" 
+          "?","+","?"
         },
         {0,1},
         {0,3},
@@ -55,7 +55,7 @@ const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::pattern
 		{
           "?","-","?",
           "-","+","+",
-          "?","-","?"   
+          "?","-","?"
         },
         {15,15},{11,12},
         true,
@@ -66,7 +66,7 @@ const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::pattern
 		{
           "?","-","?",
           "-","+","-",
-          "?","+","?" 
+          "?","+","?"
         },
         {14,14},{9,10},
         false,
@@ -77,7 +77,7 @@ const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::pattern
 		{
           "?","+","?",
           "-","+","+",
-          "?","+","?"  
+          "?","+","?"
         },
         {6,7},{7,8},
         true,
@@ -88,46 +88,46 @@ const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::pattern
 		{
           "?","-","?",
           "+","+","+",
-          "?","+","?" 
+          "?","+","?"
         },
         {8,9},{5,6},
         false,
         true
 	},
-	//Straight Horizontal 
+	//Straight Horizontal
 	{
 		{
           "?","-","?",
           "+","+","+",
-          "?","-","?"  
+          "?","-","?"
         },
         {12,13},{11,12},
         false,
         false
 	},
-	//Straight Vertical 
+	//Straight Vertical
 	{
 		{
           "?","+","?",
           "-","+","-",
-          "?","+","?" 
+          "?","+","?"
         },
         {10,11},{9,10},
         false,
         false
 	},
-	//X-cross 
+	//X-cross
 	{
 		{
           "?","+","?",
           "+","+","+",
-          "?","+","?"  
+          "?","+","?"
         },
         {16,16},{4,4},
         false,
         false
-	}			
-	
+	}
+
 };
 
 static bool ruleIsNone(const std::string & rule)
@@ -140,45 +140,47 @@ static bool ruleIsSomething(const std::string & rule)
 	return rule == "+";
 }
 
+#ifndef NDEBUG
 static bool ruleIsAny(const std::string & rule)
 {
 	return rule == "?";
 }
+#endif
 
 ///CDrawRoadsOperation
 CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen):
 	CMapOperation(map),terrainSel(terrainSel), roadType(roadType), gen(gen)
 {
-	
+
 }
 
 void CDrawRoadsOperation::execute()
 {
 	std::set<int3> invalidated;
-	
+
 	for(const auto & pos : terrainSel.getSelectedItems())
 	{
 		auto & tile = map->getTile(pos);
 		tile.roadType = roadType;
-		
+
 		auto rect = extendTileAroundSafely(pos);
 		rect.forEach([&invalidated](const int3 & pos)
 		{
 			invalidated.insert(pos);
 		});
 	}
-	
-	updateTiles(invalidated);	
+
+	updateTiles(invalidated);
 }
 
 void CDrawRoadsOperation::undo()
 {
-  //TODO	
+  //TODO
 }
 
 void CDrawRoadsOperation::redo()
 {
-  //TODO	
+  //TODO
 }
 
 std::string CDrawRoadsOperation::getLabel() const
@@ -188,14 +190,14 @@ std::string CDrawRoadsOperation::getLabel() const
 
 bool CDrawRoadsOperation::canApplyPattern(const RoadPattern & pattern) const
 {
-	//TODO: this method should be virtual for river support	
+	//TODO: this method should be virtual for river support
 	return pattern.roadMapping.first >= 0;
 }
 
 void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const
 {
 	//todo: use cashing here and also in terrain patterns
-	
+
 	if(flip == 0)
 	{
 		return;
@@ -217,7 +219,7 @@ void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const
 		{
 			std::swap(pattern.data[i], pattern.data[6 + i]);
 		}
-	}	
+	}
 }
 
 
@@ -226,98 +228,98 @@ bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
 	return tile.roadType != ERoadType::NO_ROAD; //TODO: this method should be virtual for river support
 }
 
-void CDrawRoadsOperation::updateTiles(std::set<int3> & invalidated) 
+void CDrawRoadsOperation::updateTiles(std::set<int3> & invalidated)
 {
 	for(int3 coord : invalidated)
 	{
 		TerrainTile & tile = map->getTile(coord);
 		ValidationResult result(false);
-		
+
 		if(!needUpdateTile(tile))
 			continue;
-			
+
 		int bestPattern = -1;
-		
+
 		for(int k = 0; k < patterns.size(); ++k)
 		{
 			result = validateTile(patterns[k], coord);
-			
+
 			if(result.result)
 			{
 				bestPattern = k;
 				break;
 			}
 		}
-		
+
 		if(bestPattern != -1)
 		{
 			updateTile(tile, patterns[bestPattern], result.flip);
 		}
-		
+
 	}
 };
 
 bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
 {
-//TODO: this method should be virtual for river support	
+//TODO: this method should be virtual for river support
 
-   return map->getTile(pos).roadType != ERoadType::NO_ROAD;	
+   return map->getTile(pos).roadType != ERoadType::NO_ROAD;
 }
 
 
 void CDrawRoadsOperation::updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip)
 {
-  //TODO: this method should be virtual for river support	
-  
+  //TODO: this method should be virtual for river support
+
 	const std::pair<int, int> & mapping  = pattern.roadMapping;
-  
+
 	tile.roadDir = gen->nextInt(mapping.first, mapping.second);
-	tile.extTileFlags = (tile.extTileFlags & 0xCF) | (flip << 4); 
+	tile.extTileFlags = (tile.extTileFlags & 0xCF) | (flip << 4);
 }
 
 CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const RoadPattern & pattern, const int3 & pos)
 {
 	ValidationResult result(false);
-	
+
 	if(!canApplyPattern(pattern))
 		return result;
-	
-	
+
+
 	for(int flip = 0; flip < 4; ++flip)
 	{
-		if((flip == FLIP_PATTERN_BOTH) && !(pattern.hasHFlip && pattern.hasVFlip)) 
+		if((flip == FLIP_PATTERN_BOTH) && !(pattern.hasHFlip && pattern.hasVFlip))
 			continue;
-		if((flip == FLIP_PATTERN_HORIZONTAL) && !pattern.hasHFlip) 
+		if((flip == FLIP_PATTERN_HORIZONTAL) && !pattern.hasHFlip)
 			continue;
-		if((flip == FLIP_PATTERN_VERTICAL) && !(pattern.hasVFlip)) 
+		if((flip == FLIP_PATTERN_VERTICAL) && !(pattern.hasVFlip))
 			continue;
-		
-		RoadPattern flipped = pattern;		
-		
+
+		RoadPattern flipped = pattern;
+
 		flipPattern(flipped, flip);
-		
+
 		bool validated = true;
-		
+
 		for(int i = 0; i < 9; ++i)
 		{
 			if(4 == i)
 				continue;
 			int cx = pos.x + (i % 3) - 1;
 			int cy = pos.y + (i / 3) - 1;
-			
+
 			int3 currentPos(cx, cy, pos.z);
-			
+
 			bool hasSomething;
-			
+
 			if(!map->isInTheMap(currentPos))
 			{
 				hasSomething = true; //road/river can go out of map
 			}
 			else
 			{
-				hasSomething = tileHasSomething(currentPos);				
+				hasSomething = tileHasSomething(currentPos);
 			}
-			
+
 			if(ruleIsSomething(flipped.data[i]))
 			{
 				if(!hasSomething)
@@ -332,23 +334,22 @@ CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const Ro
 				{
 					validated = false;
 					break;
-				}		
+				}
 			}
 			else
 			{
-				assert(ruleIsAny(flipped.data[i]));			
-			}		
-			
+				assert(ruleIsAny(flipped.data[i]));
+			}
+
 		}
-		
+
 		if(validated)
 		{
 			result.result = true;
 			result.flip = flip;
-			return result;			
-		}		
+			return result;
+		}
 	}
-	
+
 	return result;
 }
-

+ 1 - 1
lib/mapping/CMap.cpp

@@ -215,7 +215,7 @@ CMapHeader::~CMapHeader()
 
 }
 
-CMap::CMap() : checksum(0), grailPos(-1, -1, -1), grailRadious(0), terrain(nullptr)
+CMap::CMap() : checksum(0), grailPos(-1, -1, -1), grailRadius(0), terrain(nullptr)
 {
 	allHeroes.resize(allowedHeroes.size());
 	allowedAbilities = VLC->heroh->getDefaultAllowedAbilities();

+ 1 - 1
lib/mapping/CMap.h

@@ -308,7 +308,7 @@ public:
 	std::vector<bool> allowedAbilities;
 	std::list<CMapEvent> events;
 	int3 grailPos;
-	int grailRadious;
+	int grailRadius;
 
 	//Central lists of items in game. Position of item in the vectors below is their (instance) id.
 	std::vector< ConstTransitivePtr<CGObjectInstance> > objects;

+ 3 - 3
lib/mapping/MapFormatH3M.cpp

@@ -1350,7 +1350,7 @@ void CMapLoaderH3M::readObjects()
 		case Obj::GRAIL:
 			{
 				map->grailPos = objPos;
-				map->grailRadious = reader.readUInt32();
+				map->grailRadius = reader.readUInt32();
 				continue;
 			}
 		case Obj::RANDOM_DWELLING: //same as castle + level range
@@ -1650,8 +1650,8 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i
 
 	nhi->formation = reader.readUInt8();
 	loadArtifactsOfHero(nhi);
-	nhi->patrol.patrolRadious = reader.readUInt8();
-	if(nhi->patrol.patrolRadious == 0xff)
+	nhi->patrol.patrolRadius = reader.readUInt8();
+	if(nhi->patrol.patrolRadius == 0xff)
 	{
 		nhi->patrol.patrolling = false;
 	}

+ 2 - 2
lib/minizip/ioapi.c

@@ -221,7 +221,7 @@ static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream)
     return ret;
 }
 
-void fill_fopen_filefunc (pzlib_filefunc_def)
+void fill_fopen_filefunc(pzlib_filefunc_def)
   zlib_filefunc_def* pzlib_filefunc_def;
 {
     pzlib_filefunc_def->zopen_file = fopen_file_func;
@@ -234,7 +234,7 @@ void fill_fopen_filefunc (pzlib_filefunc_def)
     pzlib_filefunc_def->opaque = NULL;
 }
 
-void fill_fopen64_filefunc (zlib_filefunc64_def*  pzlib_filefunc_def)
+void fill_fopen64_filefunc(zlib_filefunc64_def*  pzlib_filefunc_def)
 {
     pzlib_filefunc_def->zopen64_file = fopen64_file_func;
     pzlib_filefunc_def->zread_file = fread_file_func;

+ 1 - 1
lib/rmg/CRmgTemplateZone.cpp

@@ -1128,7 +1128,7 @@ bool CRmgTemplateZone::createTreasurePile(CMapGenerator* gen, int3 &pos, float m
 				info.occupiedPositions.insert(blockPos);
 				info.blockedPositions.insert(blockPos);
 			}
-			info.occupiedPositions.insert(visitablePos);
+			info.occupiedPositions.insert(visitablePos + oi.templ.getVisitableOffset());
 
 			currentValue += oi.value;
 

+ 35 - 29
lib/spells/CDefaultSpellMechanics.cpp

@@ -154,6 +154,13 @@ bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, Adve
 	}
 
 	const CGHeroInstance * caster = parameters.caster;
+
+	if(caster->inTownGarrison)
+	{
+		env->complain("Attempt to cast an adventure spell in town garrison");
+		return false;
+	}
+
 	const int cost = caster->getSpellCost(owner);
 
 	if(!caster->canCastThisSpell(owner))
@@ -174,7 +181,7 @@ bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, Adve
 		asc.spellID = owner->id;
 		env->sendAndApply(&asc);
 	}
-	
+
 	switch(applyAdventureEffects(env, parameters))
 	{
 	case ESpellCastResult::OK:
@@ -184,7 +191,7 @@ bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, Adve
 			sm.absolute = false;
 			sm.val = -cost;
 			env->sendAndApply(&sm);
-			return true;			
+			return true;
 		}
 		break;
 	case ESpellCastResult::CANCEL:
@@ -224,16 +231,16 @@ ESpellCastResult DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnv
 void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
 {
 	logGlobal->debugStream() << "Started spell cast. Spell: "<<owner->name<<"; mode:"<<parameters.mode;
-	
+
 	if(nullptr == parameters.caster)
 	{
 		env->complain("No spell-caster provided.");
-		return;		
+		return;
 	}
-	
+
 	BattleSpellCast sc;
 	prepareBattleCast(parameters, sc);
-	
+
 	//check it there is opponent hero
 	const ui8 otherSide = 1-parameters.casterSide;
 	const CGHeroInstance * otherHero = nullptr;
@@ -247,7 +254,7 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 		spellCost = parameters.cb->battleGetSpellCost(owner, parameters.casterHero);
 
 		if(nullptr != otherHero) //handle mana channel
-		{			
+		{
 			int manaChannel = 0;
 			for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
 			{
@@ -271,10 +278,10 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 	logGlobal->debugStream() << "will affect: " << attackedCres.size() << " stacks";
 
 	std::vector <const CStack*> reflected;//for magic mirror
-	//checking if creatures resist	
+	//checking if creatures resist
 	handleResistance(env, attackedCres, sc);
 	//it is actual spell and can be reflected to single target, no recurrence
-	const bool tryMagicMirror = owner->isNegative() && owner->level && owner->getLevelInfo(0).range == "0";	
+	const bool tryMagicMirror = owner->isNegative() && owner->level && owner->getLevelInfo(0).range == "0";
 	if(tryMagicMirror)
 	{
 		for(auto s : attackedCres)
@@ -295,7 +302,7 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 			effect.effect = 3;
 			effect.stack = s->ID;
 			sc.customEffects.push_back(effect);
-		}		
+		}
 	}
 
 	for(auto cre : attackedCres)
@@ -376,13 +383,13 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 	}
 }
 
-void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet, 
+void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
 	const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const
 {
 	const std::string attackedName = attackedStack->getName();
 	const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
 	const std::string attackedNamePl = attackedStack->getCreature()->namePl;
-	
+
 	auto getPluralFormat = [attackedStack](const int baseTextID) -> boost::format
 	{
 		return boost::format(VLC->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID + 1 : baseTextID)]);
@@ -470,7 +477,7 @@ void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & log
 			logLines.push_back(text.str());
 		}
 		break;
-	}	
+	}
 }
 
 void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
@@ -479,7 +486,7 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
 	if(owner->isOffensiveSpell())
 	{
 		int spellDamage = parameters.effectValue;
-		
+
 		int chainLightningModifier = 0;
 		for(auto & attackedCre : ctx.attackedCres)
 		{
@@ -522,13 +529,13 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
 				vstd::amax(maxDuration, b.turnsRemain);
 				sse.effect.push_back(b);
 			}
-			//if all spell effects have special duration, use it 
-			duration = maxDuration;			
+			//if all spell effects have special duration, use it
+			duration = maxDuration;
 		}
 		//fix to original config: shield should display damage reduction
 		if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
 		{
-			sse.effect.back().val = (100 - sse.effect.back().val); 
+			sse.effect.back().val = (100 - sse.effect.back().val);
 		}
 		//we need to know who cast Bind
 		if(owner->id == SpellID::BIND && parameters.casterStack)
@@ -593,7 +600,7 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
 		if(!sse.stacks.empty())
 			env->sendAndApply(&sse);
 
-	}	
+	}
 }
 
 std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
@@ -738,7 +745,7 @@ std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargeting
 
 ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
 {
-	//no problems by default, this method is for spell-specific problems	
+	//no problems by default, this method is for spell-specific problems
 	return ESpellCastProblem::OK;
 }
 
@@ -756,7 +763,7 @@ void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast
 		if(sourceSpell != nullptr)
 		{
 			//Special case: DISRUPTING_RAY is "immune" to dispell
-			//Other even PERMANENT effects can be removed (f.e. BIND)						
+			//Other even PERMANENT effects can be removed (f.e. BIND)
 			if(sourceSpell->id == SpellID::DISRUPTING_RAY)
 				return false;
 		}
@@ -766,7 +773,7 @@ void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast
 	{
 		CStack *s = battle->getStack(stackID);
 		s->popBonuses(CSelector(localSelector).And(selector));
-	}	
+	}
 }
 
 void DefaultSpellMechanics::castMagicMirror(const SpellCastEnvironment* env, BattleSpellCastParameters& parameters) const
@@ -781,12 +788,12 @@ void DefaultSpellMechanics::castMagicMirror(const SpellCastEnvironment* env, Bat
 	if(!destination.isValid())
 	{
 		env->complain("MagicMirror: invalid destination");
-		return;		
+		return;
 	}
 
 	BattleSpellCast sc;
 	prepareBattleCast(parameters, sc);
-	
+
 	//calculating affected creatures for all spells
 	//must be vector, as in Chain Lightning order matters
 	std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
@@ -802,14 +809,14 @@ void DefaultSpellMechanics::castMagicMirror(const SpellCastEnvironment* env, Bat
 	{
 		sc.affectedCres.insert(cre->ID);
 	}
-	
+
 	StacksInjured si;
 	SpellCastContext ctx(attackedCres, sc, si);
 	applyBattleEffects(env, parameters, ctx);
 
 	env->sendAndApply(&sc);
 	if(!si.stacks.empty()) //after spellcast info shows
-		env->sendAndApply(&si);	
+		env->sendAndApply(&si);
 	logGlobal->debugStream() << "Finished spell cast. Spell: "<<owner->name<<"; mode: MAGIC_MIRROR";
 }
 
@@ -842,8 +849,8 @@ void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, s
 			effect.effect = 78;
 			effect.stack = s->ID;
 			sc.customEffects.push_back(effect);
-		}		
-	}	
+		}
+	}
 }
 
 void DefaultSpellMechanics::prepareBattleCast(const BattleSpellCastParameters& parameters, BattleSpellCast& sc) const
@@ -855,6 +862,5 @@ void DefaultSpellMechanics::prepareBattleCast(const BattleSpellCastParameters& p
 	sc.dmgToDisplay = 0;
 	sc.castByHero = parameters.mode == ECastingMode::HERO_CASTING;
 	sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
-	sc.manaGained = 0;	
+	sc.manaGained = 0;
 }
-

+ 1 - 1
lib/spells/SpellMechanics.h

@@ -16,4 +16,4 @@
 #include "CDefaultSpellMechanics.h"
 #include "AdventureSpellMechanics.h"
 #include "BattleSpellMechanics.h"
-#include "CreatureSpellMechanics.h"
+#include "CreatureSpellMechanics.h"

+ 131 - 36
server/CGameHandler.cpp

@@ -662,8 +662,8 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 		sendAndApply(&cs);
 	}
 
-	cab1.takeFromArmy(this);
-	cab2.takeFromArmy(this); //take casualties after battle is deleted
+	cab1.updateArmy(this);
+	cab2.updateArmy(this); //take casualties after battle is deleted
 
 	//if one hero has lost we will erase him
 	if(battleResult.data->winner!=0 && hero1)
@@ -1324,6 +1324,25 @@ void CGameHandler::newTurn()
 
 	std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = gs->hpool.heroesPool;
 
+	for(auto& hp : pool)
+	{
+		auto hero = hp.second;
+		if(hero->isInitialized() && hero->stacks.size())
+		{
+			// reset retreated or surrendered heroes
+			auto maxmove = hero->maxMovePoints(true);
+			// if movement is greater than maxmove, we should decrease it
+			if(hero->movement != maxmove || hero->mana < hero->manaLimit())
+			{
+				NewTurn::Hero hth;
+				hth.id = hero->id;
+				hth.move = maxmove;
+				hth.mana = hero->getManaNewTurn();
+				n.heroes.insert(hth);
+			}
+		}
+	}
+
 	for (auto & elem : gs->players)
 	{
 		if(elem.first == PlayerColor::NEUTRAL)
@@ -1351,7 +1370,9 @@ void CGameHandler::newTurn()
 					banned = h->type->heroClass;
 				}
 				else
+				{
 					sah.hid[j] = -1;
+				}
 			}
 
 			sendAndApply(&sah);
@@ -1368,11 +1389,7 @@ void CGameHandler::newTurn()
 			hth.id = h->id;
 			// TODO: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356
 			hth.move = h->maxMovePoints(gs->map->getTile(h->getPosition(false)).terType != ETerrainType::WATER, new TurnInfo(h, 1));
-
-			if(h->visitedTown && h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1)) //if hero starts turn in town with mage guild
-				hth.mana = std::max(h->mana, h->manaLimit()); //restore all mana
-			else
-				hth.mana = std::max((si32)(0), std::max(h->mana, std::min((si32)(h->mana + h->manaRegain()), h->manaLimit())));
+			hth.mana = h->getManaNewTurn();
 
 			n.heroes.insert(hth);
 
@@ -1852,7 +1869,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo
 		{
 			obj->onHeroLeave(h);
 		}
-		this->getTilesInRange(tmh.fowRevealed, h->getSightCenter()+(tmh.end-tmh.start), h->getSightRadious(), h->tempOwner, 1);
+		this->getTilesInRange(tmh.fowRevealed, h->getSightCenter()+(tmh.end-tmh.start), h->getSightRadius(), h->tempOwner, 1);
 	};
 
 	auto doMove = [&](TryMoveHero::EResult result, EGuardLook lookForGuards,
@@ -2719,7 +2736,7 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
 	FoWChange fw;
 	fw.player = t->tempOwner;
 	fw.mode = 1;
-	getTilesInRange(fw.tiles, t->getSightCenter(), t->getSightRadious(), t->tempOwner, 1);
+	getTilesInRange(fw.tiles, t->getSightCenter(), t->getSightRadius(), t->tempOwner, 1);
 	sendAndApply(&fw);
 
 	if(t->visitingHero)
@@ -3907,18 +3924,16 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			//TODO: From Strategija:
 			//Summon Demon is a level 2 spell.
 		{
-			StartAction start_action(ba);
-			sendAndApply(&start_action);
-
 			const CStack *summoner = gs->curB->battleGetStackByID(ba.stackNumber),
 				*destStack = gs->curB->battleGetStackByPos(ba.destinationTile, false);
 
+			CreatureID summonedType(summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype);//in case summoner can summon more than one type of monsters... scream!
 			BattleStackAdded bsa;
 			bsa.attacker = summoner->attackerOwned;
 
-			bsa.creID = CreatureID(summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype); //in case summoner can summon more than one type of monsters... scream!
+			bsa.creID = summonedType;
 			ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID.toEnum());
-			ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;
+			ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;//todo: ignore AGE effect
 
 			ui64 canRiseHp = std::min(targetHealth, risedHp);
 			ui32 canRiseAmount = canRiseHp / VLC->creh->creatures.at(bsa.creID)->MaxHealth();
@@ -3930,6 +3945,9 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 
 			if (bsa.amount) //there's rare possibility single creature cannot rise desired type
 			{
+				StartAction start_action(ba);
+				sendAndApply(&start_action);
+
 				BattleStacksRemoved bsr; //remove body
 				bsr.stackIDs.insert(destStack->ID);
 				sendAndApply(&bsr);
@@ -3941,9 +3959,9 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				ssp.val = -1;
 				ssp.absolute = false;
 				sendAndApply(&ssp);
-			}
 
-			sendAndApply(&end_action);
+				sendAndApply(&end_action);
+			}
 			break;
 		}
 		case Battle::MONSTER_SPELL:
@@ -5805,11 +5823,11 @@ void CGameHandler::changeFogOfWar(int3 center, ui32 radius, PlayerColor player,
 		auto p = gs->getPlayer(player);
 		for (auto h : p->heroes)
 		{
-			getTilesInRange(observedTiles, h->getSightCenter(), h->getSightRadious(), h->tempOwner, -1);
+			getTilesInRange(observedTiles, h->getSightCenter(), h->getSightRadius(), h->tempOwner, -1);
 		}
 		for (auto t : p->towns)
 		{
-			getTilesInRange(observedTiles, t->getSightCenter(), t->getSightRadious(), t->tempOwner, -1);
+			getTilesInRange(observedTiles, t->getSightCenter(), t->getSightRadius(), t->tempOwner, -1);
 		}
 		for (auto tile : observedTiles)
 			vstd::erase_if_present (tiles, tile);
@@ -5878,7 +5896,8 @@ void CGameHandler::duelFinished()
 	return;
 }
 
-CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat)
+CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, BattleInfo *bat):
+	army(_army)
 {
 	heroWithDeadCommander = ObjectInstanceID();
 
@@ -5886,50 +5905,111 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleI
 	if(color == PlayerColor::UNFLAGGABLE)
 		color = PlayerColor::NEUTRAL;
 
+	auto killStack = [&, this](const SlotID slot, const CStackInstance * instance)
+	{
+		StackLocation sl(army, slot);
+		newStackCounts.push_back(TStackAndItsNewCount(sl, 0));
+		if(nullptr == instance)
+			return;
+		auto c = dynamic_cast <const CCommanderInstance *>(instance);
+		if (c) //switch commander status to dead
+		{
+			auto h = dynamic_cast <const CGHeroInstance *>(army);
+			if (h && h->commander == c)
+				heroWithDeadCommander = army->id; //TODO: unify commander handling
+		}
+	};
+
+	//1. Find removed stacks.
+	for(const auto & slotInfo : army->stacks)
+	{
+		const SlotID slot = slotInfo.first;
+		const CStackInstance * instance = slotInfo.second;
+
+		if(nullptr != instance)//just in case
+		{
+			bool found = false;
+			for(const CStack * sta : bat->stacks)
+			{
+				if(sta->base == instance)
+				{
+					found = true;
+					break;
+				}
+			}
+			//stack in this slot was removed == it is dead
+			if(!found)
+				killStack(slot, instance);
+		}
+	}
+
 	for(CStack *st : bat->stacks)
 	{
-		if(vstd::contains(st->state, EBattleStackState::SUMMONED)) //don't take into account summoned stacks
+		if(vstd::contains(st->state, EBattleStackState::SUMMONED)) //don't take into account temporary summoned stacks
 			continue;
 		if (st->owner != color) //remove only our stacks
 			continue;
 
+		logGlobal->debugStream() << "Calculating casualties for " << st->nodeName();
+
 		//FIXME: this info is also used in BattleInfo::calculateCasualties, refactor
 		st->count = std::max (0, st->count - st->resurrected);
 
-		if (!st->count && !st->base) //we can imagine stacks of war mahcines that are not spawned by artifacts?
+		if(st->slot == SlotID::ARROW_TOWERS_SLOT)
+		{
+			//do nothing
+			logGlobal->debugStream() << "Ignored arrow towers stack";
+		}
+		else if(st->slot == SlotID::WAR_MACHINES_SLOT)
 		{
 			auto warMachine = VLC->arth->creatureToMachineID(st->type->idNumber);
+
+			if(warMachine == ArtifactID::NONE)
+			{
+				logGlobal->errorStream() << "Invalid creature in war machine virtual slot: " << st->nodeName();
+			}
 			//catapult artifact remain even if "creature" killed in siege
-			if(warMachine != ArtifactID::NONE && warMachine != ArtifactID::CATAPULT)
+			else if(warMachine != ArtifactID::CATAPULT && !st->count)
 			{
+				logGlobal->debugStream() << "War machine has been destroyed";
 				auto hero = dynamic_ptr_cast<CGHeroInstance> (army);
 				if (hero)
 					removedWarMachines.push_back (ArtifactLocation(hero, hero->getArtPos(warMachine, true)));
+				else
+					logGlobal->errorStream() << "War machine in army without hero";
 			}
 		}
-
-		if(!army->slotEmpty(st->slot) && st->count < army->getStackCount(st->slot))
+		else if(st->slot == SlotID::SUMMONED_SLOT_PLACEHOLDER)
 		{
-			StackLocation sl(army, st->slot);
-			if(st->alive())
-				newStackCounts.push_back(std::pair<StackLocation, int>(sl, st->count));
-			else
-				newStackCounts.push_back(std::pair<StackLocation, int>(sl, 0));
+			if(st->alive() && st->count > 0)
+			{
+				logGlobal->debugStream() << "Stack has been permanently summoned";
+				//this stack was permanently summoned
+				const CreatureID summonedType = st->type->idNumber;
+				summoned[summonedType] += st->count;
+			}
 		}
-		if (st->base && !st->count)
+		else if(st->base && !army->slotEmpty(st->slot))
 		{
-			auto c = dynamic_cast <const CCommanderInstance *>(st->base);
-			if (c) //switch commander status to dead
+			if(st->count == 0 || !st->alive())
+			{
+				killStack(st->slot, st->base);
+				logGlobal->debugStream() << "Stack has been destroyed";
+			}
+			else if(st->count < army->getStackCount(st->slot))
 			{
-				auto h = dynamic_cast <const CGHeroInstance *>(army);
-				if (h && h->commander == c)
-					heroWithDeadCommander = army->id; //TODO: unify commander handling
+				StackLocation sl(army, st->slot);
+				newStackCounts.push_back(TStackAndItsNewCount(sl, st->count));
 			}
 		}
+		else
+		{
+			logGlobal->warnStream() << "Unhandled stack " << st->nodeName();
+		}
 	}
 }
 
-void CasualtiesAfterBattle::takeFromArmy(CGameHandler *gh)
+void CasualtiesAfterBattle::updateArmy(CGameHandler *gh)
 {
 	for(TStackAndItsNewCount &ncount : newStackCounts)
 	{
@@ -5938,6 +6018,21 @@ void CasualtiesAfterBattle::takeFromArmy(CGameHandler *gh)
 		else
 			gh->eraseStack(ncount.first, true);
 	}
+	for(auto summoned_iter : summoned)
+	{
+		SlotID slot = army->getSlotFor(summoned_iter.first);
+		if(slot.validSlot())
+		{
+			StackLocation location(army, slot);
+			gh->addToSlot(location, summoned_iter.first.toCreature(), summoned_iter.second);
+		}
+		else
+		{
+			//even if it will be possible to summon anything permanently it should be checked for free slot
+			//necromancy is handled separately
+			gh->complain("No free slot to put summoned creature");
+		}
+	}
 	for (auto al : removedWarMachines)
 	{
 		gh->removeArtifact(al);

+ 6 - 3
server/CGameHandler.h

@@ -69,13 +69,16 @@ public:
 struct CasualtiesAfterBattle
 {
 	typedef std::pair<StackLocation, int> TStackAndItsNewCount;
+	typedef std::map<CreatureID, TQuantity> TSummoned;
 	enum {ERASE = -1};
+	const CArmedInstance * army;
 	std::vector<TStackAndItsNewCount> newStackCounts;
 	std::vector<ArtifactLocation> removedWarMachines;
-	ObjectInstanceID heroWithDeadCommander; //TODO: unify stack loactions
+	TSummoned summoned;
+	ObjectInstanceID heroWithDeadCommander; //TODO: unify stack locations
 
-	CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat);
-	void takeFromArmy(CGameHandler *gh);
+	CasualtiesAfterBattle(const CArmedInstance * _army, BattleInfo *bat);
+	void updateArmy(CGameHandler *gh);
 };
 
 class CGameHandler : public IGameCallback, CBattleInfoCallback

部分文件因文件數量過多而無法顯示