瀏覽代碼

Finally shattered CObjectHandler.cpp into tiny bits

- This file is now split into multiple smaller files in mapObjects
directory
- CObjectHandler itself now contains only core classes (Handler itself,
CGObject and interfaces)
- Cleaned up excessive #include's through whole project
Ivan Savenko 11 年之前
父節點
當前提交
652ceb2bde
共有 71 個文件被更改,包括 7779 次插入7403 次删除
  1. 1 1
      AI/VCAI/AIUtility.cpp
  2. 2 2
      AI/VCAI/AIUtility.h
  3. 1 1
      AI/VCAI/Fuzzy.cpp
  4. 1 1
      AI/VCAI/Goals.h
  5. 1 2
      AI/VCAI/VCAI.cpp
  6. 2 2
      AI/VCAI/VCAI.h
  7. 1 1
      CCallback.cpp
  8. 1 1
      client/AdventureMapClasses.cpp
  9. 1 1
      client/CAdvmapInterface.cpp
  10. 1 1
      client/CCastleInterface.cpp
  11. 1 1
      client/CHeroWindow.cpp
  12. 2 2
      client/CKingdomInterface.cpp
  13. 2 2
      client/CMT.cpp
  14. 2 2
      client/CPlayerInterface.cpp
  15. 2 2
      client/CPreGame.cpp
  16. 1 1
      client/CQuestLog.cpp
  17. 1 1
      client/CSpellWindow.cpp
  18. 2 2
      client/Client.cpp
  19. 1 1
      client/GUIClasses.cpp
  20. 3 3
      client/Graphics.cpp
  21. 2 2
      client/NetPacksClient.cpp
  22. 1 1
      client/battle/CBattleInterface.cpp
  23. 1 1
      client/battle/CBattleInterfaceClasses.cpp
  24. 1 1
      client/mapHandler.cpp
  25. 3 1
      lib/BattleState.h
  26. 3 1
      lib/CArtHandler.cpp
  27. 2 0
      lib/CCreatureHandler.cpp
  28. 1 1
      lib/CCreatureSet.cpp
  29. 1 1
      lib/CGameState.h
  30. 2 0
      lib/CHeroHandler.cpp
  31. 24 13
      lib/CMakeLists.txt
  32. 1 2
      lib/Connection.h
  33. 1 0
      lib/IGameCallback.cpp
  34. 1 2
      lib/NetPacks.h
  35. 128 0
      lib/mapObjects/CGArmedInstance.cpp
  36. 40 0
      lib/mapObjects/CGArmedInstance.h
  37. 443 0
      lib/mapObjects/CGBank.cpp
  38. 59 0
      lib/mapObjects/CGBank.h
  39. 1368 0
      lib/mapObjects/CGHeroInstance.cpp
  40. 227 0
      lib/mapObjects/CGHeroInstance.h
  41. 334 0
      lib/mapObjects/CGMarket.cpp
  42. 88 0
      lib/mapObjects/CGMarket.h
  43. 364 0
      lib/mapObjects/CGPandoraBox.cpp
  44. 73 0
      lib/mapObjects/CGPandoraBox.h
  45. 1201 0
      lib/mapObjects/CGTownInstance.cpp
  46. 248 0
      lib/mapObjects/CGTownInstance.h
  47. 1 1
      lib/mapObjects/CObjectClassesHandler.cpp
  48. 4 3
      lib/mapObjects/CObjectClassesHandler.h
  49. 60 6105
      lib/mapObjects/CObjectHandler.cpp
  50. 6 1220
      lib/mapObjects/CObjectHandler.h
  51. 861 0
      lib/mapObjects/CQuest.cpp
  52. 198 0
      lib/mapObjects/CQuest.h
  53. 3 2
      lib/mapObjects/CRewardableConstructor.h
  54. 2 0
      lib/mapObjects/CRewardableObject.cpp
  55. 3 1
      lib/mapObjects/CRewardableObject.h
  56. 25 0
      lib/mapObjects/MapObjects.h
  57. 1574 0
      lib/mapObjects/MiscObjects.cpp
  58. 377 0
      lib/mapObjects/MiscObjects.h
  59. 1 1
      lib/mapping/CCampaignHandler.cpp
  60. 1 0
      lib/mapping/CMap.cpp
  61. 3 1
      lib/mapping/CMap.h
  62. 1 0
      lib/mapping/CMapEditManager.cpp
  63. 1 2
      lib/mapping/MapFormatH3M.cpp
  64. 2 1
      lib/mapping/MapFormatH3M.h
  65. 1 2
      lib/registerTypes/RegisterTypes.h
  66. 2 2
      lib/rmg/CMapGenerator.cpp
  67. 0 1
      lib/rmg/CMapGenerator.h
  68. 2 0
      lib/rmg/CRmgTemplateZone.cpp
  69. 2 2
      server/CGameHandler.cpp
  70. 2 2
      server/CVCMIServer.cpp
  71. 1 1
      server/NetPacksServer.cpp

+ 1 - 1
AI/VCAI/AIUtility.cpp

@@ -3,7 +3,7 @@
 #include "VCAI.h"
 
 #include "../../lib/UnlockGuard.h"
-#include "../../lib/mapObjects/CObjectHandler.h"
+//#include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CHeroHandler.h"
 

+ 2 - 2
AI/VCAI/AIUtility.h

@@ -5,7 +5,7 @@
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/CSpellHandler.h"
-#include "../../lib/mapObjects/CObjectHandler.h"
+//#include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/Connection.h"
 #include "../../lib/CGameState.h"
 #include "../../lib/mapping/CMap.h"
@@ -200,4 +200,4 @@ bool compareMovement(HeroPtr lhs, HeroPtr rhs);
 bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
 bool compareArmyStrength(const CArmedInstance *a1, const CArmedInstance *a2);
 ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance *t);
-int3 whereToExplore(HeroPtr h);
+int3 whereToExplore(HeroPtr h);

+ 1 - 1
AI/VCAI/Fuzzy.cpp

@@ -2,7 +2,7 @@
 #include "Fuzzy.h"
 #include <limits>
 
-#include "../../lib/mapObjects/CObjectHandler.h"
+#include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/VCMI_Lib.h"
 #include "../../CCallback.h"

+ 1 - 1
AI/VCAI/Goals.h

@@ -1,7 +1,7 @@
 #pragma once
 
 #include "../../lib/VCMI_Lib.h"
-#include "../../lib/mapObjects/CObjectHandler.h"
+//#include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/CBuildingHandler.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CTownHandler.h"

+ 1 - 2
AI/VCAI/VCAI.cpp

@@ -2,8 +2,7 @@
 #include "VCAI.h"
 #include "Goals.h"
 #include "../../lib/UnlockGuard.h"
-#include "../../lib/mapObjects/CObjectHandler.h"
-#include "../../lib/mapObjects/CRewardableObject.h"
+#include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CHeroHandler.h"
 

+ 2 - 2
AI/VCAI/VCAI.h

@@ -4,7 +4,7 @@
 #include "Goals.h"
 #include "../../lib/AI_Base.h"
 #include "../../CCallback.h"
-#include "../../lib/mapObjects/CObjectClassesHandler.h"
+//#include "../../lib/mapObjects/CObjectClassesHandler.h"
 
 #include "../../lib/CThreadHelper.h"
 
@@ -14,7 +14,7 @@
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/CSpellHandler.h"
-#include "../../lib/mapObjects/CObjectHandler.h"
+//#include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/Connection.h"
 #include "../../lib/CGameState.h"
 #include "../../lib/mapping/CMap.h"

+ 1 - 1
CCallback.cpp

@@ -12,7 +12,7 @@
 #include "lib/mapObjects/CObjectClassesHandler.h"
 #include "lib/CGeneralTextHandler.h"
 #include "lib/CHeroHandler.h"
-#include "lib/mapObjects/CObjectHandler.h"
+//#include "lib/mapObjects/CObjectHandler.h"
 #include "lib/Connection.h"
 #include "lib/NetPacks.h"
 #include "client/mapHandler.h"

+ 1 - 1
client/AdventureMapClasses.cpp

@@ -6,7 +6,7 @@
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/CModHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/CGameState.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CTownHandler.h"

+ 1 - 1
client/CAdvmapInterface.cpp

@@ -17,7 +17,7 @@
 #include "CDefHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/JsonNode.h"

+ 1 - 1
client/CCastleInterface.cpp

@@ -7,7 +7,7 @@
 #include "../lib/CCreatureHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CModHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/CSpellHandler.h"
 #include "../lib/CTownHandler.h"
 #include "CAdvmapInterface.h"

+ 1 - 1
client/CHeroWindow.cpp

@@ -20,7 +20,7 @@
 #include "CDefHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/NetPacksBase.h"
 
 #include "gui/CGuiHandler.h"

+ 2 - 2
client/CKingdomInterface.cpp

@@ -5,7 +5,7 @@
 #include "../lib/CCreatureHandler.h" //creatures name for objects list
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CModHandler.h" //for buildings per turn
-#include "../lib/mapObjects/CObjectHandler.h" //Hero/Town objects
+#include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/CHeroHandler.h" // only for calculating required xp? worth it?
 #include "../lib/CTownHandler.h"
 #include "CAnimation.h" //CAnimImage
@@ -953,4 +953,4 @@ void CHeroItem::onArtChange(int tabIndex)
 	//redraw item after background change
 	if (active)
 		redraw();
-}
+}

+ 2 - 2
client/CMT.cpp

@@ -34,7 +34,7 @@
 #include "CMessage.h"
 #include "../lib/CModHandler.h"
 #include "../lib/CTownHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/CScriptingModule.h"
 #include "../lib/GameConstants.h"
@@ -44,7 +44,7 @@
 #ifdef _WIN32
 #include "SDL_syswm.h"
 #endif
-#include "../lib/mapObjects/CObjectClassesHandler.h"
+//#include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "../lib/UnlockGuard.h"
 #include "CMT.h"
 

+ 2 - 2
client/CPlayerInterface.cpp

@@ -1,5 +1,5 @@
 #include "StdInc.h"
-#include "../lib/mapObjects/CObjectClassesHandler.h"
+//#include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "CAdvmapInterface.h"
 #include "battle/CBattleInterface.h"
 #include "battle/CBattleInterfaceClasses.h"
@@ -21,7 +21,7 @@
 #include "../lib/CArtHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/Connection.h"
 #include "../lib/CSpellHandler.h"
 #include "../lib/CTownHandler.h"

+ 2 - 2
client/CPreGame.cpp

@@ -11,11 +11,11 @@
 #include "gui/CCursorHandler.h"
 #include "CAnimation.h"
 #include "CDefHandler.h"
-#include "../lib/mapObjects/CObjectClassesHandler.h"
+//#include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/mapping/CCampaignHandler.h"
 #include "../lib/CCreatureHandler.h"
 #include "../lib/JsonNode.h"

+ 1 - 1
client/CQuestLog.cpp

@@ -16,7 +16,7 @@
 #include "../lib/CGameState.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/NetPacksBase.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 
 #include "gui/CGuiHandler.h"
 #include "gui/CIntObjectClasses.h"

+ 1 - 1
client/CSpellWindow.cpp

@@ -3,7 +3,7 @@
 
 #include "Graphics.h"
 #include "CDefHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/CSpellHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "CVideoHandler.h"

+ 2 - 2
client/Client.cpp

@@ -11,11 +11,11 @@
 #include "../lib/BattleState.h"
 #include "../lib/CModHandler.h"
 #include "../lib/CArtHandler.h"
-#include "../lib/mapObjects/CObjectClassesHandler.h"
+//#include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CTownHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/CBuildingHandler.h"
 #include "../lib/CSpellHandler.h"
 #include "../lib/Connection.h"

+ 1 - 1
client/GUIClasses.cpp

@@ -24,7 +24,7 @@
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CModHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/CSpellHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CondSh.h"

+ 3 - 3
client/Graphics.cpp

@@ -12,13 +12,13 @@
 #include "../CCallback.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CTownHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CCreatureHandler.h"
 #include "CBitmapHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/CSpellHandler.h"
-#include "../lib/mapObjects/CObjectClassesHandler.h"
+//#include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "../lib/CGameState.h"
 #include "../lib/JsonNode.h"
 #include "../lib/vcmi_endian.h"

+ 2 - 2
client/NetPacksClient.cpp

@@ -9,9 +9,9 @@
 #include "CGameInfo.h"
 #include "../lib/Connection.h"
 #include "../lib/CGeneralTextHandler.h"
-#include "../lib/mapObjects/CObjectClassesHandler.h"
+//#include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/VCMI_Lib.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/VCMIDirs.h"

+ 1 - 1
client/battle/CBattleInterface.cpp

@@ -6,7 +6,7 @@
 #include "../CAdvmapInterface.h"
 #include "../CAnimation.h"
 #include "../CBitmapHandler.h"
-#include "../../lib/mapObjects/CObjectHandler.h"
+//#include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/CHeroHandler.h"
 # include "../CDefHandler.h"
 #include "../../lib/CSpellHandler.h"

+ 1 - 1
client/battle/CBattleInterfaceClasses.cpp

@@ -16,7 +16,7 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/CCreatureHandler.h"
-#include "../../lib/mapObjects/CObjectHandler.h"
+//#include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/BattleState.h"
 #include "../CMusicHandler.h"
 #include "../CVideoHandler.h"

+ 1 - 1
client/mapHandler.cpp

@@ -14,12 +14,12 @@
 #include "CBitmapHandler.h"
 #include "gui/SDL_Extensions.h"
 #include "CGameInfo.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "../lib/CGameState.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CTownHandler.h"
 #include "Graphics.h"
-#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/mapping/CMap.h"
 #include "CDefHandler.h"
 #include "../lib/CConfigHandler.h"

+ 3 - 1
lib/BattleState.h

@@ -4,12 +4,14 @@
 #include "BattleHex.h"
 #include "HeroBonus.h"
 #include "CCreatureSet.h"
-#include "mapObjects/CObjectHandler.h"
+#include "mapObjects/CGTownInstance.h"
+#include "mapObjects/CGHeroInstance.h"
 #include "CCreatureHandler.h"
 #include "CObstacleInstance.h"
 #include "ConstTransitivePtr.h"
 #include "GameConstants.h"
 #include "CBattleCallback.h"
+#include "int3.h"
 
 /*
  * BattleState.h, part of VCMI engine

+ 3 - 1
lib/CArtHandler.cpp

@@ -16,11 +16,13 @@
 #include "VCMI_Lib.h"
 #include "CModHandler.h"
 #include "CSpellHandler.h"
-#include "mapObjects/CObjectHandler.h"
+#include "mapObjects/MapObjects.h"
 #include "NetPacksBase.h"
 #include "GameConstants.h"
 #include "CRandomGenerator.h"
 
+#include "mapObjects/CObjectClassesHandler.h"
+
 using namespace boost::assign;
 
 // Note: list must match entries in ArtTraits.txt

+ 2 - 0
lib/CCreatureHandler.cpp

@@ -9,6 +9,8 @@
 #include "CModHandler.h"
 #include "StringConstants.h"
 
+#include "mapObjects/CObjectClassesHandler.h"
+
 using namespace boost::assign;
 
 /*

+ 1 - 1
lib/CCreatureSet.cpp

@@ -4,7 +4,7 @@
 #include "CCreatureHandler.h"
 #include "VCMI_Lib.h"
 #include "CModHandler.h"
-#include "mapObjects/CObjectHandler.h"
+#include "mapObjects/CGHeroInstance.h"
 #include "IGameCallback.h"
 #include "CGameState.h"
 #include "CGeneralTextHandler.h"

+ 1 - 1
lib/CGameState.h

@@ -14,7 +14,7 @@
 #include "IGameCallback.h"
 #include "ResourceSet.h"
 #include "int3.h"
-#include "mapObjects/CObjectHandler.h"
+//#include "mapObjects/CObjectHandler.h"
 #include "CRandomGenerator.h"
 
 /*

+ 2 - 0
lib/CHeroHandler.cpp

@@ -13,6 +13,8 @@
 #include "mapObjects/CObjectHandler.h" //for hero specialty
 #include <math.h>
 
+#include "mapObjects/CObjectClassesHandler.h"
+
 /*
  * CHeroHandler.cpp, part of VCMI engine
  *

+ 24 - 13
lib/CMakeLists.txt

@@ -6,19 +6,6 @@ include_directories(${Boost_INCLUDE_DIRS} ${SDL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}
 
 set(lib_SRCS
 		StdInc.cpp
-		IGameCallback.cpp
-		CGameInfoCallback.cpp
-		CGameState.cpp
-		Connection.cpp
-		NetPacksLib.cpp
-
-		registerTypes/RegisterTypes.cpp
-		registerTypes/TypesClientPacks1.cpp
-		registerTypes/TypesClientPacks2.cpp
-		registerTypes/TypesMapObjects1.cpp
-		registerTypes/TypesMapObjects2.cpp
-		registerTypes/TypesPregamePacks.cpp
-		registerTypes/TypesServerPacks.cpp
 
 		filesystem/AdapterLoaders.cpp
 		filesystem/CCompressedStream.cpp
@@ -32,10 +19,18 @@ set(lib_SRCS
 		filesystem/Filesystem.cpp
 		filesystem/ResourceID.cpp
 
+		mapObjects/CGArmedInstance.cpp
+		mapObjects/CGBank.cpp
+		mapObjects/CGHeroInstance.cpp
+		mapObjects/CGMarket.cpp
+		mapObjects/CGPandoraBox.cpp
+		mapObjects/CGTownInstance.cpp
 		mapObjects/CObjectClassesHandler.cpp
 		mapObjects/CObjectHandler.cpp
+		mapObjects/CQuest.cpp
 		mapObjects/CRewardableConstructor.cpp
 		mapObjects/CRewardableObject.cpp
+		mapObjects/MiscObjects.cpp
 		mapObjects/ObjectTemplate.cpp
 
 		logging/CBasicLogConfigurator.cpp
@@ -85,6 +80,20 @@ set(lib_SRCS
 		ResourceSet.cpp
 		VCMI_Lib.cpp
 		VCMIDirs.cpp
+
+		IGameCallback.cpp
+		CGameInfoCallback.cpp
+		CGameState.cpp
+		Connection.cpp
+		NetPacksLib.cpp
+
+		registerTypes/RegisterTypes.cpp
+		registerTypes/TypesClientPacks1.cpp
+		registerTypes/TypesClientPacks2.cpp
+		registerTypes/TypesMapObjects1.cpp
+		registerTypes/TypesMapObjects2.cpp
+		registerTypes/TypesPregamePacks.cpp
+		registerTypes/TypesServerPacks.cpp
 )
 
 set(lib_HEADERS
@@ -93,6 +102,8 @@ set(lib_HEADERS
 		filesystem/CInputStream.h
 		filesystem/ISimpleResourceLoader.h
 
+		mapObjects/MapObjects.h
+
 		AI_Base.h
 		CondSh.h
 		ConstTransitivePtr.h

+ 1 - 2
lib/Connection.h

@@ -14,7 +14,6 @@
 #include <typeinfo> //XXX this is in namespace std if you want w/o use typeinfo.h?
 #include <type_traits>
 
-#include <boost/variant.hpp>
 #include <boost/mpl/eval_if.hpp>
 #include <boost/mpl/equal_to.hpp>
 #include <boost/mpl/int.hpp>
@@ -24,7 +23,7 @@
 
 #include "ConstTransitivePtr.h"
 #include "CCreatureSet.h" //for CStackInstance
-#include "mapObjects/CObjectHandler.h" //for CArmedInstance
+#include "mapObjects/CGHeroInstance.h"
 #include "mapping/CCampaignHandler.h" //for CCampaignState
 #include "rmg/CMapGenerator.h" // for CMapGenOptions
 

+ 1 - 0
lib/IGameCallback.cpp

@@ -17,6 +17,7 @@
 #include "CBonusTypeHandler.h"
 
 #include "Connection.h" // for SAVEGAME_MAGIC
+#include "mapObjects/CObjectClassesHandler.h"
 
 void CPrivilagedInfoCallback::getFreeTiles (std::vector<int3> &tiles) const
 {

+ 1 - 2
lib/NetPacks.h

@@ -1,11 +1,10 @@
 #pragma once
 
-#include <boost/variant.hpp>
-
 #include "NetPacksBase.h"
 
 #include "BattleAction.h"
 #include "HeroBonus.h"
+#include "mapObjects/CGHeroInstance.h"
 #include "CCreatureSet.h"
 #include "mapping/CMapInfo.h"
 #include "StartInfo.h"

+ 128 - 0
lib/mapObjects/CGArmedInstance.cpp

@@ -0,0 +1,128 @@
+/*
+ * CObjectHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CGArmedInstance.h"
+
+#include "../CTownHandler.h"
+#include "../CCreatureHandler.h"
+#include "../CGeneralTextHandler.h"
+#include "../CGameState.h"
+
+using namespace boost::assign;
+
+void CArmedInstance::randomizeArmy(int type)
+{
+	for (auto & elem : stacks)
+	{
+		int & randID = elem.second->idRand;
+		if(randID >= 0)
+		{
+			int level = randID / 2;
+			bool upgrade = randID % 2;
+			elem.second->setType(VLC->townh->factions[type]->town->creatures[level][upgrade]);
+
+			randID = -1;
+		}
+		assert(elem.second->valid(false));
+		assert(elem.second->armyObj == this);
+	}
+	return;
+}
+
+CArmedInstance::CArmedInstance()
+{
+	battle = nullptr;
+}
+
+void CArmedInstance::updateMoraleBonusFromArmy()
+{
+	if(!validTypes(false)) //object not randomized, don't bother
+		return;
+
+	Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE)));
+	if(!b)
+	{
+		b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1);
+		addNewBonus(b);
+	}
+
+	//number of alignments and presence of undead
+	std::set<TFaction> factions;
+	bool hasUndead = false;
+
+	for(auto slot : Slots())
+	{
+		const CStackInstance * inst = slot.second;
+		const CCreature * creature  = VLC->creh->creatures[inst->getCreatureID()];
+
+		factions.insert(creature->faction);
+		// Check for undead flag instead of faction (undead mummies are neutral)
+		hasUndead |= inst->hasBonusOfType(Bonus::UNDEAD);
+	}
+
+	size_t factionsInArmy = factions.size(); //town garrison seems to take both sets into account
+
+	// Take Angelic Alliance troop-mixing freedom of non-evil units into account.
+	if (hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX))
+	{
+		size_t mixableFactions = 0;
+
+		for(TFaction f : factions)
+		{
+			if (VLC->townh->factions[f]->alignment != EAlignment::EVIL)
+				mixableFactions++;
+		}
+		if (mixableFactions > 0)
+			factionsInArmy -= mixableFactions - 1;
+	}
+
+	if(factionsInArmy == 1)
+	{
+		b->val = +1;
+		b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
+	}
+	else if (!factions.empty()) // no bonus from empty garrison
+	{
+	 	b->val = 2 - factionsInArmy;
+		b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d
+	}
+	boost::algorithm::trim(b->description);
+
+	//-1 modifier for any Undead unit in army
+	const ui8 UNDEAD_MODIFIER_ID = -2;
+	Bonus *undeadModifier = getBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID));
+ 	if(hasUndead)
+	{
+		if(!undeadModifier)
+			addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]));
+	}
+	else if(undeadModifier)
+		removeBonus(undeadModifier);
+
+}
+
+void CArmedInstance::armyChanged()
+{
+	updateMoraleBonusFromArmy();
+}
+
+CBonusSystemNode * CArmedInstance::whereShouldBeAttached(CGameState *gs)
+{
+	if(tempOwner < PlayerColor::PLAYER_LIMIT)
+		return gs->getPlayer(tempOwner);
+	else
+		return &gs->globalEffects;
+}
+
+CBonusSystemNode * CArmedInstance::whatShouldBeAttached()
+{
+	return this;
+}

+ 40 - 0
lib/mapObjects/CGArmedInstance.h

@@ -0,0 +1,40 @@
+#pragma once
+
+#include "CObjectHandler.h"
+#include "../CCreatureSet.h"
+
+/*
+ * CObjectHandler.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
+ *
+ */
+
+class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet
+{
+public:
+	BattleInfo *battle; //set to the current battle, if engaged
+
+	void randomizeArmy(int type);
+	virtual void updateMoraleBonusFromArmy();
+
+	void armyChanged() override;
+
+	//////////////////////////////////////////////////////////////////////////
+//	int valOfGlobalBonuses(CSelector selector) const; //used only for castle interface								???
+	virtual CBonusSystemNode *whereShouldBeAttached(CGameState *gs);
+	virtual CBonusSystemNode *whatShouldBeAttached();
+	//////////////////////////////////////////////////////////////////////////
+
+	CArmedInstance();
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+		h & static_cast<CBonusSystemNode&>(*this);
+		h & static_cast<CCreatureSet&>(*this);
+	}
+};

+ 443 - 0
lib/mapObjects/CGBank.cpp

@@ -0,0 +1,443 @@
+/*
+ * CObjectHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CGBank.h"
+
+#include "NetPacks.h"
+#include "CGeneralTextHandler.h"
+#include "../client/CSoundBase.h"
+
+using namespace boost::assign;
+
+///helpers
+static std::string & visitedTxt(const bool visited)
+{
+	int id = visited ? 352 : 353;
+	return VLC->generaltexth->allTexts[id];
+}
+
+void CBank::initObj()
+{
+	index = VLC->objh->bankObjToIndex(this);
+	bc = nullptr;
+	daycounter = 0;
+	multiplier = 1;
+}
+const std::string & CBank::getHoverText() const
+{
+	bool visited = (bc == nullptr);
+	hoverName = VLC->objh->creBanksNames[index] + " " + visitedTxt(visited);
+	return hoverName;
+}
+void CBank::reset(ui16 var1) //prevents desync
+{
+	ui8 chance = 0;
+	for (auto & elem : VLC->objh->banksInfo[index])
+	{
+		if (var1 < (chance += elem->chance))
+		{
+ 			bc = elem;
+			break;
+		}
+	}
+	artifacts.clear();
+}
+
+void CBank::initialize() const
+{
+	cb->setObjProperty(id, ObjProperty::BANK_RESET, cb->gameState()->getRandomGenerator().nextInt()); //synchronous reset
+
+	for (ui8 i = 0; i <= 3; i++)
+	{
+		for (ui8 n = 0; n < bc->artifacts[i]; n++)
+		{
+			CArtifact::EartClass artClass;
+			switch(i)
+			{
+			case 0: artClass = CArtifact::ART_TREASURE; break;
+			case 1: artClass = CArtifact::ART_MINOR; break;
+			case 2: artClass = CArtifact::ART_MAJOR; break;
+			case 3: artClass = CArtifact::ART_RELIC; break;
+			default: assert(0); continue;
+			}
+
+			int artID = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), artClass);
+			cb->setObjProperty(id, ObjProperty::BANK_ADD_ARTIFACT, artID);
+		}
+	}
+
+	cb->setObjProperty(id, ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //get army
+}
+void CBank::setPropertyDer (ui8 what, ui32 val)
+/// random values are passed as arguments and processed identically on all clients
+{
+	switch (what)
+	{
+		case ObjProperty::BANK_DAYCOUNTER: //daycounter
+			if (val == 0)
+				daycounter = 1; //yes, 1
+			else
+				daycounter++;
+			break;
+		case ObjProperty::BANK_MULTIPLIER: //multiplier, in percent
+			multiplier = val / 100.0;
+			break;
+		case 13: //bank preset
+			bc = VLC->objh->banksInfo[index][val];
+			break;
+		case ObjProperty::BANK_RESET:
+			reset (val%100);
+			break;
+		case ObjProperty::BANK_CLEAR_CONFIG:
+			bc = nullptr;
+			break;
+		case ObjProperty::BANK_CLEAR_ARTIFACTS: //remove rewards from Derelict Ship
+			artifacts.clear();
+			break;
+		case ObjProperty::BANK_INIT_ARMY: //set ArmedInstance army
+			{
+				int upgraded = 0;
+				if (val%100 < bc->upgradeChance) //once again anti-desync
+					upgraded = 1;
+				switch (bc->guards.size())
+				{
+					case 1:
+						for	(int i = 0; i < 4; ++i)
+							setCreature (SlotID(i), bc->guards[0].first, bc->guards[0].second  / 5 );
+						setCreature (SlotID(4), CreatureID(bc->guards[0].first + upgraded), bc->guards[0].second  / 5 );
+						break;
+					case 4:
+					{
+						if (bc->guards.back().second) //all stacks are present
+						{
+							for (auto & elem : bc->guards)
+							{
+								setCreature (SlotID(stacksCount()), elem.first, elem.second);
+							}
+						}
+						else if (bc->guards[2].second)//Wraiths are present, split two stacks in Crypt
+						{
+							setCreature (SlotID(0), bc->guards[0].first, bc->guards[0].second  / 2 );
+							setCreature (SlotID(1), bc->guards[1].first, bc->guards[1].second / 2);
+							setCreature (SlotID(2), CreatureID(bc->guards[2].first + upgraded), bc->guards[2].second);
+							setCreature (SlotID(3), bc->guards[1].first, bc->guards[1].second / 2 );
+							setCreature (SlotID(4), bc->guards[0].first, bc->guards[0].second - (bc->guards[0].second  / 2) );
+
+						}
+						else //split both stacks
+						{
+							for	(int i = 0; i < 3; ++i) //skellies
+								setCreature (SlotID(2*i), bc->guards[0].first, bc->guards[0].second  / 3);
+							for	(int i = 0; i < 2; ++i) //zombies
+								setCreature (SlotID(2*i+1), bc->guards[1].first, bc->guards[1].second  / 2);
+						}
+					}
+						break;
+					default:
+                        logGlobal->warnStream() << "Error: Unexpected army data: " << bc->guards.size() <<" items found";
+						return;
+				}
+			}
+			break;
+		case ObjProperty::BANK_ADD_ARTIFACT: //add Artifact
+		{
+			artifacts.push_back (val);
+			break;
+		}
+	}
+}
+
+void CBank::newTurn() const
+{
+	if (bc == nullptr)
+	{
+		if (cb->getDate() == 1)
+			initialize(); //initialize on first day
+		else if (daycounter >= 28 && (subID < 13 || subID > 16)) //no reset for Emissaries
+		{
+			initialize();
+			cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 0); //daycounter 0
+			if (ID == Obj::DERELICT_SHIP && cb->getDate() > 1)
+			{
+				cb->setObjProperty (id, ObjProperty::BANK_MULTIPLIER, 0);//ugly hack to make derelict ships usable only once
+				cb->setObjProperty (id, ObjProperty::BANK_CLEAR_ARTIFACTS, 0);
+			}
+		}
+		else
+			cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++
+	}
+}
+bool CBank::wasVisited (PlayerColor player) const
+{
+	return !bc;
+}
+
+void CBank::onHeroVisit (const CGHeroInstance * h) const
+{
+	if (bc)
+	{
+		int banktext = 0;
+		switch (ID)
+		{
+		case Obj::CREATURE_BANK:
+			banktext = 32;
+			break;
+		case Obj::DERELICT_SHIP:
+			banktext = 41;
+			break;
+		case Obj::DRAGON_UTOPIA:
+			banktext = 47;
+			break;
+		case Obj::CRYPT:
+			banktext = 119;
+			break;
+		case Obj::SHIPWRECK:
+			banktext = 122;
+			break;
+		}
+		BlockingDialog bd (true, false);
+		bd.player = h->getOwner();
+		bd.soundID = soundBase::ROGUE;
+		bd.text.addTxt(MetaString::ADVOB_TXT,banktext);
+		if (ID == Obj::CREATURE_BANK)
+			bd.text.addReplacement(VLC->objh->creBanksNames[index]);
+		cb->showBlockingDialog (&bd);
+	}
+	else
+	{
+		InfoWindow iw;
+		iw.soundID = soundBase::GRAVEYARD;
+		iw.player = h->getOwner();
+		if (ID == Obj::CRYPT) //morale penalty for empty Crypt
+		{
+			GiveBonus gbonus;
+			gbonus.id = h->id.getNum();
+			gbonus.bonus.duration = Bonus::ONE_BATTLE;
+			gbonus.bonus.source = Bonus::OBJECT;
+			gbonus.bonus.sid = ID;
+			gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[98];
+			gbonus.bonus.type = Bonus::MORALE;
+			gbonus.bonus.val = -1;
+			cb->giveHeroBonus(&gbonus);
+			iw.text << VLC->generaltexth->advobtxt[120];
+			iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
+		}
+		else
+		{
+			iw.text << VLC->generaltexth->advobtxt[33];
+			iw.text.addReplacement(VLC->objh->creBanksNames[index]);
+		}
+		cb->showInfoDialog(&iw);
+	}
+}
+
+void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+	if (result.winner == 0)
+	{
+		int textID = -1;
+		InfoWindow iw;
+		iw.player = hero->getOwner();
+		MetaString loot;
+
+		switch (ID)
+		{
+		case Obj::CREATURE_BANK: case Obj::DRAGON_UTOPIA:
+			textID = 34;
+			break;
+		case Obj::DERELICT_SHIP:
+			if (multiplier)
+				textID = 43;
+			else
+			{
+				GiveBonus gbonus;
+				gbonus.id = hero->id.getNum();
+				gbonus.bonus.duration = Bonus::ONE_BATTLE;
+				gbonus.bonus.source = Bonus::OBJECT;
+				gbonus.bonus.sid = ID;
+				gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101];
+				gbonus.bonus.type = Bonus::MORALE;
+				gbonus.bonus.val = -1;
+				cb->giveHeroBonus(&gbonus);
+				textID = 42;
+				iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
+			}
+			break;
+		case Obj::CRYPT:
+			if (bc->resources.size() != 0)
+				textID = 121;
+			else
+			{
+				iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
+				GiveBonus gbonus;
+				gbonus.id = hero->id.getNum();
+				gbonus.bonus.duration = Bonus::ONE_BATTLE;
+				gbonus.bonus.source = Bonus::OBJECT;
+				gbonus.bonus.sid = ID;
+				gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID];
+				gbonus.bonus.type = Bonus::MORALE;
+				gbonus.bonus.val = -1;
+				cb->giveHeroBonus(&gbonus);
+				textID = 120;
+				iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
+			}
+			break;
+		case Obj::SHIPWRECK:
+			if (bc->resources.size())
+				textID = 124;
+			else
+				textID = 123;
+			break;
+		}
+
+		//grant resources
+		if (textID != 42) //empty derelict ship gives no cash
+		{
+			for (int it = 0; it < bc->resources.size(); it++)
+			{
+				if (bc->resources[it] != 0)
+				{
+					iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0));
+					loot << "%d %s";
+					loot.addReplacement(iw.components.back().val);
+					loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype);
+					cb->giveResource (hero->getOwner(), static_cast<Res::ERes>(it), bc->resources[it]);
+				}
+			}
+		}
+		//grant artifacts
+		for (auto & elem : artifacts)
+		{
+			iw.components.push_back (Component (Component::ARTIFACT, elem, 0, 0));
+			loot << "%s";
+			loot.addReplacement(MetaString::ART_NAMES, elem);
+			cb->giveHeroNewArtifact (hero, VLC->arth->artifacts[elem], ArtifactPosition::FIRST_AVAILABLE);
+		}
+		//display loot
+		if (!iw.components.empty())
+		{
+			iw.text.addTxt (MetaString::ADVOB_TXT, textID);
+			if (textID == 34)
+			{
+				iw.text.addReplacement(MetaString::CRE_PL_NAMES, result.casualties[1].begin()->first);
+				iw.text.addReplacement(loot.buildList());
+			}
+			cb->showInfoDialog(&iw);
+		}
+		loot.clear();
+		iw.components.clear();
+		iw.text.clear();
+
+		//grant creatures
+		CCreatureSet ourArmy;
+		for (auto it = bc->creatures.cbegin(); it != bc->creatures.cend(); it++)
+		{
+			SlotID slot = ourArmy.getSlotFor(it->first);
+			ourArmy.addToSlot(slot, it->first, it->second);
+		}
+		for (auto & elem : ourArmy.Slots())
+		{
+			iw.components.push_back(Component(*elem.second));
+			loot << "%s";
+			loot.addReplacement(*elem.second);
+		}
+
+		if (ourArmy.Slots().size())
+		{
+			if (ourArmy.Slots().size() == 1 && ourArmy.Slots().begin()->second->count == 1)
+				iw.text.addTxt (MetaString::ADVOB_TXT, 185);
+			else
+				iw.text.addTxt (MetaString::ADVOB_TXT, 186);
+
+			iw.text.addReplacement(loot.buildList());
+			iw.text.addReplacement(hero->name);
+			cb->showInfoDialog(&iw);
+			cb->giveCreatures(this, hero, ourArmy, false);
+		}
+		cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); //bc = nullptr
+	}
+}
+
+void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	if (answer)
+	{
+		cb->startBattleI(hero, this, true);
+	}
+}
+
+void CGPyramid::initObj()
+{
+	std::vector<SpellID> available;
+	cb->getAllowedSpells (available, 5);
+	if (available.size())
+	{
+		bc = VLC->objh->banksInfo[21].front(); //TODO: remove hardcoded value?
+		spell = *RandomGeneratorUtil::nextItem(available, cb->gameState()->getRandomGenerator());
+	}
+	else
+	{
+        logGlobal->errorStream() <<"No spells available for Pyramid! Object set to empty.";
+	}
+	setPropertyDer(ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //set guards at game start
+}
+const std::string & CGPyramid::getHoverText() const
+{
+	hoverName = VLC->objh->creBanksNames[21]+ " " + visitedTxt((bc==nullptr));
+	return hoverName;
+}
+void CGPyramid::onHeroVisit (const CGHeroInstance * h) const
+{
+	if (bc)
+	{
+		BlockingDialog bd (true, false);
+		bd.player = h->getOwner();
+		bd.soundID = soundBase::MYSTERY;
+		bd.text << VLC->generaltexth->advobtxt[105];
+		cb->showBlockingDialog(&bd);
+	}
+	else
+	{
+		InfoWindow iw;
+		iw.player = h->getOwner();
+		iw.text << VLC->generaltexth->advobtxt[107];
+		iw.components.push_back (Component (Component::LUCK, 0 , -2, 0));
+		GiveBonus gb;
+		gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]);
+		gb.id = h->id.getNum();
+		cb->giveHeroBonus(&gb);
+		cb->showInfoDialog(&iw);
+	}
+}
+
+void CGPyramid::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+	if (result.winner == 0)
+	{
+		InfoWindow iw;
+		iw.player = hero->getOwner();
+		iw.text.addTxt (MetaString::ADVOB_TXT, 106);
+		iw.text.addTxt (MetaString::SPELL_NAME, spell);
+		if (!hero->getArt(ArtifactPosition::SPELLBOOK))
+			iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook
+		else if (hero->getSecSkillLevel(SecondarySkill::WISDOM) < 3)
+			iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom
+		else
+		{
+			std::set<SpellID> spells;
+			spells.insert (SpellID(spell));
+			cb->changeSpells (hero, true, spells);
+			iw.components.push_back(Component (Component::SPELL, spell, 0, 0));
+		}
+		cb->showInfoDialog(&iw);
+		cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0);
+	}
+}

+ 59 - 0
lib/mapObjects/CGBank.h

@@ -0,0 +1,59 @@
+#pragma once
+
+#include "CObjectHandler.h"
+#include "CGArmedInstance.h"
+
+/*
+ * CObjectHandler.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
+ *
+ */
+
+class DLL_LINKAGE CBank : public CArmedInstance
+{
+	public:
+	int index; //banks have unusal numbering - see ZCRBANK.txt and initObj()
+	BankConfig *bc;
+	double multiplier; //for improved banks script
+	std::vector<ui32> artifacts; //fixed and deterministic
+	ui32 daycounter;
+
+	void initObj() override;
+	const std::string & getHoverText() const override;
+	void initialize() const;
+	void reset(ui16 var1);
+	void newTurn() const override;
+	bool wasVisited (PlayerColor player) const override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this);
+		h & index & multiplier & artifacts & daycounter & bc;
+	}
+protected:
+	void setPropertyDer(ui8 what, ui32 val) override;
+};
+class DLL_LINKAGE CGPyramid : public CBank
+{
+public:
+	ui16 spell;
+
+	void initObj() override;
+	const std::string & getHoverText() const override;
+	void newTurn() const override {}; //empty, no reset
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CBank&>(*this);
+		h & spell;
+	}
+};

+ 1368 - 0
lib/mapObjects/CGHeroInstance.cpp

@@ -0,0 +1,1368 @@
+/*
+ * CObjectHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CGHeroInstance.h"
+
+#include "NetPacks.h"
+#include "CGeneralTextHandler.h"
+#include "CHeroHandler.h"
+#include "../client/CSoundBase.h"
+#include "CSpellHandler.h"
+#include "CObjectClassesHandler.h"
+
+
+using namespace boost::assign;
+
+///helpers
+static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
+{
+	InfoWindow iw;
+	iw.soundID = soundID;
+	iw.player = playerID;
+	iw.text.addTxt(MetaString::ADVOB_TXT,txtID);
+	IObjectInterface::cb->sendAndApply(&iw);
+}
+
+static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID)
+{
+	const PlayerColor playerID = h->getOwner();
+	showInfoDialog(playerID,txtID,soundID);
+}
+
+static int lowestSpeed(const CGHeroInstance * chi)
+{
+	if(!chi->Slots().size())
+	{
+        logGlobal->errorStream() << "Error! Hero " << chi->id.getNum() << " ("<<chi->name<<") has no army!";
+		return 20;
+	}
+	auto i = chi->Slots().begin();
+	//TODO? should speed modifiers (eg from artifacts) affect hero movement?
+	int ret = (i++)->second->valOfBonuses(Bonus::STACKS_SPEED);
+	for (;i!=chi->Slots().end();i++)
+	{
+		ret = std::min(ret, i->second->valOfBonuses(Bonus::STACKS_SPEED));
+	}
+	return ret;
+}
+
+ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const
+{
+	//base move cost
+	unsigned ret = 100;
+
+	//if there is road both on dest and src tiles - use road movement cost
+    if(dest.roadType != ERoadType::NO_ROAD && from.roadType != ERoadType::NO_ROAD)
+	{
+        int road = std::min(dest.roadType,from.roadType); //used road ID
+		switch(road)
+		{
+        case ERoadType::DIRT_ROAD:
+			ret = 75;
+			break;
+        case ERoadType::GRAVEL_ROAD:
+			ret = 65;
+			break;
+        case ERoadType::COBBLESTONE_ROAD:
+			ret = 50;
+			break;
+		default:
+            logGlobal->errorStream() << "Unknown road type: " << road << "... Something wrong!";
+			break;
+		}
+	}
+	else
+	{
+		//FIXME: in H3 presence of Nomad in army will remove terrain penalty for sand. Bonus not implemented in VCMI
+
+		// NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army.
+		// This is clearly bug in H3 however intended behaviour is not clear.
+		// Current VCMI behaviour will ignore neutrals in calculations so army in VCMI
+		// will always have best penalty without any influence from player-defined stacks order
+
+		bool nativeArmy = true;
+		for(auto stack : stacks)
+		{
+			int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain;
+
+            if (nativeTerrain != -1 && nativeTerrain != from.terType)
+			{
+				nativeArmy = false;
+				break;
+			}
+		}
+		if (!nativeArmy)
+            ret = VLC->heroh->terrCosts[from.terType];
+ 	}
+	return ret;
+}
+
+int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
+{
+	if (toh3m)
+	{
+		src.x+=1;
+		return src;
+	}
+	else
+	{
+		src.x-=1;
+		return src;
+	}
+}
+int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation'
+{
+	if (h3m)
+	{
+		return pos;
+	}
+	else
+	{
+		return convertPosition(pos,false);
+	}
+}
+
+bool CGHeroInstance::canWalkOnSea() const
+{
+	return hasBonusOfType(Bonus::FLYING_MOVEMENT) || hasBonusOfType(Bonus::WATER_WALKING);
+}
+
+ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const
+{
+	for(auto & elem : secSkills)
+		if(elem.first == skill)
+			return elem.second;
+	return 0;
+}
+
+void CGHeroInstance::setSecSkillLevel(SecondarySkill which, int val, bool abs)
+{
+	if(getSecSkillLevel(which) == 0)
+	{
+		secSkills.push_back(std::pair<SecondarySkill,ui8>(which, val));
+		updateSkill(which, val);
+	}
+	else
+	{
+		for (auto & elem : secSkills)
+		{
+			if(elem.first == which)
+			{
+				if(abs)
+					elem.second = val;
+				else
+					elem.second += val;
+
+				if(elem.second > 3) //workaround to avoid crashes when same sec skill is given more than once
+				{
+                    logGlobal->warnStream() << "Warning: Skill " << which << " increased over limit! Decreasing to Expert.";
+					elem.second = 3;
+				}
+				updateSkill(which, elem.second); //when we know final value
+			}
+		}
+	}
+}
+
+bool CGHeroInstance::canLearnSkill() const
+{
+	return secSkills.size() < GameConstants::SKILL_PER_HERO;
+}
+
+int CGHeroInstance::maxMovePoints(bool onLand) const
+{
+	int base;
+
+	if(onLand)
+	{
+		// used function is f(x) = 66.6x + 1300, rounded to second digit, where x is lowest speed in army
+		static const int baseSpeed = 1300; // base speed from creature with 0 speed
+
+		int armySpeed = lowestSpeed(this) * 20 / 3;
+
+		base = armySpeed * 10 + baseSpeed; // separate *10 is intentional to receive same rounding as in h3
+		vstd::abetween(base, 1500, 2000); // base speed is limited by these values
+	}
+	else
+	{
+		base = 1500; //on water base movement is always 1500 (speed of army doesn't matter)
+	}
+
+	const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT;
+	const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt);
+
+	const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION;
+	const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0;
+
+	return int(base* (1+modifier)) + bonus;
+}
+
+CGHeroInstance::CGHeroInstance()
+ : IBoatGenerator(this)
+{
+	setNodeType(HERO);
+	ID = Obj::HERO;
+	tacticFormationEnabled = inTownGarrison = false;
+	mana = movement = portrait = level = -1;
+	isStanding = true;
+	moveDir = 4;
+	exp = 0xffffffff;
+	visitedTown = nullptr;
+	type = nullptr;
+	boat = nullptr;
+	commander = nullptr;
+	sex = 0xff;
+	secSkills.push_back(std::make_pair(SecondarySkill::DEFAULT, -1));
+}
+
+void CGHeroInstance::initHero(HeroTypeID SUBID)
+{
+	subID = SUBID.getNum();
+	initHero();
+}
+
+void CGHeroInstance::setType(si32 ID, si32 subID)
+{
+	assert(ID == Obj::HERO); // just in case
+	CGObjectInstance::setType(ID, subID);
+	type = VLC->heroh->heroes[subID];
+	portrait = type->imageIndex;
+	randomizeArmy(type->heroClass->faction);
+}
+
+void CGHeroInstance::initHero()
+{
+	assert(validTypes(true));
+	if(!type)
+		type = VLC->heroh->heroes[subID];
+
+	if (ID == Obj::HERO)
+		appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->id)->getTemplates().front();
+
+	if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell
+	{
+		for(auto spellID : type->spells)
+			spells.insert(spellID);
+	}
+	else //remove placeholder
+		spells -= SpellID::PRESET;
+
+	if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) //no catapult means we haven't read pre-existent set -> use default rules for spellbook
+		putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0));
+
+	if(!getArt(ArtifactPosition::MACH4))
+		putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createNewArtifactInstance(3)); //everyone has a catapult
+
+	if(portrait < 0 || portrait == 255)
+		portrait = type->imageIndex;
+	if(!hasBonus(Selector::sourceType(Bonus::HERO_BASE_SKILL)))
+	{
+		for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
+		{
+			pushPrimSkill(static_cast<PrimarySkill::PrimarySkill>(g), type->heroClass->primarySkillInitial[g]);
+		}
+	}
+	if(secSkills.size() == 1 && secSkills[0] == std::pair<SecondarySkill,ui8>(SecondarySkill::DEFAULT, -1)) //set secondary skills to default
+		secSkills = type->secSkillsInit;
+	if (!name.length())
+		name = type->name;
+
+	if (sex == 0xFF)//sex is default
+		sex = type->sex;
+
+	setFormation(false);
+	if (!stacksCount()) //standard army//initial army
+	{
+		initArmy();
+	}
+	assert(validTypes());
+
+	level = 1;
+	if(exp == 0xffffffff)
+	{
+		initExp();
+	}
+	else
+	{
+		levelUpAutomatically();
+	}
+
+	if (VLC->modh->modules.COMMANDERS && !commander)
+	{
+		commander = new CCommanderInstance(type->heroClass->commander->idNumber);
+		commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders
+		commander->giveStackExp (exp); //after our exp is set
+	}
+
+	if (mana < 0)
+		mana = manaLimit();
+}
+
+void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/)
+{
+	if(!dst)
+		dst = this;
+
+	int howManyStacks = 0; //how many stacks will hero receives <1 - 3>
+	int pom = cb->gameState()->getRandomGenerator().nextInt(99);
+	int warMachinesGiven = 0;
+
+	if(pom < 9)
+		howManyStacks = 1;
+	else if(pom < 79)
+		howManyStacks = 2;
+	else
+		howManyStacks = 3;
+
+	vstd::amin(howManyStacks, type->initialArmy.size());
+
+	for(int stackNo=0; stackNo < howManyStacks; stackNo++)
+	{
+		auto & stack = type->initialArmy[stackNo];
+
+		int count = cb->gameState()->getRandomGenerator().nextInt(stack.minAmount, stack.maxAmount);
+
+		if(stack.creature >= CreatureID::CATAPULT &&
+		   stack.creature <= CreatureID::ARROW_TOWERS) //war machine
+		{
+			warMachinesGiven++;
+			if(dst != this)
+				continue;
+
+			int slot = -1;
+			ArtifactID aid = ArtifactID::NONE;
+			switch (stack.creature)
+			{
+			case CreatureID::CATAPULT:
+				slot = ArtifactPosition::MACH4;
+				aid = ArtifactID::CATAPULT;
+				break;
+			default:
+				aid = CArtHandler::creatureToMachineID(stack.creature);
+				slot = 9 + aid;
+				break;
+			}
+			auto convSlot = ArtifactPosition(slot);
+			if(!getArt(convSlot))
+				putArtifact(convSlot, CArtifactInstance::createNewArtifactInstance(aid));
+			else
+                logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid;
+		}
+		else
+			dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count);
+	}
+}
+
+CGHeroInstance::~CGHeroInstance()
+{
+	commander.dellNull();
+}
+
+bool CGHeroInstance::needsLastStack() const
+{
+	return true;
+}
+
+void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
+{
+	if(h == this) return; //exclude potential self-visiting
+
+	if (ID == Obj::HERO)
+	{
+		if( cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) //our or ally hero
+		{
+			//exchange
+			cb->heroExchange(h->id, id);
+		}
+		else //battle
+		{
+			if(visitedTown) //we're in town
+				visitedTown->onHeroVisit(h); //town will handle attacking
+			else
+				cb->startBattleI(h,	this);
+		}
+	}
+	else if(ID == Obj::PRISON)
+	{
+		int txt_id;
+
+		if (cb->getHeroCount(h->tempOwner, false) < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)//GameConstants::MAX_HEROES_PER_PLAYER) //free hero slot
+		{
+			cb->changeObjPos(id,pos+int3(1,0,0),0);
+			//update hero parameters
+			SetMovePoints smp;
+			smp.hid = id;
+			smp.val = maxMovePoints (true); //TODO: hota prison on water?
+			cb->setMovePoints (&smp);
+			cb->setManaPoints (id, manaLimit());
+
+			cb->setObjProperty(id, ObjProperty::ID, Obj::HERO); //set ID to 34
+			cb->giveHero(id,h->tempOwner); //recreates def and adds hero to player
+
+			txt_id = 102;
+		}
+		else //already 8 wandering heroes
+		{
+			txt_id = 103;
+		}
+
+		showInfoDialog(h,txt_id,soundBase::ROGUE);
+	}
+}
+
+const std::string & CGHeroInstance::getHoverText() const
+{
+	if(ID != Obj::PRISON)
+	{
+		hoverName = VLC->generaltexth->allTexts[15];
+		boost::algorithm::replace_first(hoverName,"%s",name);
+		boost::algorithm::replace_first(hoverName,"%s", type->heroClass->name);
+		return hoverName;
+	}
+	else
+		hoverName = VLC->objtypeh->getObjectName(ID);
+
+	return hoverName;
+}
+
+const std::string & CGHeroInstance::getBiography() const
+{
+	if (biography.length())
+		return biography;
+	return type->biography;
+}
+
+ui8 CGHeroInstance::maxlevelsToMagicSchool() const
+{
+	return type->heroClass->isMagicHero() ? 3 : 4;
+}
+ui8 CGHeroInstance::maxlevelsToWisdom() const
+{
+	return type->heroClass->isMagicHero() ? 3 : 6;
+}
+
+void CGHeroInstance::SecondarySkillsInfo::resetMagicSchoolCounter()
+{
+	magicSchoolCounter = 1;
+}
+void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter()
+{
+	wisdomCounter = 1;
+}
+
+void CGHeroInstance::initObj()
+{
+	blockVisit = true;
+	auto  hs = new HeroSpecial();
+	hs->setNodeType(CBonusSystemNode::SPECIALTY);
+	attachTo(hs); //do we ever need to detach it?
+
+	if(!type)
+		initHero(); //TODO: set up everything for prison before specialties are configured
+
+	skillsInfo.rand.setSeed(cb->gameState()->getRandomGenerator().nextInt());
+	skillsInfo.resetMagicSchoolCounter();
+	skillsInfo.resetWisdomCounter();
+
+	for(const auto &spec : type->spec) //TODO: unfity with bonus system
+	{
+		auto bonus = new Bonus();
+		bonus->val = spec.val;
+		bonus->sid = id.getNum(); //from the hero, specialty has no unique id
+		bonus->duration = Bonus::PERMANENT;
+		bonus->source = Bonus::HERO_SPECIAL;
+		switch (spec.type)
+		{
+			case 1:// creature specialty
+				{
+					hs->growsWithLevel = true;
+
+					const CCreature &specCreature = *VLC->creh->creatures[spec.additionalinfo]; //creature in which we have specialty
+
+					//int creLevel = specCreature.level;
+					//if(!creLevel)
+					//{
+					//	if(spec.additionalinfo == 146)
+					//		creLevel = 5; //treat ballista as 5-level
+					//	else
+					//	{
+					//        logGlobal->warnStream() << "Warning: unknown level of " << specCreature.namePl;
+					//		continue;
+					//	}
+					//}
+
+					//bonus->additionalInfo = spec.additionalinfo; //creature id, should not be used again - this works only with limiter
+					bonus->limiter.reset(new CCreatureTypeLimiter (specCreature, true)); //with upgrades
+					bonus->type = Bonus::PRIMARY_SKILL;
+					bonus->valType = Bonus::ADDITIVE_VALUE;
+
+					bonus->subtype = PrimarySkill::ATTACK;
+					hs->addNewBonus(bonus);
+
+					bonus = new Bonus(*bonus);
+					bonus->subtype = PrimarySkill::DEFENSE;
+					hs->addNewBonus(bonus);
+					//values will be calculated later
+
+					bonus = new Bonus(*bonus);
+					bonus->type = Bonus::STACKS_SPEED;
+					bonus->val = 1; //+1 speed
+					hs->addNewBonus(bonus);
+				}
+				break;
+			case 2://secondary skill
+				hs->growsWithLevel = true;
+				bonus->type = Bonus::SPECIAL_SECONDARY_SKILL; //needs to be recalculated with level, based on this value
+				bonus->valType = Bonus::BASE_NUMBER; // to receive nonzero value
+				bonus->subtype = spec.subtype; //skill id
+				bonus->val = spec.val; //value per level, in percent
+				hs->addNewBonus(bonus);
+				bonus = new Bonus(*bonus);
+
+				switch (spec.additionalinfo)
+				{
+					case 0: //normal
+						bonus->valType = Bonus::PERCENT_TO_BASE;
+						break;
+					case 1: //when it's navigation or there's no 'base' at all
+						bonus->valType = Bonus::PERCENT_TO_ALL;
+						break;
+				}
+				bonus->type = Bonus::SECONDARY_SKILL_PREMY; //value will be calculated later
+				hs->addNewBonus(bonus);
+				break;
+			case 3://spell damage bonus, level dependent but calculated elsewhere
+				bonus->type = Bonus::SPECIAL_SPELL_LEV;
+				bonus->subtype = spec.subtype;
+				hs->addNewBonus(bonus);
+				break;
+			case 4://creature stat boost
+				switch (spec.subtype)
+				{
+					case 1://attack
+						bonus->type = Bonus::PRIMARY_SKILL;
+						bonus->subtype = PrimarySkill::ATTACK;
+						break;
+					case 2://defense
+						bonus->type = Bonus::PRIMARY_SKILL;
+						bonus->subtype = PrimarySkill::DEFENSE;
+						break;
+					case 3:
+						bonus->type = Bonus::CREATURE_DAMAGE;
+						bonus->subtype = 0; //both min and max
+						break;
+					case 4://hp
+						bonus->type = Bonus::STACK_HEALTH;
+						break;
+					case 5:
+						bonus->type = Bonus::STACKS_SPEED;
+						break;
+					default:
+						continue;
+				}
+				bonus->additionalInfo = spec.additionalinfo; //creature id
+				bonus->valType = Bonus::ADDITIVE_VALUE;
+				bonus->limiter.reset(new CCreatureTypeLimiter (*VLC->creh->creatures[spec.additionalinfo], true));
+				hs->addNewBonus(bonus);
+				break;
+			case 5://spell damage bonus in percent
+				bonus->type = Bonus::SPECIFIC_SPELL_DAMAGE;
+				bonus->valType = Bonus::BASE_NUMBER; // current spell system is screwed
+				bonus->subtype = spec.subtype; //spell id
+				hs->addNewBonus(bonus);
+				break;
+			case 6://damage bonus for bless (Adela)
+				bonus->type = Bonus::SPECIAL_BLESS_DAMAGE;
+				bonus->subtype = spec.subtype; //spell id if you ever wanted to use it otherwise
+				bonus->additionalInfo = spec.additionalinfo; //damage factor
+				hs->addNewBonus(bonus);
+				break;
+			case 7://maxed mastery for spell
+				bonus->type = Bonus::MAXED_SPELL;
+				bonus->subtype = spec.subtype; //spell i
+				hs->addNewBonus(bonus);
+				break;
+			case 8://peculiar spells - enchantments
+				bonus->type = Bonus::SPECIAL_PECULIAR_ENCHANT;
+				bonus->subtype = spec.subtype; //spell id
+				bonus->additionalInfo = spec.additionalinfo;//0, 1 for Coronius
+				hs->addNewBonus(bonus);
+				break;
+			case 9://upgrade creatures
+			{
+				const auto &creatures = VLC->creh->creatures;
+				bonus->type = Bonus::SPECIAL_UPGRADE;
+				bonus->subtype = spec.subtype; //base id
+				bonus->additionalInfo = spec.additionalinfo; //target id
+				hs->addNewBonus(bonus);
+				bonus = new Bonus(*bonus);
+
+				for(auto cre_id : creatures[spec.subtype]->upgrades)
+				{
+					bonus->subtype = cre_id; //propagate for regular upgrades of base creature
+					hs->addNewBonus(bonus);
+					bonus = new Bonus(*bonus);
+				}
+				vstd::clear_pointer(bonus);
+				break;
+			}
+			case 10://resource generation
+				bonus->type = Bonus::GENERATE_RESOURCE;
+				bonus->subtype = spec.subtype;
+				hs->addNewBonus(bonus);
+				break;
+			case 11://starting skill with mastery (Adrienne)
+				setSecSkillLevel(SecondarySkill(spec.val), spec.additionalinfo, true);
+				break;
+			case 12://army speed
+				bonus->type = Bonus::STACKS_SPEED;
+				hs->addNewBonus(bonus);
+				break;
+			case 13://Dragon bonuses (Mutare)
+				bonus->type = Bonus::PRIMARY_SKILL;
+				bonus->valType = Bonus::ADDITIVE_VALUE;
+				switch (spec.subtype)
+				{
+					case 1:
+						bonus->subtype = PrimarySkill::ATTACK;
+						break;
+					case 2:
+						bonus->subtype = PrimarySkill::DEFENSE;
+						break;
+				}
+				bonus->limiter.reset(new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE));
+				hs->addNewBonus(bonus);
+				break;
+			default:
+                logGlobal->warnStream() << "Unexpected hero specialty " << type;
+		}
+	}
+	specialty.push_back(hs); //will it work?
+
+	for (auto hs2 : type->specialty) //copy active (probably growing) bonuses from hero prootype to hero object
+	{
+		auto  hs = new HeroSpecial();
+		attachTo(hs); //do we ever need to detach it?
+
+		hs->setNodeType(CBonusSystemNode::SPECIALTY);
+		for (auto bonus : hs2.bonuses)
+		{
+			hs->addNewBonus (bonus);
+		}
+		hs->growsWithLevel = hs2.growsWithLevel;
+
+		specialty.push_back(hs); //will it work?
+	}
+
+	//initialize bonuses
+	recreateSecondarySkillsBonuses();
+	Updatespecialty();
+
+	mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one
+	type->name = name;
+}
+void CGHeroInstance::Updatespecialty() //TODO: calculate special value of bonuses on-the-fly?
+{
+	for (auto hs : specialty)
+	{
+		if (hs->growsWithLevel)
+		{
+			//const auto &creatures = VLC->creh->creatures;
+
+			for(Bonus * b : hs->getBonusList())
+			{
+				switch (b->type)
+				{
+					case Bonus::SECONDARY_SKILL_PREMY:
+						b->val = (hs->valOfBonuses(Bonus::SPECIAL_SECONDARY_SKILL, b->subtype) * level);
+						break; //use only hero skills as bonuses to avoid feedback loop
+					case Bonus::PRIMARY_SKILL: //for creatures, that is
+					{
+						const CCreature * cre = nullptr;
+						int creLevel = 0;
+						if (auto creatureLimiter = std::dynamic_pointer_cast<CCreatureTypeLimiter>(b->limiter)) //TODO: more general eveluation of bonuses?
+						{
+							cre = creatureLimiter->creature;
+							creLevel = cre->level;
+							if (!creLevel)
+							{
+								creLevel = 5; //treat ballista as tier 5
+							}
+						}
+						else //no creature found, can't calculate value
+						{
+                            logGlobal->warnStream() << "Primary skill specialty growth supported only with creature type limiters";
+							break;
+						}
+
+						double primSkillModifier = (int)(level / creLevel) / 20.0;
+						int param;
+						switch (b->subtype)
+						{
+							case PrimarySkill::ATTACK:
+								param = cre->Attack();
+								break;
+							case PrimarySkill::DEFENSE:
+								param = cre->Defense();
+								break;
+							default:
+								continue;
+						}
+						b->val = ceil(param * (1 + primSkillModifier)) - param; //yep, overcomplicated but matches original
+						break;
+					}
+				}
+			}
+		}
+	}
+}
+
+void CGHeroInstance::recreateSecondarySkillsBonuses()
+{
+	auto secondarySkillsBonuses = getBonuses(Selector::sourceType(Bonus::SECONDARY_SKILL));
+	for(auto bonus : *secondarySkillsBonuses)
+		removeBonus(bonus);
+
+	for(auto skill_info : secSkills)
+		updateSkill(SecondarySkill(skill_info.first), skill_info.second);
+}
+
+void CGHeroInstance::updateSkill(SecondarySkill which, int val)
+{
+	if(which == SecondarySkill::LEADERSHIP || which == SecondarySkill::LUCK)
+	{ //luck-> VLC->generaltexth->arraytxt[73+luckSkill]; VLC->generaltexth->arraytxt[104+moraleSkill]
+		bool luck = which == SecondarySkill::LUCK;
+		Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK};
+
+		Bonus *b = getBonusLocalFirst(Selector::type(type[luck]).And(Selector::sourceType(Bonus::SECONDARY_SKILL)));
+		if(!b)
+		{
+			b = new Bonus(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER);
+			addNewBonus(b);
+		}
+		else
+			b->val = +val;
+	}
+	else if(which == SecondarySkill::DIPLOMACY) //surrender discount: 20% per level
+	{
+
+		if(Bonus *b = getBonusLocalFirst(Selector::type(Bonus::SURRENDER_DISCOUNT).And(Selector::sourceType(Bonus::SECONDARY_SKILL))))
+			b->val = +val;
+		else
+			addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which));
+	}
+
+	int skillVal = 0;
+	switch (which)
+	{
+	case SecondarySkill::ARCHERY:
+		switch (val)
+		{
+		case 1:
+			skillVal = 10; break;
+		case 2:
+			skillVal = 25; break;
+		case 3:
+			skillVal = 50; break;
+		}
+		break;
+	case SecondarySkill::LOGISTICS:
+		skillVal = 10 * val; break;
+	case SecondarySkill::NAVIGATION:
+		skillVal = 50 * val; break;
+	case SecondarySkill::MYSTICISM:
+		skillVal = val; break;
+	case SecondarySkill::EAGLE_EYE:
+		skillVal = 30 + 10 * val; break;
+	case SecondarySkill::NECROMANCY:
+		skillVal = 10 * val; break;
+	case SecondarySkill::LEARNING:
+		skillVal = 5 * val; break;
+	case SecondarySkill::OFFENCE:
+		skillVal = 10 * val; break;
+	case SecondarySkill::ARMORER:
+		skillVal = 5 * val; break;
+	case SecondarySkill::INTELLIGENCE:
+		skillVal = 25 << (val-1); break;
+	case SecondarySkill::SORCERY:
+		skillVal = 5 * val; break;
+	case SecondarySkill::RESISTANCE:
+		skillVal = 5 << (val-1); break;
+	case SecondarySkill::FIRST_AID:
+		skillVal = 25 + 25*val; break;
+	case SecondarySkill::ESTATES:
+		skillVal = 125 << (val-1); break;
+	}
+
+
+	Bonus::ValueType skillValType = skillVal ? Bonus::BASE_NUMBER : Bonus::INDEPENDENT_MIN;
+	if(Bonus * b = getBonusList().getFirst(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, which)
+										.And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) //only local hero bonus
+	{
+		b->val = skillVal;
+		b->valType = skillValType;
+	}
+	else
+	{
+		auto bonus = new Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, Bonus::SECONDARY_SKILL, skillVal, id.getNum(), which, skillValType);
+		bonus->source = Bonus::SECONDARY_SKILL;
+		addNewBonus(bonus);
+	}
+
+}
+void CGHeroInstance::setPropertyDer( ui8 what, ui32 val )
+{
+	if(what == ObjProperty::PRIMARY_STACK_COUNT)
+		setStackCount(SlotID(0), val);
+}
+
+double CGHeroInstance::getFightingStrength() const
+{
+	return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE)));
+}
+
+double CGHeroInstance::getMagicStrength() const
+{
+	return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER)));
+}
+
+double CGHeroInstance::getHeroStrength() const
+{
+	return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrength(), 2.0));
+}
+
+ui64 CGHeroInstance::getTotalStrength() const
+{
+	double ret = getFightingStrength() * getArmyStrength();
+	return (ui64) ret;
+}
+
+TExpType CGHeroInstance::calculateXp(TExpType exp) const
+{
+	return exp * (100 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::LEARNING))/100.0;
+}
+
+ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool) const
+{
+	si16 skill = -1; //skill level
+
+#define TRY_SCHOOL(schoolName, schoolMechanicsId, schoolOutId)	\
+	if(spell-> schoolName)									\
+	{															\
+		int thisSchool = std::max<int>(getSecSkillLevel( \
+			SecondarySkill(14 + (schoolMechanicsId))), \
+			valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << (schoolMechanicsId))); \
+		if(thisSchool > skill)									\
+		{														\
+			skill = thisSchool;									\
+			if(outSelectedSchool)								\
+				*outSelectedSchool = schoolOutId;				\
+		}														\
+	}
+	TRY_SCHOOL(fire, 0, 1)
+	TRY_SCHOOL(air, 1, 0)
+	TRY_SCHOOL(water, 2, 2)
+	TRY_SCHOOL(earth, 3, 3)
+#undef TRY_SCHOOL
+
+
+
+	vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus
+	vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect
+	if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero specialty (Daremyth, Melodia)
+		skill = 3;
+	assert(skill >= 0 && skill <= 3);
+	return skill;
+}
+
+bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
+{
+	if(!getArt(ArtifactPosition::SPELLBOOK)) //if hero has no spellbook
+		return false;
+
+    if (spell->isSpecialSpell())
+    {
+        if (vstd::contains(spells, spell->id))
+        {//hero has this spell in spellbook
+            logGlobal->errorStream() << "Special spell in spellbook "<<spell->name;
+        }
+
+        if (hasBonusOfType(Bonus::SPELL, spell->id))
+            return true;
+
+        return false;
+    }
+    else
+    {
+        if(vstd::contains(spells, spell->id) //hero has this spell in spellbook
+            || (spell->air && hasBonusOfType(Bonus::AIR_SPELLS)) // this is air spell and hero can cast all air spells
+            || (spell->fire && hasBonusOfType(Bonus::FIRE_SPELLS)) // this is fire spell and hero can cast all fire spells
+            || (spell->water && hasBonusOfType(Bonus::WATER_SPELLS)) // this is water spell and hero can cast all water spells
+            || (spell->earth && hasBonusOfType(Bonus::EARTH_SPELLS)) // this is earth spell and hero can cast all earth spells
+            || hasBonusOfType(Bonus::SPELL, spell->id)
+            || hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->level)
+            )
+            return true;
+
+        return false;
+    }
+}
+
+/**
+ * Calculates what creatures and how many to be raised from a battle.
+ * @param battleResult The results of the battle.
+ * @return Returns a pair with the first value indicating the ID of the creature
+ * type and second value the amount. Both values are returned as -1 if necromancy
+ * could not be applied.
+ */
+CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &battleResult) const
+{
+	const ui8 necromancyLevel = getSecSkillLevel(SecondarySkill::NECROMANCY);
+
+	// Hero knows necromancy or has Necromancer Cloak
+	if (necromancyLevel > 0 || hasBonusOfType(Bonus::IMPROVED_NECROMANCY))
+	{
+		double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::NECROMANCY)/100.0;
+		vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all...
+		const std::map<ui32,si32> &casualties = battleResult.casualties[!battleResult.winner];
+		ui32 raisedUnits = 0;
+
+		// Figure out what to raise and how many.
+		const CreatureID creatureTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES};
+		const bool improvedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY);
+		const CCreature *raisedUnitType = VLC->creh->creatures[creatureTypes[improvedNecromancy ? necromancyLevel : 0]];
+		const ui32 raisedUnitHP = raisedUnitType->valOfBonuses(Bonus::STACK_HEALTH);
+
+		//calculate creatures raised from each defeated stack
+		for (auto & casualtie : casualties)
+		{
+			// Get lost enemy hit points convertible to units.
+			CCreature * c = VLC->creh->creatures[casualtie.first];
+
+			const ui32 raisedHP = c->valOfBonuses(Bonus::STACK_HEALTH) * casualtie.second * necromancySkill;
+			raisedUnits += std::min<ui32>(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count
+		}
+
+		// Make room for new units.
+		SlotID slot = getSlotFor(raisedUnitType->idNumber);
+		if (slot == SlotID())
+		{
+			// If there's no room for unit, try it's upgraded version 2/3rds the size.
+			raisedUnitType = VLC->creh->creatures[*raisedUnitType->upgrades.begin()];
+			raisedUnits = (raisedUnits*2)/3;
+
+			slot = getSlotFor(raisedUnitType->idNumber);
+		}
+		if (raisedUnits <= 0)
+			raisedUnits = 1;
+
+		return CStackBasicDescriptor(raisedUnitType->idNumber, raisedUnits);
+	}
+
+	return CStackBasicDescriptor();
+}
+
+/**
+ * Show the necromancy dialog with information about units raised.
+ * @param raisedStack Pair where the first element represents ID of the raised creature
+ * and the second element the amount.
+ */
+void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const
+{
+	InfoWindow iw;
+	iw.soundID = soundBase::pickup01 + cb->gameState()->getRandomGenerator().nextInt(6);
+	iw.player = tempOwner;
+	iw.components.push_back(Component(raisedStack));
+
+	if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural)
+	{
+		iw.text.addTxt(MetaString::GENERAL_TXT, 145);
+		iw.text.addReplacement(raisedStack.count);
+	}
+	else // Practicing the dark arts of necromancy, ... (singular)
+	{
+		iw.text.addTxt(MetaString::GENERAL_TXT, 146);
+	}
+	iw.text.addReplacement(raisedStack);
+
+	cb->showInfoDialog(&iw);
+}
+
+int3 CGHeroInstance::getSightCenter() const
+{
+	return getPosition(false);
+}
+
+int CGHeroInstance::getSightRadious() const
+{
+	return 5 + getSecSkillLevel(SecondarySkill::SCOUTING) + valOfBonuses(Bonus::SIGHT_RADIOUS); //default + scouting
+}
+
+si32 CGHeroInstance::manaRegain() const
+{
+	if (hasBonusOfType(Bonus::FULL_MANA_REGENERATION))
+		return manaLimit();
+
+	return 1 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 8) + valOfBonuses(Bonus::MANA_REGENERATION); //1 + Mysticism level
+}
+
+// /**
+//  * Places an artifact in hero's backpack. If it's a big artifact equips it
+//  * or discards it if it cannot be equipped.
+//  */
+// void CGHeroInstance::giveArtifact (ui32 aid) //use only for fixed artifacts
+// {
+// 	CArtifact * const artifact = VLC->arth->artifacts[aid]; //pointer to constant object
+// 	CArtifactInstance *ai = CArtifactInstance::createNewArtifactInstance(artifact);
+// 	ai->putAt(this, ai->firstAvailableSlot(this));
+// }
+
+int CGHeroInstance::getBoatType() const
+{
+	switch(type->heroClass->getAlignment())
+	{
+	case EAlignment::GOOD:
+		return 1;
+	case EAlignment::EVIL:
+		return 0;
+	case EAlignment::NEUTRAL:
+		return 2;
+	default:
+		throw std::runtime_error("Wrong alignment!");
+	}
+}
+
+void CGHeroInstance::getOutOffsets(std::vector<int3> &offsets) const
+{
+	static int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
+	for (auto & dir : dirs)
+		offsets += dir;
+}
+
+int CGHeroInstance::getSpellCost(const CSpell *sp) const
+{
+	return sp->getCost(getSpellSchoolLevel(sp));
+}
+
+void CGHeroInstance::pushPrimSkill( PrimarySkill::PrimarySkill which, int val )
+{
+	assert(!hasBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, which)
+						.And(Selector::sourceType(Bonus::HERO_BASE_SKILL))));
+	addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::HERO_BASE_SKILL, val, id.getNum(), which));
+}
+
+EAlignment::EAlignment CGHeroInstance::getAlignment() const
+{
+	return type->heroClass->getAlignment();
+}
+
+void CGHeroInstance::initExp()
+{
+	exp = cb->gameState()->getRandomGenerator().nextInt(40, 89);
+}
+
+std::string CGHeroInstance::nodeName() const
+{
+	return "Hero " + name;
+}
+
+void CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance *art)
+{
+	assert(!getArt(pos));
+	art->putAt(ArtifactLocation(this, pos));
+}
+
+void CGHeroInstance::putInBackpack(CArtifactInstance *art)
+{
+	putArtifact(art->firstBackpackSlot(this), art);
+}
+
+bool CGHeroInstance::hasSpellbook() const
+{
+	return getArt(ArtifactPosition::SPELLBOOK);
+}
+
+void CGHeroInstance::deserializationFix()
+{
+	artDeserializationFix(this);
+
+	for (auto hs : specialty)
+	{
+		attachTo (hs);
+	}
+}
+
+CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs)
+{
+	if(visitedTown)
+	{
+		if(inTownGarrison)
+			return visitedTown;
+		else
+			return &visitedTown->townAndVis;
+	}
+	else
+		return CArmedInstance::whereShouldBeAttached(gs);
+}
+
+int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const
+{
+	if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
+		return (MPsBefore - basicCost) * static_cast<double>(maxMovePoints(disembark)) / maxMovePoints(!disembark);
+
+	return 0; //take all MPs otherwise
+}
+
+CGHeroInstance::ECanDig CGHeroInstance::diggingStatus() const
+{
+	if(movement < maxMovePoints(true))
+		return LACK_OF_MOVEMENT;
+    else if(cb->getTile(getPosition(false))->terType == ETerrainType::WATER)
+		return WRONG_TERRAIN;
+	else
+	{
+		const TerrainTile *t = cb->getTile(getPosition());
+		//TODO look for hole
+		//CGI->mh->getTerrainDescr(h->getPosition(false), hlp, false);
+		if(/*hlp.length() || */t->blockingObjects.size() > 1)
+			return TILE_OCCUPIED;
+		else
+			return CAN_DIG;
+	}
+}
+
+ArtBearer::ArtBearer CGHeroInstance::bearerType() const
+{
+	return ArtBearer::HERO;
+}
+
+std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills() const
+{
+	std::vector<SecondarySkill> obligatorySkills; //hero is offered magic school or wisdom if possible
+	if (!skillsInfo.wisdomCounter)
+	{
+		if (cb->isAllowed(2, SecondarySkill::WISDOM) && !getSecSkillLevel(SecondarySkill::WISDOM))
+			obligatorySkills.push_back(SecondarySkill::WISDOM);
+	}
+	if (!skillsInfo.magicSchoolCounter)
+	{
+		std::vector<SecondarySkill> ss;
+		ss += SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC;
+
+		std::shuffle(ss.begin(), ss.end(), skillsInfo.rand.getStdGenerator());
+
+		for (auto skill : ss)
+		{
+			if (cb->isAllowed(2, skill) && !getSecSkillLevel(skill)) //only schools hero doesn't know yet
+			{
+				obligatorySkills.push_back(skill);
+				break; //only one
+			}
+		}
+	}
+
+	std::vector<SecondarySkill> skills;
+	//picking sec. skills for choice
+	std::set<SecondarySkill> basicAndAdv, expert, none;
+	for(int i=0;i<GameConstants::SKILL_QUANTITY;i++)
+		if (cb->isAllowed(2,i))
+			none.insert(SecondarySkill(i));
+
+	for(auto & elem : secSkills)
+	{
+		if(elem.second < SecSkillLevel::EXPERT)
+			basicAndAdv.insert(elem.first);
+		else
+			expert.insert(elem.first);
+		none.erase(elem.first);
+	}
+	for (auto s : obligatorySkills) //don't duplicate them
+	{
+		none.erase (s);
+		basicAndAdv.erase (s);
+		expert.erase (s);
+	}
+
+	//first offered skill:
+	// 1) give obligatory skill
+	// 2) give any other new skill
+	// 3) upgrade existing
+	if (canLearnSkill() && obligatorySkills.size() > 0)
+	{
+		skills.push_back (obligatorySkills[0]);
+	}
+	else if(none.size() && canLearnSkill()) //hero have free skill slot
+	{
+		skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //new skill
+		none.erase(skills.back());
+	}
+	else if(!basicAndAdv.empty())
+	{
+		skills.push_back(type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand)); //upgrade existing
+		basicAndAdv.erase(skills.back());
+	}
+
+	//second offered skill:
+	//1) upgrade existing
+	//2) give obligatory skill
+	//3) give any other new skill
+	if(!basicAndAdv.empty())
+	{
+		SecondarySkill s = type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand);//upgrade existing
+		skills.push_back(s);
+		basicAndAdv.erase(s);
+	}
+	else if (canLearnSkill() && obligatorySkills.size() > 1)
+	{
+		skills.push_back (obligatorySkills[1]);
+	}
+	else if(none.size() && canLearnSkill())
+	{
+		skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //give new skill
+		none.erase(skills.back());
+	}
+
+	return skills;
+}
+
+PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill() const
+{
+	assert(gainsLevel());
+	int randomValue = cb->gameState()->getRandomGenerator().nextInt(99), pom = 0, primarySkill = 0;
+	const auto & skillChances = (level > 9) ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel;
+
+	for(; primarySkill < GameConstants::PRIMARY_SKILLS; ++primarySkill)
+	{
+		pom += skillChances[primarySkill];
+		if(randomValue < pom)
+		{
+			break;
+		}
+	}
+
+	logGlobal->traceStream() << "The hero gets the primary skill " << primarySkill << " with a probability of " << randomValue << "%.";
+	return static_cast<PrimarySkill::PrimarySkill>(primarySkill);
+}
+
+boost::optional<SecondarySkill> CGHeroInstance::nextSecondarySkill() const
+{
+	assert(gainsLevel());
+
+	boost::optional<SecondarySkill> chosenSecondarySkill;
+	const auto proposedSecondarySkills = getLevelUpProposedSecondarySkills();
+	if(!proposedSecondarySkills.empty())
+	{
+		std::vector<SecondarySkill> learnedSecondarySkills;
+		for(auto secondarySkill : proposedSecondarySkills)
+		{
+			if(getSecSkillLevel(secondarySkill) > 0)
+			{
+				learnedSecondarySkills.push_back(secondarySkill);
+			}
+		}
+
+		auto & rand = cb->gameState()->getRandomGenerator();
+		if(learnedSecondarySkills.empty())
+		{
+			// there are only new skills to learn, so choose anyone of them
+			chosenSecondarySkill = *RandomGeneratorUtil::nextItem(proposedSecondarySkills, rand);
+		}
+		else
+		{
+			// preferably upgrade a already learned secondary skill
+			chosenSecondarySkill = *RandomGeneratorUtil::nextItem(learnedSecondarySkills, rand);
+		}
+	}
+	return chosenSecondarySkill;
+}
+
+void CGHeroInstance::setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs)
+{
+	if(primarySkill < PrimarySkill::EXPERIENCE)
+	{
+		Bonus * skill = getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL)
+											.And(Selector::subtype(primarySkill))
+											.And(Selector::sourceType(Bonus::HERO_BASE_SKILL)));
+		assert(skill);
+
+		if(abs)
+		{
+			skill->val = value;
+		}
+		else
+		{
+			skill->val += value;
+		}
+	}
+	else if(primarySkill == PrimarySkill::EXPERIENCE)
+	{
+		if(abs)
+		{
+			exp = value;
+		}
+		else
+		{
+			exp += value;
+		}
+	}
+}
+
+bool CGHeroInstance::gainsLevel() const
+{
+	return exp >= VLC->heroh->reqExp(level+1);
+}
+
+void CGHeroInstance::levelUp(std::vector<SecondarySkill> skills)
+{
+	++level;
+
+	//deterministic secondary skills
+	skillsInfo.magicSchoolCounter = (skillsInfo.magicSchoolCounter + 1) % maxlevelsToMagicSchool();
+	skillsInfo.wisdomCounter = (skillsInfo.wisdomCounter + 1) % maxlevelsToWisdom();
+	if(vstd::contains(skills, SecondarySkill::WISDOM))
+	{
+		skillsInfo.resetWisdomCounter();
+	}
+
+	SecondarySkill spellSchools[] = {
+		SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC};
+	for(auto skill : spellSchools)
+	{
+		if(vstd::contains(skills, skill))
+		{
+			skillsInfo.resetMagicSchoolCounter();
+			break;
+		}
+	}
+
+	//specialty
+	Updatespecialty();
+}
+
+void CGHeroInstance::levelUpAutomatically()
+{
+	while(gainsLevel())
+	{
+		const auto primarySkill = nextPrimarySkill();
+		setPrimarySkill(primarySkill, 1, false);
+
+		auto proposedSecondarySkills = getLevelUpProposedSecondarySkills();
+
+		const auto secondarySkill = nextSecondarySkill();
+		if(secondarySkill)
+		{
+			setSecSkillLevel(*secondarySkill, 1, false);
+		}
+
+		//TODO why has the secondary skills to be passed to the method?
+		levelUp(proposedSecondarySkills);
+	}
+}

+ 227 - 0
lib/mapObjects/CGHeroInstance.h

@@ -0,0 +1,227 @@
+#pragma once
+
+#include "CObjectHandler.h"
+#include "CGArmedInstance.h"
+
+#include "../CArtHandler.h" // For CArtifactSet
+#include "../CRandomGenerator.h"
+
+/*
+ * CObjectHandler.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
+ *
+ */
+
+class CHero;
+class CGBoat;
+
+class CGHeroPlaceholder : public CGObjectInstance
+{
+public:
+	//subID stores id of hero type. If it's 0xff then following field is used
+	ui8 power;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+		h & power;
+	}
+};
+
+
+class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet
+{
+public:
+	enum ECanDig
+	{
+		CAN_DIG, LACK_OF_MOVEMENT, WRONG_TERRAIN, TILE_OCCUPIED
+	};
+	//////////////////////////////////////////////////////////////////////////
+
+	ui8 moveDir; //format:	123
+					//		8 4
+					//		765
+	mutable ui8 isStanding, tacticFormationEnabled;
+
+	//////////////////////////////////////////////////////////////////////////
+
+	ConstTransitivePtr<CHero> type;
+	TExpType exp; //experience points
+	ui32 level; //current level of hero
+	std::string name; //may be custom
+	std::string biography; //if custom
+	si32 portrait; //may be custom
+	si32 mana; // remaining spell points
+	std::vector<std::pair<SecondarySkill,ui8> > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities
+	ui32 movement; //remaining movement points
+	ui8 sex;
+	bool inTownGarrison; // if hero is in town garrison
+	ConstTransitivePtr<CGTownInstance> visitedTown; //set if hero is visiting town or in the town garrison
+	ConstTransitivePtr<CCommanderInstance> commander;
+	const CGBoat *boat; //set to CGBoat when sailing
+
+
+	//std::vector<const CArtifact*> artifacts; //hero's artifacts from bag
+	//std::map<ui16, const CArtifact*> artifWorn; //map<position,artifact_id>; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5
+	std::set<SpellID> spells; //known spells (spell IDs)
+	std::set<ObjectInstanceID> visitedObjects;
+
+	struct DLL_LINKAGE Patrol
+	{
+		Patrol(){patrolling=false;patrolRadious=-1;};
+		bool patrolling;
+		ui32 patrolRadious;
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & patrolling & patrolRadious;
+		}
+	} patrol;
+
+	struct DLL_LINKAGE HeroSpecial : CBonusSystemNode
+	{
+		bool growsWithLevel;
+
+		HeroSpecial(){growsWithLevel = false;};
+
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & static_cast<CBonusSystemNode&>(*this);
+			h & growsWithLevel;
+		}
+	};
+
+	std::vector<HeroSpecial*> specialty;
+
+	struct DLL_LINKAGE SecondarySkillsInfo
+	{
+		//skills are determined, initialized at map start
+		//FIXME remove mutable
+		mutable CRandomGenerator rand;
+		ui8 magicSchoolCounter;
+		ui8 wisdomCounter;
+
+		void resetMagicSchoolCounter();
+		void resetWisdomCounter();
+
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & magicSchoolCounter & wisdomCounter & rand;
+		}
+	} skillsInfo;
+
+	int3 getSightCenter() const; //"center" tile from which the sight distance is calculated
+	int getSightRadious() const; //sight distance (should be used if player-owned structure)
+	//////////////////////////////////////////////////////////////////////////
+
+	int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral
+	void getOutOffsets(std::vector<int3> &offsets) const; //offsets to obj pos when we boat can be placed
+
+	//////////////////////////////////////////////////////////////////////////
+
+	bool hasSpellbook() const;
+	EAlignment::EAlignment getAlignment() const;
+	const std::string &getBiography() const;
+	bool needsLastStack()const;
+	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
+	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
+	bool canWalkOnSea() const;
+	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
+
+	// ----- primary and secondary skill, experience, level handling -----
+
+	/// Returns true if hero has lower level than should upon his experience.
+	bool gainsLevel() const;
+
+	/// Returns the next primary skill on level up. Can only be called if hero can gain a level up.
+	PrimarySkill::PrimarySkill nextPrimarySkill() const;
+
+	/// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up.
+	boost::optional<SecondarySkill> nextSecondarySkill() const;
+
+	/// Gets 0, 1 or 2 secondary skills which are proposed on hero level up.
+	std::vector<SecondarySkill> getLevelUpProposedSecondarySkills() const;
+
+	ui8 getSecSkillLevel(SecondarySkill skill) const; //0 - no skill
+
+	/// Returns true if hero has free secondary skill slot.
+	bool canLearnSkill() const;
+
+	void setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs);
+	void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value
+	void levelUp(std::vector<SecondarySkill> skills);
+
+	int maxMovePoints(bool onLand) const;
+	int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const;
+
+	//int getSpellSecLevel(int spell) const; //returns level of secondary ability (fire, water, earth, air magic) known to this hero and applicable to given spell; -1 if error
+	static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
+	double getFightingStrength() const; // takes attack / defense skill into account
+	double getMagicStrength() const; // takes knowledge / spell power skill into account
+	double getHeroStrength() const; // includes fighting and magic strength
+	ui64 getTotalStrength() const; // includes fighting strength and army strength
+	TExpType calculateXp(TExpType exp) const; //apply learning skill
+	ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const; //returns level on which given spell would be cast by this hero (0 - none, 1 - basic etc); optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic,
+	bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses
+	CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const;
+	void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const;
+	ECanDig diggingStatus() const; //0 - can dig; 1 - lack of movement; 2 -
+
+	//////////////////////////////////////////////////////////////////////////
+
+	void setType(si32 ID, si32 subID);
+
+	void initHero();
+	void initHero(HeroTypeID SUBID);
+
+	void putArtifact(ArtifactPosition pos, CArtifactInstance *art);
+	void putInBackpack(CArtifactInstance *art);
+	void initExp();
+	void initArmy(IArmyDescriptor *dst = nullptr);
+	//void giveArtifact (ui32 aid);
+	void pushPrimSkill(PrimarySkill::PrimarySkill which, int val);
+	ui8 maxlevelsToMagicSchool() const;
+	ui8 maxlevelsToWisdom() const;
+	void Updatespecialty();
+	void recreateSecondarySkillsBonuses();
+	void updateSkill(SecondarySkill which, int val);
+
+	CGHeroInstance();
+	virtual ~CGHeroInstance();
+	//////////////////////////////////////////////////////////////////////////
+	//
+	ArtBearer::ArtBearer bearerType() const override;
+	//////////////////////////////////////////////////////////////////////////
+
+	CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override;
+	std::string nodeName() const override;
+	void deserializationFix();
+
+	void initObj() override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	const std::string & getHoverText() const override;
+protected:
+	void setPropertyDer(ui8 what, ui32 val) override;//synchr
+
+private:
+	void levelUpAutomatically();
+
+public:
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this);
+		h & static_cast<CArtifactSet&>(*this);
+		h & exp & level & name & biography & portrait & mana & secSkills & movement
+			& sex & inTownGarrison & spells & patrol & moveDir & skillsInfo;
+		h & visitedTown & boat;
+		h & type & specialty & commander;
+		BONUS_TREE_DESERIALIZATION_FIX
+		//visitied town pointer will be restored by map serialization method
+	}
+};

+ 334 - 0
lib/mapObjects/CGMarket.cpp

@@ -0,0 +1,334 @@
+/*
+ * CObjectHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CGMarket.h"
+
+#include "NetPacks.h"
+#include "CGeneralTextHandler.h"
+
+using namespace boost::assign;
+
+///helpers
+static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1)
+{
+	OpenWindow ow;
+	ow.window = type;
+	ow.id1 = id1;
+	ow.id2 = id2;
+	IObjectInterface::cb->sendAndApply(&ow);
+}
+
+bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const
+{
+	switch(mode)
+	{
+	case EMarketMode::RESOURCE_RESOURCE:
+		{
+			double effectiveness = std::min((getMarketEfficiency() + 1.0) / 20.0, 0.5);
+
+			double r = VLC->objh->resVals[id1], //value of given resource
+				g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource
+
+			if(r>g) //if given resource is more expensive than wanted
+			{
+				val2 = ceil(r / g);
+				val1 = 1;
+			}
+			else //if wanted resource is more expensive
+			{
+				val1 = (g / r) + 0.5;
+				val2 = 1;
+			}
+		}
+		break;
+	case EMarketMode::CREATURE_RESOURCE:
+		{
+			const double effectivenessArray[] = {0.0, 0.3, 0.45, 0.50, 0.65, 0.7, 0.85, 0.9, 1.0};
+			double effectiveness = effectivenessArray[std::min(getMarketEfficiency(), 8)];
+
+			double r = VLC->creh->creatures[id1]->cost[6], //value of given creature in gold
+				g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource
+
+			if(r>g) //if given resource is more expensive than wanted
+			{
+				val2 = ceil(r / g);
+				val1 = 1;
+			}
+			else //if wanted resource is more expensive
+			{
+				val1 = (g / r) + 0.5;
+				val2 = 1;
+			}
+		}
+		break;
+	case EMarketMode::RESOURCE_PLAYER:
+		val1 = 1;
+		val2 = 1;
+		break;
+	case EMarketMode::RESOURCE_ARTIFACT:
+		{
+			double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6);
+			double r = VLC->objh->resVals[id1], //value of offered resource
+				g = VLC->arth->artifacts[id2]->price / effectiveness; //value of bought artifact in gold
+
+			if(id1 != 6) //non-gold prices are doubled
+				r /= 2;
+
+			val1 = std::max(1, (int)((g / r) + 0.5)); //don't sell arts for less than 1 resource
+			val2 = 1;
+		}
+		break;
+	case EMarketMode::ARTIFACT_RESOURCE:
+		{
+			double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6);
+			double r = VLC->arth->artifacts[id1]->price * effectiveness,
+				g = VLC->objh->resVals[id2];
+
+// 			if(id2 != 6) //non-gold prices are doubled
+// 				r /= 2;
+
+			val1 = 1;
+			val2 = std::max(1, (int)((r / g) + 0.5)); //at least one resource is given in return
+		}
+		break;
+	case EMarketMode::CREATURE_EXP:
+		{
+			val1 = 1;
+			val2 = (VLC->creh->creatures[id1]->AIValue / 40) * 5;
+		}
+		break;
+	case EMarketMode::ARTIFACT_EXP:
+		{
+			val1 = 1;
+
+			int givenClass = VLC->arth->artifacts[id1]->getArtClassSerial();
+			if(givenClass < 0 || givenClass > 3)
+			{
+				val2 = 0;
+				return false;
+			}
+
+			static const int expPerClass[] = {1000, 1500, 3000, 6000};
+			val2 = expPerClass[givenClass];
+		}
+		break;
+	default:
+		assert(0);
+		return false;
+	}
+
+	return true;
+}
+
+bool IMarket::allowsTrade(EMarketMode::EMarketMode mode) const
+{
+	return false;
+}
+
+int IMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const
+{
+	switch(mode)
+	{
+	case EMarketMode::RESOURCE_RESOURCE:
+	case EMarketMode::ARTIFACT_RESOURCE:
+	case EMarketMode::CREATURE_RESOURCE:
+			return -1;
+	default:
+			return 1;
+	}
+}
+
+std::vector<int> IMarket::availableItemsIds(EMarketMode::EMarketMode mode) const
+{
+	std::vector<int> ret;
+	switch(mode)
+	{
+	case EMarketMode::RESOURCE_RESOURCE:
+	case EMarketMode::ARTIFACT_RESOURCE:
+	case EMarketMode::CREATURE_RESOURCE:
+		for (int i = 0; i < 7; i++)
+			ret.push_back(i);
+	}
+	return ret;
+}
+
+const IMarket * IMarket::castFrom(const CGObjectInstance *obj, bool verbose /*= true*/)
+{
+	switch(obj->ID)
+	{
+	case Obj::TOWN:
+		return static_cast<const CGTownInstance*>(obj);
+	case Obj::ALTAR_OF_SACRIFICE:
+	case Obj::BLACK_MARKET:
+	case Obj::TRADING_POST:
+	case Obj::TRADING_POST_SNOW:
+	case Obj::FREELANCERS_GUILD:
+		return static_cast<const CGMarket*>(obj);
+	case Obj::UNIVERSITY:
+		return static_cast<const CGUniversity*>(obj);
+	default:
+		if(verbose)
+            logGlobal->errorStream() << "Cannot cast to IMarket object with ID " << obj->ID;
+		return nullptr;
+	}
+}
+
+IMarket::IMarket(const CGObjectInstance *O)
+	:o(O)
+{
+
+}
+
+std::vector<EMarketMode::EMarketMode> IMarket::availableModes() const
+{
+	std::vector<EMarketMode::EMarketMode> ret;
+	for (int i = 0; i < EMarketMode::MARTKET_AFTER_LAST_PLACEHOLDER; i++)
+		if(allowsTrade((EMarketMode::EMarketMode)i))
+			ret.push_back((EMarketMode::EMarketMode)i);
+
+	return ret;
+}
+
+void CGMarket::onHeroVisit(const CGHeroInstance * h) const
+{
+	openWindow(OpenWindow::MARKET_WINDOW,id.getNum(),h->id.getNum());
+}
+
+int CGMarket::getMarketEfficiency() const
+{
+	return 5;
+}
+
+bool CGMarket::allowsTrade(EMarketMode::EMarketMode mode) const
+{
+	switch(mode)
+	{
+	case EMarketMode::RESOURCE_RESOURCE:
+	case EMarketMode::RESOURCE_PLAYER:
+		switch(ID)
+		{
+		case Obj::TRADING_POST:
+		case Obj::TRADING_POST_SNOW:
+			return true;
+		default:
+			return false;
+		}
+	case EMarketMode::CREATURE_RESOURCE:
+		return ID == Obj::FREELANCERS_GUILD;
+	//case ARTIFACT_RESOURCE:
+	case EMarketMode::RESOURCE_ARTIFACT:
+		return ID == Obj::BLACK_MARKET;
+	case EMarketMode::ARTIFACT_EXP:
+	case EMarketMode::CREATURE_EXP:
+		return ID == Obj::ALTAR_OF_SACRIFICE; //TODO? check here for alignment of visiting hero? - would not be coherent with other checks here
+	case EMarketMode::RESOURCE_SKILL:
+		return ID == Obj::UNIVERSITY;
+	default:
+		return false;
+	}
+}
+
+int CGMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const
+{
+	return -1;
+}
+
+std::vector<int> CGMarket::availableItemsIds(EMarketMode::EMarketMode mode) const
+{
+	switch(mode)
+	{
+	case EMarketMode::RESOURCE_RESOURCE:
+	case EMarketMode::RESOURCE_PLAYER:
+		return IMarket::availableItemsIds(mode);
+	default:
+		return std::vector<int>();
+	}
+}
+
+CGMarket::CGMarket()
+	:IMarket(this)
+{
+}
+
+std::vector<int> CGBlackMarket::availableItemsIds(EMarketMode::EMarketMode mode) const
+{
+	switch(mode)
+	{
+	case EMarketMode::ARTIFACT_RESOURCE:
+		return IMarket::availableItemsIds(mode);
+	case EMarketMode::RESOURCE_ARTIFACT:
+		{
+			std::vector<int> ret;
+			for(const CArtifact *a : artifacts)
+				if(a)
+					ret.push_back(a->id);
+				else
+					ret.push_back(-1);
+			return ret;
+		}
+	default:
+		return std::vector<int>();
+	}
+}
+
+void CGBlackMarket::newTurn() const
+{
+	if(cb->getDate(Date::DAY_OF_MONTH) != 1) //new month
+		return;
+
+	SetAvailableArtifacts saa;
+	saa.id = id.getNum();
+	cb->pickAllowedArtsSet(saa.arts);
+	cb->sendAndApply(&saa);
+}
+
+void CGUniversity::initObj()
+{
+	std::vector<int> toChoose;
+	for(int i = 0; i < GameConstants::SKILL_QUANTITY; ++i)
+	{
+		if(cb->isAllowed(2, i))
+		{
+			toChoose.push_back(i);
+		}
+	}
+	if(toChoose.size() < 4)
+	{
+        logGlobal->warnStream()<<"Warning: less then 4 available skills was found by University initializer!";
+		return;
+	}
+
+	// get 4 skills
+	for(int i = 0; i < 4; ++i)
+	{
+		// move randomly one skill to selected and remove from list
+		auto it = RandomGeneratorUtil::nextItem(toChoose, cb->gameState()->getRandomGenerator());
+		skills.push_back(*it);
+		toChoose.erase(it);
+	}
+}
+
+std::vector<int> CGUniversity::availableItemsIds(EMarketMode::EMarketMode mode) const
+{
+	switch (mode)
+	{
+		case EMarketMode::RESOURCE_SKILL:
+			return skills;
+
+		default:
+			return std::vector <int> ();
+	}
+}
+
+void CGUniversity::onHeroVisit(const CGHeroInstance * h) const
+{
+	openWindow(OpenWindow::UNIVERSITY_WINDOW,id.getNum(),h->id.getNum());
+}

+ 88 - 0
lib/mapObjects/CGMarket.h

@@ -0,0 +1,88 @@
+#pragma once
+
+#include "CObjectHandler.h"
+
+/*
+ * CObjectHandler.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
+ *
+ */
+
+class DLL_LINKAGE IMarket
+{
+public:
+	const CGObjectInstance *o;
+
+	IMarket(const CGObjectInstance *O);
+	virtual ~IMarket() {}
+
+	virtual int getMarketEfficiency() const =0;
+	virtual bool allowsTrade(EMarketMode::EMarketMode mode) const;
+	virtual int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const; //-1 if unlimited
+	virtual std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
+
+	bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units
+	std::vector<EMarketMode::EMarketMode> availableModes() const;
+
+	static const IMarket *castFrom(const CGObjectInstance *obj, bool verbose = true);
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & o;
+	}
+};
+
+class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket
+{
+public:
+	CGMarket();
+	///IObjectIntercae
+	void onHeroVisit(const CGHeroInstance * h) const override; //open trading window
+
+	///IMarket
+	int getMarketEfficiency() const override;
+	bool allowsTrade(EMarketMode::EMarketMode mode) const override;
+	int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited
+	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+		h & static_cast<IMarket&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGBlackMarket : public CGMarket
+{
+public:
+	std::vector<const CArtifact *> artifacts; //available artifacts
+
+	void newTurn() const override; //reset artifacts for black market every month
+	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGMarket&>(*this);
+		h & artifacts;
+	}
+};
+
+class DLL_LINKAGE CGUniversity : public CGMarket
+{
+public:
+	std::vector<int> skills; //available skills
+
+	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
+	void initObj() override;//set skills for trade
+	void onHeroVisit(const CGHeroInstance * h) const override; //open window
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGMarket&>(*this);
+		h & skills;
+	}
+};

+ 364 - 0
lib/mapObjects/CGPandoraBox.cpp

@@ -0,0 +1,364 @@
+/*
+ * CObjectHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CGPandoraBox.h"
+
+#include "NetPacks.h"
+#include "../client/CSoundBase.h"
+
+#include "../CSpellHandler.h"
+
+using namespace boost::assign;
+
+///helpers
+static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
+{
+	InfoWindow iw;
+	iw.soundID = soundID;
+	iw.player = playerID;
+	iw.text.addTxt(MetaString::ADVOB_TXT,txtID);
+	IObjectInterface::cb->sendAndApply(&iw);
+}
+
+static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID)
+{
+	const PlayerColor playerID = h->getOwner();
+	showInfoDialog(playerID,txtID,soundID);
+}
+
+void CGPandoraBox::initObj()
+{
+	blockVisit = (ID==Obj::PANDORAS_BOX); //block only if it's really pandora's box (events also derive from that class)
+	hasGuardians = stacks.size();
+}
+
+void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const
+{
+		BlockingDialog bd (true, false);
+		bd.player = h->getOwner();
+		bd.soundID = soundBase::QUEST;
+		bd.text.addTxt (MetaString::ADVOB_TXT, 14);
+		cb->showBlockingDialog (&bd);
+}
+
+void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const
+{
+	cb->removeAfterVisit(this);
+
+	InfoWindow iw;
+	iw.player = h->getOwner();
+
+	bool changesPrimSkill = false;
+	for (auto & elem : primskills)
+	{
+		if(elem)
+		{
+			changesPrimSkill = true;
+			break;
+		}
+	}
+
+	if(gainedExp || changesPrimSkill || abilities.size())
+	{
+		TExpType expVal = h->calculateXp(gainedExp);
+		//getText(iw,afterBattle,175,h); //wtf?
+		iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something
+		iw.text.addReplacement(h->name);
+
+		if(expVal)
+			iw.components.push_back(Component(Component::EXPERIENCE,0,expVal,0));
+		for(int i=0; i<primskills.size(); i++)
+			if(primskills[i])
+				iw.components.push_back(Component(Component::PRIM_SKILL,i,primskills[i],0));
+
+		for(int i=0; i<abilities.size(); i++)
+			iw.components.push_back(Component(Component::SEC_SKILL,abilities[i],abilityLevels[i],0));
+
+		cb->showInfoDialog(&iw);
+
+		//give sec skills
+		for(int i=0; i<abilities.size(); i++)
+		{
+			int curLev = h->getSecSkillLevel(abilities[i]);
+
+			if( (curLev  &&  curLev < abilityLevels[i])	|| (h->canLearnSkill() ))
+			{
+				cb->changeSecSkill(h,abilities[i],abilityLevels[i],true);
+			}
+		}
+
+		//give prim skills
+		for(int i=0; i<primskills.size(); i++)
+			if(primskills[i])
+				cb->changePrimSkill(h,static_cast<PrimarySkill::PrimarySkill>(i),primskills[i],false);
+
+		assert(!cb->isVisitCoveredByAnotherQuery(this, h));
+
+		//give exp
+		if(expVal)
+			cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false);
+	}
+
+	if(!cb->isVisitCoveredByAnotherQuery(this, h))
+		giveContentsAfterExp(h);
+	//Otherwise continuation occurs via post-level-up callback.
+}
+
+void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const
+{
+	bool hadGuardians = hasGuardians; //copy, because flag will be emptied after issuing first post-battle message
+
+	std::string msg = message; //in case box is removed in the meantime
+	InfoWindow iw;
+	iw.player = h->getOwner();
+
+	if(spells.size())
+	{
+		std::set<SpellID> spellsToGive;
+		iw.components.clear();
+		if (spells.size() > 1)
+		{
+			iw.text.addTxt(MetaString::ADVOB_TXT, 188); //%s learns spells
+		}
+		else
+		{
+			iw.text.addTxt(MetaString::ADVOB_TXT, 184); //%s learns a spell
+		}
+		iw.text.addReplacement(h->name);
+		std::vector<ConstTransitivePtr<CSpell> > * sp = &VLC->spellh->objects;
+		for(auto i=spells.cbegin(); i != spells.cend(); i++)
+		{
+			if ((*sp)[*i]->level <= h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) //enough wisdom
+			{
+				iw.components.push_back(Component(Component::SPELL,*i,0,0));
+				spellsToGive.insert(*i);
+			}
+		}
+		if(!spellsToGive.empty())
+		{
+			cb->changeSpells(h,true,spellsToGive);
+			cb->showInfoDialog(&iw);
+		}
+	}
+
+	if(manaDiff)
+	{
+		getText(iw,hadGuardians,manaDiff,176,177,h);
+		iw.components.push_back(Component(Component::PRIM_SKILL,5,manaDiff,0));
+		cb->showInfoDialog(&iw);
+		cb->setManaPoints(h->id, h->mana + manaDiff);
+	}
+
+	if(moraleDiff)
+	{
+		getText(iw,hadGuardians,moraleDiff,178,179,h);
+		iw.components.push_back(Component(Component::MORALE,0,moraleDiff,0));
+		cb->showInfoDialog(&iw);
+		GiveBonus gb;
+		gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,moraleDiff,id.getNum(),"");
+		gb.id = h->id.getNum();
+		cb->giveHeroBonus(&gb);
+	}
+
+	if(luckDiff)
+	{
+		getText(iw,hadGuardians,luckDiff,180,181,h);
+		iw.components.push_back(Component(Component::LUCK,0,luckDiff,0));
+		cb->showInfoDialog(&iw);
+		GiveBonus gb;
+		gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,luckDiff,id.getNum(),"");
+		gb.id = h->id.getNum();
+		cb->giveHeroBonus(&gb);
+	}
+
+	iw.components.clear();
+	iw.text.clear();
+	for(int i=0; i<resources.size(); i++)
+	{
+		if(resources[i] < 0)
+			iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0));
+	}
+	if(iw.components.size())
+	{
+		getText(iw,hadGuardians,182,h);
+		cb->showInfoDialog(&iw);
+	}
+
+	iw.components.clear();
+	iw.text.clear();
+	for(int i=0; i<resources.size(); i++)
+	{
+		if(resources[i] > 0)
+			iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0));
+	}
+	if(iw.components.size())
+	{
+		getText(iw,hadGuardians,183,h);
+		cb->showInfoDialog(&iw);
+	}
+
+	iw.components.clear();
+	// 	getText(iw,afterBattle,183,h);
+	iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure
+	iw.text.addReplacement(h->name);
+	for(auto & elem : artifacts)
+	{
+		iw.components.push_back(Component(Component::ARTIFACT,elem,0,0));
+		if(iw.components.size() >= 14)
+		{
+			cb->showInfoDialog(&iw);
+			iw.components.clear();
+			iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - once more?
+			iw.text.addReplacement(h->name);
+		}
+	}
+	if(iw.components.size())
+	{
+		cb->showInfoDialog(&iw);
+	}
+
+	for(int i=0; i<resources.size(); i++)
+		if(resources[i])
+			cb->giveResource(h->getOwner(),static_cast<Res::ERes>(i),resources[i]);
+
+	for(auto & elem : artifacts)
+		cb->giveHeroNewArtifact(h, VLC->arth->artifacts[elem],ArtifactPosition::FIRST_AVAILABLE);
+
+	iw.components.clear();
+	iw.text.clear();
+
+	if (creatures.Slots().size())
+	{ //this part is taken straight from creature bank
+		MetaString loot;
+		for(auto & elem : creatures.Slots())
+		{ //build list of joined creatures
+			iw.components.push_back(Component(*elem.second));
+			loot << "%s";
+			loot.addReplacement(*elem.second);
+		}
+
+		if (creatures.Slots().size() == 1 && creatures.Slots().begin()->second->count == 1)
+			iw.text.addTxt(MetaString::ADVOB_TXT, 185);
+		else
+			iw.text.addTxt(MetaString::ADVOB_TXT, 186);
+
+		iw.text.addReplacement(loot.buildList());
+		iw.text.addReplacement(h->name);
+
+		cb->showInfoDialog(&iw);
+		cb->giveCreatures(this, h, creatures, true);
+	}
+	if(!hasGuardians && msg.size())
+	{
+		iw.text << msg;
+		cb->showInfoDialog(&iw);
+	}
+}
+
+void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const
+{
+	if(afterBattle || !message.size())
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT,text);//%s has lost treasure.
+		iw.text.addReplacement(h->name);
+	}
+	else
+	{
+		iw.text << message;
+		afterBattle = true;
+	}
+}
+
+void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const
+{
+	iw.components.clear();
+	iw.text.clear();
+	if(afterBattle || !message.size())
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases
+		iw.text.addReplacement(h->name);
+	}
+	else
+	{
+		iw.text << message;
+		afterBattle = true;
+	}
+}
+
+void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+	if(result.winner)
+		return;
+
+	giveContentsUpToExp(hero);
+}
+
+void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	if (answer)
+	{
+		if (stacksCount() > 0) //if pandora's box is protected by army
+		{
+			showInfoDialog(hero,16,0);
+			cb->startBattleI(hero, this); //grants things after battle
+		}
+		else if (message.size() == 0 && resources.size() == 0
+			&& primskills.size() == 0 && abilities.size() == 0
+			&& abilityLevels.size() == 0 &&  artifacts.size() == 0
+			&& spells.size() == 0 && creatures.Slots().size() > 0
+			&& gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle
+		{
+			showInfoDialog(hero,15,0);
+			cb->removeObject(this);
+		}
+		else //if it gives something without battle
+		{
+			giveContentsUpToExp(hero);
+		}
+	}
+}
+
+void CGPandoraBox::heroLevelUpDone(const CGHeroInstance *hero) const
+{
+	giveContentsAfterExp(hero);
+}
+
+void CGEvent::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if(!(availableFor & (1 << h->tempOwner.getNum())))
+		return;
+	if(cb->getPlayerSettings(h->tempOwner)->playerID)
+	{
+		if(humanActivate)
+			activated(h);
+	}
+	else if(computerActivate)
+		activated(h);
+}
+
+void CGEvent::activated( const CGHeroInstance * h ) const
+{
+	if(stacksCount() > 0)
+	{
+		InfoWindow iw;
+		iw.player = h->tempOwner;
+		if(message.size())
+			iw.text << message;
+		else
+			iw.text.addTxt(MetaString::ADVOB_TXT, 16);
+		cb->showInfoDialog(&iw);
+		cb->startBattleI(h, this);
+	}
+	else
+	{
+		giveContentsUpToExp(h);
+	}
+}

+ 73 - 0
lib/mapObjects/CGPandoraBox.h

@@ -0,0 +1,73 @@
+#pragma once
+
+#include "CObjectHandler.h"
+#include "CGArmedInstance.h"
+
+
+/*
+ * CObjectHandler.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
+ *
+ */
+
+class DLL_LINKAGE CGPandoraBox : public CArmedInstance
+{
+public:
+	std::string message;
+	bool hasGuardians; //helper - after battle even though we have no stacks, allows us to know that there was battle
+
+	//gained things:
+	ui32 gainedExp;
+	si32 manaDiff; //amount of gained / lost mana
+	si32 moraleDiff; //morale modifier
+	si32 luckDiff; //luck modifier
+	TResources resources;//gained / lost resources
+	std::vector<si32> primskills;//gained / lost prim skills
+	std::vector<SecondarySkill> abilities; //gained abilities
+	std::vector<si32> abilityLevels; //levels of gained abilities
+	std::vector<ArtifactID> artifacts; //gained artifacts
+	std::vector<SpellID> spells; //gained spells
+	CCreatureSet creatures; //gained creatures
+
+	void initObj() override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+	void heroLevelUpDone(const CGHeroInstance *hero) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this);
+		h & message & hasGuardians & gainedExp & manaDiff & moraleDiff & luckDiff & resources & primskills
+			& abilities & abilityLevels & artifacts & spells & creatures;
+	}
+protected:
+	void giveContentsUpToExp(const CGHeroInstance *h) const;
+	void giveContentsAfterExp(const CGHeroInstance *h) const;
+private:
+	void getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const;
+	void getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const;
+};
+
+class DLL_LINKAGE CGEvent : public CGPandoraBox  //event objects
+{
+public:
+	bool removeAfterVisit; //true if event is removed after occurring
+	ui8 availableFor; //players whom this event is available for
+	bool computerActivate; //true if computer player can activate this event
+	bool humanActivate; //true if human player can activate this event
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGPandoraBox &>(*this);
+		h & removeAfterVisit & availableFor & computerActivate & humanActivate;
+	}
+
+	void onHeroVisit(const CGHeroInstance * h) const override;
+private:
+	void activated(const CGHeroInstance * h) const;
+};

+ 1201 - 0
lib/mapObjects/CGTownInstance.cpp

@@ -0,0 +1,1201 @@
+/*
+ * CObjectHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CGTownInstance.h"
+
+#include "NetPacks.h"
+#include "../CGeneralTextHandler.h"
+
+using namespace boost::assign;
+
+std::vector<const CArtifact *> CGTownInstance::merchantArtifacts;
+std::vector<int> CGTownInstance::universitySkills;
+
+void CGDwelling::initObj()
+{
+	switch(ID)
+	{
+	case Obj::CREATURE_GENERATOR1:
+		{
+			CreatureID crid = VLC->objh->cregens[subID];
+			const CCreature *crs = VLC->creh->creatures[crid];
+
+			creatures.resize(1);
+			creatures[0].second.push_back(crid);
+			if (subID >= VLC->generaltexth->creGens.size()) //very messy workaround
+			{
+				auto & dwellingNames = VLC->townh->factions[crs->faction]->town->dwellingNames;
+				assert (dwellingNames.size() > crs->level - 1);
+				hoverName = dwellingNames[crs->level - 1];
+			}
+			else
+				hoverName = VLC->generaltexth->creGens[subID];
+			if(crs->level > 4)
+				putStack(SlotID(0), new CStackInstance(crs, (crs->growth) * 3));
+			if (getOwner() != PlayerColor::NEUTRAL)
+				cb->gameState()->players[getOwner()].dwellings.push_back (this);
+		}
+		break;
+
+	case Obj::CREATURE_GENERATOR4:
+		creatures.resize(4);
+		if(subID == 1) //Golem Factory
+		{
+			creatures[0].second.push_back(CreatureID::STONE_GOLEM);
+			creatures[1].second.push_back(CreatureID::IRON_GOLEM);
+			creatures[2].second.push_back(CreatureID::GOLD_GOLEM);
+			creatures[3].second.push_back(CreatureID::DIAMOND_GOLEM);
+			//guards
+			putStack(SlotID(0), new CStackInstance(CreatureID::GOLD_GOLEM, 9));
+			putStack(SlotID(1), new CStackInstance(CreatureID::DIAMOND_GOLEM, 6));
+		}
+		else if(subID == 0) // Elemental Conflux
+		{
+			creatures[0].second.push_back(CreatureID::AIR_ELEMENTAL);
+			creatures[1].second.push_back(CreatureID::FIRE_ELEMENTAL);
+			creatures[2].second.push_back(CreatureID::EARTH_ELEMENTAL);
+			creatures[3].second.push_back(CreatureID::WATER_ELEMENTAL);
+			//guards
+			putStack(SlotID(0), new CStackInstance(CreatureID::EARTH_ELEMENTAL, 12));
+		}
+		else
+		{
+			assert(0);
+		}
+		hoverName = VLC->generaltexth->creGens4[subID];
+		break;
+
+	case Obj::REFUGEE_CAMP:
+		//is handled within newturn func
+		break;
+
+	case Obj::WAR_MACHINE_FACTORY:
+		creatures.resize(3);
+		creatures[0].second.push_back(CreatureID::BALLISTA);
+		creatures[1].second.push_back(CreatureID::FIRST_AID_TENT);
+		creatures[2].second.push_back(CreatureID::AMMO_CART);
+		break;
+
+	default:
+		assert(0);
+		break;
+	}
+}
+
+void CGDwelling::setProperty(ui8 what, ui32 val)
+{
+	switch (what)
+	{
+		case ObjProperty::OWNER: //change owner
+			if (ID == Obj::CREATURE_GENERATOR1) //single generators
+			{
+				if (tempOwner != PlayerColor::NEUTRAL)
+				{
+					std::vector<ConstTransitivePtr<CGDwelling> >* dwellings = &cb->gameState()->players[tempOwner].dwellings;
+					dwellings->erase (std::find(dwellings->begin(), dwellings->end(), this));
+				}
+				if (PlayerColor(val) != PlayerColor::NEUTRAL) //can new owner be neutral?
+					cb->gameState()->players[PlayerColor(val)].dwellings.push_back (this);
+			}
+			break;
+		case ObjProperty::AVAILABLE_CREATURE:
+			creatures.resize(1);
+			creatures[0].second.resize(1);
+			creatures[0].second[0] = CreatureID(val);
+			break;
+	}
+	CGObjectInstance::setProperty(what,val);
+}
+void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if(ID == Obj::REFUGEE_CAMP && !creatures[0].first) //Refugee Camp, no available cres
+	{
+		InfoWindow iw;
+		iw.player = h->tempOwner;
+		iw.text.addTxt(MetaString::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted.  Perhaps you should try next week.
+		iw.text.addReplacement(MetaString::OBJ_NAMES, ID);
+		cb->sendAndApply(&iw);
+		return;
+	}
+
+	PlayerRelations::PlayerRelations relations = cb->gameState()->getPlayerRelations( h->tempOwner, tempOwner );
+
+	if ( relations == PlayerRelations::ALLIES )
+		return;//do not allow recruiting or capturing
+
+	if( !relations  &&  stacksCount() > 0) //object is guarded, owned by enemy
+	{
+		BlockingDialog bd(true,false);
+		bd.player = h->tempOwner;
+		bd.text.addTxt(MetaString::GENERAL_TXT, 421); //Much to your dismay, the %s is guarded by %s %s. Do you wish to fight the guards?
+		bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID);
+		bd.text.addReplacement(MetaString::ARRAY_TXT, 176 + Slots().begin()->second->getQuantityID()*3);
+		bd.text.addReplacement(*Slots().begin()->second);
+		cb->showBlockingDialog(&bd);
+		return;
+	}
+
+	if(!relations  &&  ID != Obj::WAR_MACHINE_FACTORY)
+	{
+		cb->setOwner(this, h->tempOwner);
+	}
+
+	BlockingDialog bd (true,false);
+	bd.player = h->tempOwner;
+	if(ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR4)
+	{
+		bd.text.addTxt(MetaString::ADVOB_TXT, ID == Obj::CREATURE_GENERATOR1 ? 35 : 36); //{%s} Would you like to recruit %s? / {%s} Would you like to recruit %s, %s, %s, or %s?
+		bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID);
+		for(auto & elem : creatures)
+			bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]);
+	}
+	else if(ID == Obj::REFUGEE_CAMP)
+	{
+		bd.text.addTxt(MetaString::ADVOB_TXT, 35); //{%s} Would you like to recruit %s?
+		bd.text.addReplacement(MetaString::OBJ_NAMES, ID);
+		for(auto & elem : creatures)
+			bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]);
+	}
+	else if(ID == Obj::WAR_MACHINE_FACTORY)
+		bd.text.addTxt(MetaString::ADVOB_TXT, 157); //{War Machine Factory} Would you like to purchase War Machines?
+	else
+		throw std::runtime_error("Illegal dwelling!");
+
+	cb->showBlockingDialog(&bd);
+}
+
+void CGDwelling::newTurn() const
+{
+	if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week
+		return;
+
+	//town growths and War Machines Factories are handled separately
+	if(ID == Obj::TOWN  ||  ID == Obj::WAR_MACHINE_FACTORY)
+		return;
+
+	if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature
+	{
+		cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(cb->gameState()->getRandomGenerator()));
+	}
+
+	bool change = false;
+
+	SetAvailableCreatures sac;
+	sac.creatures = creatures;
+	sac.tid = id;
+	for (size_t i = 0; i < creatures.size(); i++)
+	{
+		if(creatures[i].second.size())
+		{
+			CCreature *cre = VLC->creh->creatures[creatures[i].second[0]];
+			TQuantity amount = cre->growth * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH);
+			if (VLC->modh->settings.DWELLINGS_ACCUMULATE_CREATURES && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures
+				sac.creatures[i].first += amount;
+			else
+				sac.creatures[i].first = amount;
+			change = true;
+		}
+	}
+
+	if(change)
+		cb->sendAndApply(&sac);
+}
+
+void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
+{
+	CreatureID crid = creatures[0].second[0];
+	CCreature *crs = VLC->creh->creatures[crid];
+	TQuantity count = creatures[0].first;
+
+	if(crs->level == 1  &&  ID != Obj::REFUGEE_CAMP) //first level - creatures are for free
+	{
+		if(count) //there are available creatures
+		{
+			SlotID slot = h->getSlotFor(crid);
+			if(!slot.validSlot()) //no available slot
+			{
+				InfoWindow iw;
+				iw.player = h->tempOwner;
+				iw.text.addTxt(MetaString::GENERAL_TXT, 425);//The %s would join your hero, but there aren't enough provisions to support them.
+				iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid);
+				cb->showInfoDialog(&iw);
+			}
+			else //give creatures
+			{
+				SetAvailableCreatures sac;
+				sac.tid = id;
+				sac.creatures = creatures;
+				sac.creatures[0].first = 0;
+
+
+				InfoWindow iw;
+				iw.player = h->tempOwner;
+				iw.text.addTxt(MetaString::GENERAL_TXT, 423); //%d %s join your army.
+				iw.text.addReplacement(count);
+				iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid);
+
+				cb->showInfoDialog(&iw);
+				cb->sendAndApply(&sac);
+				cb->addToSlot(StackLocation(h, slot), crs, count);
+			}
+		}
+		else //there no creatures
+		{
+			InfoWindow iw;
+			iw.text.addTxt(MetaString::GENERAL_TXT, 422); //There are no %s here to recruit.
+			iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid);
+			iw.player = h->tempOwner;
+			cb->sendAndApply(&iw);
+		}
+	}
+	else
+	{
+		if(ID == Obj::WAR_MACHINE_FACTORY) //pick available War Machines
+		{
+			//there is 1 war machine available to recruit if hero doesn't have one
+			SetAvailableCreatures sac;
+			sac.tid = id;
+			sac.creatures = creatures;
+			sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista
+			sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent
+			sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart
+			cb->sendAndApply(&sac);
+		}
+
+		OpenWindow ow;
+		ow.id1 = id.getNum();
+		ow.id2 = h->id.getNum();
+		ow.window = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP)
+			? OpenWindow::RECRUITMENT_FIRST
+			: OpenWindow::RECRUITMENT_ALL;
+		cb->sendAndApply(&ow);
+	}
+}
+
+void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+	if (result.winner == 0)
+	{
+		onHeroVisit(hero);
+	}
+}
+
+void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner());
+	if(stacksCount() > 0  && relations == PlayerRelations::ENEMIES) //guards present
+	{
+		if(answer)
+			cb->startBattleI(hero, this);
+	}
+	else if(answer)
+	{
+		heroAcceptsCreatures(hero);
+	}
+}
+
+int CGTownInstance::getSightRadious() const //returns sight distance
+{
+	if (subID == ETownType::TOWER)
+	{
+		if (hasBuilt(BuildingID::GRAIL)) //skyship
+			return -1; //entire map
+		if (hasBuilt(BuildingID::LOOKOUT_TOWER)) //lookout tower
+			return 20;
+	}
+	return 5;
+}
+
+void CGTownInstance::setPropertyDer(ui8 what, ui32 val)
+{
+///this is freakin' overcomplicated solution
+	switch (what)
+	{
+	case ObjProperty::STRUCTURE_ADD_VISITING_HERO:
+			bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, visitingHero->id.getNum());
+			break;
+		case ObjProperty::STRUCTURE_CLEAR_VISITORS:
+			bonusingBuildings[val]->setProperty (ObjProperty::STRUCTURE_CLEAR_VISITORS, 0);
+			break;
+		case ObjProperty::STRUCTURE_ADD_GARRISONED_HERO: //add garrisoned hero to visitors
+			bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, garrisonHero->id.getNum());
+			break;
+		case ObjProperty::BONUS_VALUE_FIRST:
+			bonusValue.first = val;
+			break;
+		case ObjProperty::BONUS_VALUE_SECOND:
+			bonusValue.second = val;
+			break;
+	}
+}
+CGTownInstance::EFortLevel CGTownInstance::fortLevel() const //0 - none, 1 - fort, 2 - citadel, 3 - castle
+{
+	if (hasBuilt(BuildingID::CASTLE))
+		return CASTLE;
+	if (hasBuilt(BuildingID::CITADEL))
+		return CITADEL;
+	if (hasBuilt(BuildingID::FORT))
+		return FORT;
+	return NONE;
+}
+
+int CGTownInstance::hallLevel() const // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
+{
+	
+	if (hasBuilt(BuildingID::CAPITOL))
+		return 3;
+	if (hasBuilt(BuildingID::CITY_HALL))
+		return 2;
+	if (hasBuilt(BuildingID::TOWN_HALL))
+		return 1;
+	if (hasBuilt(BuildingID::VILLAGE_HALL))
+		return 0;
+	return -1;
+}
+int CGTownInstance::mageGuildLevel() const
+{
+	if (hasBuilt(BuildingID::MAGES_GUILD_5))
+		return 5;
+	if (hasBuilt(BuildingID::MAGES_GUILD_4))
+		return 4;
+	if (hasBuilt(BuildingID::MAGES_GUILD_3))
+		return 3;
+	if (hasBuilt(BuildingID::MAGES_GUILD_2))
+		return 2;
+	if (hasBuilt(BuildingID::MAGES_GUILD_1))
+		return 1;
+	return 0;
+}
+
+int CGTownInstance::getHordeLevel(const int & HID)  const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present
+{
+	return town->hordeLvl.at(HID);
+}
+
+int CGTownInstance::creatureGrowth(const int & level) const
+{
+	return getGrowthInfo(level).totalGrowth();
+}
+
+GrowthInfo CGTownInstance::getGrowthInfo(int level) const
+{
+	GrowthInfo ret;
+
+	if (level<0 || level >=GameConstants::CREATURES_PER_TOWN)
+		return ret;
+	if (creatures[level].second.empty())
+		return ret; //no dwelling
+
+	const CCreature *creature = VLC->creh->creatures[creatures[level].second.back()];
+	const int base = creature->growth;
+	int castleBonus = 0;
+
+	ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[590], base));// \n\nBasic growth %d"
+
+	if (hasBuilt(BuildingID::CASTLE))
+		ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CASTLE, castleBonus = base));
+	else if (hasBuilt(BuildingID::CITADEL))
+		ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CITADEL, castleBonus = base / 2));
+
+	if(town->hordeLvl.at(0) == level)//horde 1
+		if(hasBuilt(BuildingID::HORDE_1))
+			ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_1, creature->hordeGrowth));
+
+	if(town->hordeLvl.at(1) == level)//horde 2
+		if(hasBuilt(BuildingID::HORDE_2))
+			ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_2, creature->hordeGrowth));
+
+	int dwellingBonus = 0;
+	if(const PlayerState *p = cb->getPlayer(tempOwner, false))
+	{
+		for(const CGDwelling *dwelling : p->dwellings)
+			if(vstd::contains(creatures[level].second, dwelling->creatures[0].second[0]))
+				dwellingBonus++;
+	}
+
+	if(dwellingBonus)
+		ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d
+
+	//other *-of-legion-like bonuses (%d to growth cumulative with grail)
+	TBonusListPtr bonuses = getBonuses(Selector::type(Bonus::CREATURE_GROWTH).And(Selector::subtype(level)));
+	for(const Bonus *b : *bonuses)
+		ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val));
+
+	//statue-of-legion-like bonus: % to base+castle
+	TBonusListPtr bonuses2 = getBonuses(Selector::type(Bonus::CREATURE_GROWTH_PERCENT));
+	for(const Bonus *b : *bonuses2)
+		ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val * (base + castleBonus) / 100));
+
+	if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth
+		ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2));
+
+	return ret;
+}
+
+TResources CGTownInstance::dailyIncome() const
+{
+	TResources ret;
+
+	for (auto & p : town->buildings) 
+	{ 
+		BuildingID buildingUpgrade;
+
+		for (auto & p2 : town->buildings) 
+		{ 
+			if (p2.second->upgrade == p.first)
+			{
+				buildingUpgrade = p2.first;
+			}
+		}
+
+		if (!hasBuilt(buildingUpgrade)&&(hasBuilt(p.first)))
+		{
+			ret += p.second->produce;
+		}
+	
+	}
+
+	return ret;
+}
+bool CGTownInstance::hasFort() const
+{
+	return hasBuilt(BuildingID::FORT);
+}
+bool CGTownInstance::hasCapitol() const
+{
+	return hasBuilt(BuildingID::CAPITOL);
+}
+CGTownInstance::CGTownInstance()
+	:IShipyard(this), IMarket(this), town(nullptr), builded(0), destroyed(0), identifier(0), alignment(0xff)
+{
+
+}
+
+CGTownInstance::~CGTownInstance()
+{
+	for (auto & elem : bonusingBuildings)
+		delete elem;
+}
+
+int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const
+{
+	if(checkGuild && mageGuildLevel() < level)
+		return 0;
+	int ret = 6 - level; //how many spells are available at this level
+
+	if (hasBuilt(BuildingID::LIBRARY, ETownType::TOWER))
+		ret++;
+
+	return ret;
+}
+
+bool CGTownInstance::needsLastStack() const
+{
+	if(garrisonHero)
+		return true;
+	else return false;
+}
+
+void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const
+{
+	if( !cb->gameState()->getPlayerRelations( getOwner(), h->getOwner() ))//if this is enemy
+	{
+		if(armedGarrison() || visitingHero)
+		{
+			const CGHeroInstance *defendingHero = nullptr;
+			const CArmedInstance *defendingArmy = this;
+
+			if(visitingHero)
+				defendingHero = visitingHero;
+			else if(garrisonHero)
+				defendingHero = garrisonHero;
+
+			if(defendingHero)
+				defendingArmy = defendingHero;
+
+			bool outsideTown = (defendingHero == visitingHero && garrisonHero);
+
+			//TODO
+			//"borrowing" army from garrison to visiting hero
+
+			cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (outsideTown ? nullptr : this));
+		}
+		else
+		{
+			cb->setOwner(this, h->tempOwner);
+			removeCapitols(h->getOwner());
+			cb->heroVisitCastle(this, h);
+		}
+	}
+	else if(h->visitablePos() == visitablePos())
+	{
+		if (h->commander && !h->commander->alive) //rise commander. TODO: interactive script
+		{
+			SetCommanderProperty scp;
+			scp.heroid = h->id;
+			scp.which = SetCommanderProperty::ALIVE;
+			scp.amount = 1;
+			cb->sendAndApply (&scp);
+		}
+		cb->heroVisitCastle(this, h);
+	}
+	else
+	{
+		logGlobal->errorStream() << h->name << " visits allied town of " << name << " from different pos?";
+	}
+}
+
+void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const
+{
+	cb->stopHeroVisitCastle(this, h);
+}
+
+void CGTownInstance::initObj()
+///initialize town structures
+{
+	blockVisit = true;
+	hoverName = name + ", " + town->faction->name;
+
+	if (subID == ETownType::DUNGEON)
+		creatures.resize(GameConstants::CREATURES_PER_TOWN+1);//extra dwelling for Dungeon
+	else
+		creatures.resize(GameConstants::CREATURES_PER_TOWN);
+	for (int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++)
+	{
+		BuildingID buildID = BuildingID(BuildingID::DWELL_FIRST).advance(level);
+		int upgradeNum = 0;
+
+		for (; town->buildings.count(buildID); upgradeNum++, buildID.advance(GameConstants::CREATURES_PER_TOWN))
+		{
+			if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum)
+				creatures[level].second.push_back(town->creatures[level][upgradeNum]);
+		}
+	}
+
+	switch (subID)
+	{ //add new visitable objects
+		case 0:
+			bonusingBuildings.push_back (new COPWBonus(BuildingID::STABLES, this));
+			break;
+		case 5:
+			bonusingBuildings.push_back (new COPWBonus(BuildingID::MANA_VORTEX, this));
+			//fallthrough
+		case 2: case 3: case 6:
+			bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_4, this));
+			break;
+		case 7:
+			bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_1, this));
+			break;
+	}
+	//add special bonuses from buildings
+
+	recreateBuildingsBonuses();
+}
+
+void CGTownInstance::newTurn() const
+{
+	if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week
+	{
+		auto & rand = cb->gameState()->getRandomGenerator();
+
+		//give resources for Rampart, Mystic Pond
+		if (hasBuilt(BuildingID::MYSTIC_POND, ETownType::RAMPART)
+			&& cb->getDate(Date::DAY) != 1 && (tempOwner < PlayerColor::PLAYER_LIMIT))
+		{
+			int resID = rand.nextInt(2, 5); //bonus to random rare resource
+			resID = (resID==2)?1:resID;
+			int resVal = rand.nextInt(1, 4);//with size 1..4
+			cb->giveResource(tempOwner, static_cast<Res::ERes>(resID), resVal);
+			cb->setObjProperty (id, ObjProperty::BONUS_VALUE_FIRST, resID);
+			cb->setObjProperty (id, ObjProperty::BONUS_VALUE_SECOND, resVal);
+		}
+
+		if ( subID == ETownType::DUNGEON )
+			for (auto & elem : bonusingBuildings)
+		{
+			if ((elem)->ID == BuildingID::MANA_VORTEX)
+				cb->setObjProperty (id, ObjProperty::STRUCTURE_CLEAR_VISITORS, (elem)->id); //reset visitors for Mana Vortex
+		}
+
+		if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns
+			{
+				std::vector<SlotID> nativeCrits; //slots
+				for (auto & elem : Slots())
+				{
+					if (elem.second->type->faction == subID) //native
+					{
+						nativeCrits.push_back(elem.first); //collect matching slots
+					}
+				}
+				if (nativeCrits.size())
+				{
+					SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand);
+					StackLocation sl(this, pos);
+
+					const CCreature *c = getCreature(pos);
+					if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available
+					{
+						cb->changeStackCount(sl, c->growth);
+					}
+					else //upgrade
+					{
+						cb->changeStackType(sl, VLC->creh->creatures[*c->upgrades.begin()]);
+					}
+				}
+				if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack
+				{
+					int i = rand.nextInt(std::min(GameConstants::CREATURES_PER_TOWN, cb->getDate(Date::MONTH) << 1) - 1);
+					if (!town->creatures[i].empty())
+					{
+						CreatureID c = town->creatures[i][0];
+						SlotID n;
+
+						TQuantity count = creatureGrowth(i);
+						if (!count) // no dwelling
+							count = VLC->creh->creatures[c]->growth;
+
+						{//no lower tiers or above current month
+
+							if ((n = getSlotFor(c)).validSlot())
+							{
+								StackLocation sl(this, n);
+								if (slotEmpty(n))
+									cb->insertNewStack(sl, VLC->creh->creatures[c], count);
+								else //add to existing
+									cb->changeStackCount(sl, count);
+							}
+						}
+					}
+				}
+			}
+	}
+}
+
+int3 CGTownInstance::getSightCenter() const
+{
+	return pos - int3(2,0,0);
+}
+
+ui8 CGTownInstance::getPassableness() const
+{
+	if (!armedGarrison())//empty castle - anyone can visit
+		return GameConstants::ALL_PLAYERS;
+	if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit
+		return 0;
+
+	ui8 mask = 0;
+	TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner);
+	for(PlayerColor it : ts->players)
+		mask |= 1<<it.getNum();//allies - add to possible visitors
+
+	return mask;
+}
+
+void CGTownInstance::getOutOffsets( std::vector<int3> &offsets ) const
+{
+	offsets += int3(-1,2,0), int3(-3,2,0);
+}
+
+void CGTownInstance::removeCapitols (PlayerColor owner) const
+{
+	if (hasCapitol()) // search if there's an older capitol
+	{
+		PlayerState* state = cb->gameState()->getPlayer (owner); //get all towns owned by player
+		for (auto i = state->towns.cbegin(); i < state->towns.cend(); ++i)
+		{
+			if (*i != this && (*i)->hasCapitol())
+			{
+				RazeStructures rs;
+				rs.tid = id;
+				rs.bid.insert(BuildingID::CAPITOL);
+				rs.destroyed = destroyed;
+				cb->sendAndApply(&rs);
+				return;
+			}
+		}
+	}
+}
+
+int CGTownInstance::getBoatType() const
+{
+	switch (town->faction->alignment)
+	{
+	case EAlignment::EVIL : return 0;
+	case EAlignment::GOOD : return 1;
+	case EAlignment::NEUTRAL : return 2;
+	}
+	assert(0);
+	return -1;
+}
+
+int CGTownInstance::getMarketEfficiency() const
+{
+	if (!hasBuilt(BuildingID::MARKETPLACE))
+		return 0;
+
+	const PlayerState *p = cb->getPlayer(tempOwner);
+	assert(p);
+
+	int marketCount = 0;
+	for(const CGTownInstance *t : p->towns)
+		if(t->hasBuilt(BuildingID::MARKETPLACE))
+			marketCount++;
+
+	return marketCount;
+}
+
+bool CGTownInstance::allowsTrade(EMarketMode::EMarketMode mode) const
+{
+	switch(mode)
+	{
+	case EMarketMode::RESOURCE_RESOURCE:
+	case EMarketMode::RESOURCE_PLAYER:
+		return hasBuilt(BuildingID::MARKETPLACE);
+
+	case EMarketMode::ARTIFACT_RESOURCE:
+	case EMarketMode::RESOURCE_ARTIFACT:
+		return hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::TOWER)
+		    || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::DUNGEON)
+		    || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::CONFLUX);
+
+	case EMarketMode::CREATURE_RESOURCE:
+		return hasBuilt(BuildingID::FREELANCERS_GUILD, ETownType::STRONGHOLD);
+
+	case EMarketMode::CREATURE_UNDEAD:
+		return hasBuilt(BuildingID::SKELETON_TRANSFORMER, ETownType::NECROPOLIS);
+
+	case EMarketMode::RESOURCE_SKILL:
+		return hasBuilt(BuildingID::MAGIC_UNIVERSITY, ETownType::CONFLUX);
+	default:
+		assert(0);
+		return false;
+	}
+}
+
+std::vector<int> CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode) const
+{
+	if(mode == EMarketMode::RESOURCE_ARTIFACT)
+	{
+		std::vector<int> ret;
+		for(const CArtifact *a : merchantArtifacts)
+			if(a)
+				ret.push_back(a->id);
+			else
+				ret.push_back(-1);
+		return ret;
+	}
+	else if ( mode == EMarketMode::RESOURCE_SKILL )
+	{
+		return universitySkills;
+	}
+	else
+		return IMarket::availableItemsIds(mode);
+}
+
+void CGTownInstance::setType(si32 ID, si32 subID)
+{
+	assert(ID == Obj::TOWN); // just in case
+	CGObjectInstance::setType(ID, subID);
+	town = VLC->townh->factions[subID]->town;
+	randomizeArmy(subID);
+	updateAppearance();
+}
+
+void CGTownInstance::updateAppearance()
+{
+	if (!hasFort())
+		appearance.animationFile = town->clientInfo.advMapVillage;
+	else if(hasCapitol())
+		appearance.animationFile = town->clientInfo.advMapCapitol;
+	else
+		appearance.animationFile = town->clientInfo.advMapCastle;
+}
+
+std::string CGTownInstance::nodeName() const
+{
+	return "Town (" + (town ? town->faction->name : "unknown") + ") of " +  name;
+}
+
+void CGTownInstance::deserializationFix()
+{
+	attachTo(&townAndVis);
+
+	//Hero is already handled by CGameState::attachArmedObjects
+
+// 	if(visitingHero)
+// 		visitingHero->attachTo(&townAndVis);
+// 	if(garrisonHero)
+// 		garrisonHero->attachTo(this);
+}
+
+void CGTownInstance::updateMoraleBonusFromArmy()
+{
+	Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE)));
+	if(!b)
+	{
+		b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1);
+		addNewBonus(b);
+	}
+
+	if (garrisonHero)
+		b->val = 0;
+	else
+		CArmedInstance::updateMoraleBonusFromArmy();
+}
+
+void CGTownInstance::recreateBuildingsBonuses()
+{
+	static TPropagatorPtr playerProp(new CPropagatorNodeType(PLAYER));
+
+	BonusList bl;
+	getExportedBonusList().getBonuses(bl, Selector::sourceType(Bonus::TOWN_STRUCTURE));
+	for(Bonus *b : bl)
+		removeBonus(b);
+
+	//tricky! -> checks tavern only if no bratherhood of sword or not a castle
+	if(subID != ETownType::CASTLE || !addBonusIfBuilt(BuildingID::BROTHERHOOD, Bonus::MORALE, +2))
+		addBonusIfBuilt(BuildingID::TAVERN, Bonus::MORALE, +1);
+
+	if(subID == ETownType::CASTLE) //castle
+	{
+		addBonusIfBuilt(BuildingID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp);
+		addBonusIfBuilt(BuildingID::GRAIL,      Bonus::MORALE, +2, playerProp); //colossus
+	}
+	else if(subID == ETownType::RAMPART) //rampart
+	{
+		addBonusIfBuilt(BuildingID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune
+		addBonusIfBuilt(BuildingID::GRAIL, Bonus::LUCK, +2, playerProp); //guardian spirit
+	}
+	else if(subID == ETownType::TOWER) //tower
+	{
+		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail
+	}
+	else if(subID == ETownType::INFERNO) //Inferno
+	{
+		addBonusIfBuilt(BuildingID::STORMCLOUDS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER); //Brimstone Clouds
+	}
+	else if(subID == ETownType::NECROPOLIS) //necropolis
+	{
+		addBonusIfBuilt(BuildingID::COVER_OF_DARKNESS,    Bonus::DARKNESS, +20);
+		addBonusIfBuilt(BuildingID::NECROMANCY_AMPLIFIER, Bonus::SECONDARY_SKILL_PREMY, +10, playerProp, SecondarySkill::NECROMANCY); //necromancy amplifier
+		addBonusIfBuilt(BuildingID::GRAIL, Bonus::SECONDARY_SKILL_PREMY, +20, playerProp, SecondarySkill::NECROMANCY); //Soul prison
+	}
+	else if(subID == ETownType::DUNGEON) //Dungeon
+	{
+		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail
+	}
+	else if(subID == ETownType::STRONGHOLD) //Stronghold
+	{
+		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail
+	}
+	else if(subID == ETownType::FORTRESS) //Fortress
+	{
+		addBonusIfBuilt(BuildingID::GLYPHS_OF_FEAR, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE); //Glyphs of Fear
+		addBonusIfBuilt(BuildingID::BLOOD_OBELISK,  Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK); //Blood Obelisk
+		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail
+		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail
+	}
+	else if(subID == ETownType::CONFLUX)
+	{
+
+	}
+}
+
+bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype /*= -1*/)
+{
+	static auto emptyPropagator = TPropagatorPtr();
+	return addBonusIfBuilt(building, type, val, emptyPropagator, subtype);
+}
+
+bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype /*= -1*/)
+{
+	if(hasBuilt(building))
+	{
+		std::ostringstream descr;
+		descr << town->buildings.at(building)->Name() << " ";
+		if(val > 0)
+			descr << "+";
+		else if(val < 0)
+			descr << "-";
+		descr << val;
+
+		Bonus *b = new Bonus(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, descr.str(), subtype);
+		if(prop)
+			b->addPropagator(prop);
+		addNewBonus(b);
+		return true;
+	}
+
+	return false;
+}
+
+void CGTownInstance::setVisitingHero(CGHeroInstance *h)
+{
+	assert(!!visitingHero == !h);
+	if(h)
+	{
+		PlayerState *p = cb->gameState()->getPlayer(h->tempOwner);
+		assert(p);
+		h->detachFrom(p);
+		h->attachTo(&townAndVis);
+		visitingHero = h;
+		h->visitedTown = this;
+		h->inTownGarrison = false;
+	}
+	else
+	{
+		PlayerState *p = cb->gameState()->getPlayer(visitingHero->tempOwner);
+		visitingHero->visitedTown = nullptr;
+		visitingHero->detachFrom(&townAndVis);
+		visitingHero->attachTo(p);
+		visitingHero = nullptr;
+	}
+}
+
+void CGTownInstance::setGarrisonedHero(CGHeroInstance *h)
+{
+	assert(!!garrisonHero == !h);
+	if(h)
+	{
+		PlayerState *p = cb->gameState()->getPlayer(h->tempOwner);
+		assert(p);
+		h->detachFrom(p);
+		h->attachTo(this);
+		garrisonHero = h;
+		h->visitedTown = this;
+		h->inTownGarrison = true;
+	}
+	else
+	{
+		PlayerState *p = cb->gameState()->getPlayer(garrisonHero->tempOwner);
+		garrisonHero->visitedTown = nullptr;
+		garrisonHero->inTownGarrison = false;
+		garrisonHero->detachFrom(this);
+		garrisonHero->attachTo(p);
+		garrisonHero = nullptr;
+	}
+	updateMoraleBonusFromArmy(); //avoid giving morale bonus for same army twice
+}
+
+bool CGTownInstance::armedGarrison() const
+{
+	return stacksCount() || garrisonHero;
+}
+
+int CGTownInstance::getTownLevel() const
+{
+	// count all buildings that are not upgrades
+	return boost::range::count_if(builtBuildings, [&](const BuildingID & build)
+	{
+		return town->buildings.at(build) && town->buildings.at(build)->upgrade == -1;
+	});
+}
+
+CBonusSystemNode * CGTownInstance::whatShouldBeAttached()
+{
+	return &townAndVis;
+}
+
+const CArmedInstance * CGTownInstance::getUpperArmy() const
+{
+	if(garrisonHero)
+		return garrisonHero;
+	return this;
+}
+
+bool CGTownInstance::hasBuilt(BuildingID buildingID, int townID) const
+{
+	if (townID == town->faction->index || townID == ETownType::ANY)
+		return hasBuilt(buildingID);
+	return false;
+}
+
+bool CGTownInstance::hasBuilt(BuildingID buildingID) const
+{
+	return vstd::contains(builtBuildings, buildingID);
+}
+
+void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 structureInstanceID ) const
+{
+	if(visitingHero == h)
+		cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_VISITING_HERO, structureInstanceID); //add to visitors
+	else if(garrisonHero == h)
+		cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_GARRISONED_HERO, structureInstanceID); //then it must be garrisoned hero
+	else
+	{
+		//should never ever happen
+        logGlobal->errorStream() << "Cannot add hero " << h->name << " to visitors of structure #" << structureInstanceID;
+		assert(0);
+	}
+}
+
+void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+	if(result.winner == 0)
+	{
+		removeCapitols(hero->getOwner());
+		cb->setOwner (this, hero->tempOwner); //give control after checkout is done
+		FoWChange fw;
+		fw.player = hero->tempOwner;
+		fw.mode = 1;
+		getSightTiles (fw.tiles); //update visibility for castle structures
+		cb->sendAndApply (&fw);
+	}
+}
+
+COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN)
+{
+	ID = index;
+	town = TOWN;
+	id = town->bonusingBuildings.size();
+}
+void COPWBonus::setProperty(ui8 what, ui32 val)
+{
+	switch (what)
+	{
+		case ObjProperty::VISITORS:
+			visitors.insert(val);
+			break;
+		case ObjProperty::STRUCTURE_CLEAR_VISITORS:
+			visitors.clear();
+			break;
+	}
+}
+void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
+{
+	ObjectInstanceID heroID = h->id;
+	if (town->hasBuilt(ID))
+	{
+		InfoWindow iw;
+		iw.player = h->tempOwner;
+		switch (town->subID)
+		{
+			case ETownType::CASTLE: //Stables
+				if (!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables
+				{
+					GiveBonus gb;
+					gb.bonus = Bonus(Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, Bonus::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100]);
+					gb.id = heroID.getNum();
+					cb->giveHeroBonus(&gb);
+					iw.text << VLC->generaltexth->allTexts[580];
+					cb->showInfoDialog(&iw);
+				}
+				break;
+			case ETownType::DUNGEON: //Mana Vortex
+				if (visitors.empty() && h->mana <= h->manaLimit() * 2)
+				{
+					cb->setManaPoints (heroID, 2 * h->manaLimit());
+					//TODO: investigate line below
+					//cb->setObjProperty (town->id, ObjProperty::VISITED, true);
+					iw.text << VLC->generaltexth->allTexts[579];
+					cb->showInfoDialog(&iw);
+					town->addHeroToStructureVisitors(h, id);
+				}
+				break;
+		}
+	}
+}
+CTownBonus::CTownBonus (BuildingID index, CGTownInstance *TOWN)
+{
+	ID = index;
+	town = TOWN;
+	id = town->bonusingBuildings.size();
+}
+void CTownBonus::setProperty (ui8 what, ui32 val)
+{
+	if(what == ObjProperty::VISITORS)
+		visitors.insert(ObjectInstanceID(val));
+}
+void CTownBonus::onHeroVisit (const CGHeroInstance * h) const
+{
+	ObjectInstanceID heroID = h->id;
+	if (town->hasBuilt(ID) && visitors.find(heroID) == visitors.end())
+	{
+		InfoWindow iw;
+		PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK;
+		int val=0, mid=0;
+		switch (ID)
+		{
+			case BuildingID::SPECIAL_4:
+				switch(town->subID)
+				{
+					case ETownType::TOWER: //wall
+						what = PrimarySkill::KNOWLEDGE;
+						val = 1;
+						mid = 581;
+						iw.components.push_back (Component(Component::PRIM_SKILL, 3, 1, 0));
+						break;
+					case ETownType::INFERNO: //order of fire
+						what = PrimarySkill::SPELL_POWER;
+						val = 1;
+						mid = 582;
+						iw.components.push_back (Component(Component::PRIM_SKILL, 2, 1, 0));
+						break;
+					case ETownType::STRONGHOLD://hall of Valhalla
+						what = PrimarySkill::ATTACK;
+						val = 1;
+						mid = 584;
+						iw.components.push_back (Component(Component::PRIM_SKILL, 0, 1, 0));
+						break;
+					case ETownType::DUNGEON://academy of battle scholars
+						what = PrimarySkill::EXPERIENCE;
+						val = h->calculateXp(1000);
+						mid = 583;
+						iw.components.push_back (Component(Component::EXPERIENCE, 0, val, 0));
+						break;
+				}
+				break;
+			case BuildingID::SPECIAL_1:
+				switch(town->subID)
+				{
+					case ETownType::FORTRESS: //cage of warlords
+						what = PrimarySkill::DEFENSE;
+						val = 1;
+						mid = 585;
+						iw.components.push_back (Component(Component::PRIM_SKILL, 1, 1, 0));
+						break;
+				}
+				break;
+		}
+		assert(mid);
+		iw.player = cb->getOwner(heroID);
+		iw.text << VLC->generaltexth->allTexts[mid];
+		cb->showInfoDialog(&iw);
+		cb->changePrimSkill (cb->getHero(heroID), what, val);
+		town->addHeroToStructureVisitors(h, id);
+	}
+}
+
+GrowthInfo::Entry::Entry(const std::string &format, int _count)
+	: count(_count)
+{
+	description = boost::str(boost::format(format) % count);
+}
+
+GrowthInfo::Entry::Entry(int subID, BuildingID building, int _count)
+	: count(_count)
+{
+	description = boost::str(boost::format("%s %+d") % VLC->townh->factions[subID]->town->buildings.at(building)->Name() % count);
+}
+
+CTownAndVisitingHero::CTownAndVisitingHero()
+{
+	setNodeType(TOWN_AND_VISITOR);
+}
+
+int GrowthInfo::totalGrowth() const
+{
+	int ret = 0;
+	for(const Entry &entry : entries)
+		ret += entry.count;
+
+	return ret;
+}

+ 248 - 0
lib/mapObjects/CGTownInstance.h

@@ -0,0 +1,248 @@
+#pragma once
+
+#include "CObjectHandler.h"
+#include "CGMarket.h" // For IMarket interface
+#include "CGArmedInstance.h"
+
+#include "../CTownHandler.h" // For CTown
+
+/*
+ * CObjectHandler.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
+ *
+ */
+
+class CCastleEvent;
+
+class DLL_LINKAGE CSpecObjInfo
+{
+public:
+	virtual ~CSpecObjInfo(){};
+	PlayerColor player; //owner
+};
+
+class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo
+{
+public:
+	bool asCastle;
+	ui32 identifier;
+	ui8 castles[2]; //allowed castles
+};
+
+class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo
+{
+public:
+	ui8 minLevel, maxLevel; //minimal and maximal level of creature in dwelling: <0, 6>
+};
+
+class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo
+{
+};
+
+class DLL_LINKAGE CGDwelling : public CArmedInstance
+{
+public:
+	typedef std::vector<std::pair<ui32, std::vector<CreatureID> > > TCreaturesSet;
+
+	CSpecObjInfo * info; //h3m info about dewlling
+	TCreaturesSet creatures; //creatures[level] -> <vector of alternative ids (base creature and upgrades, creatures amount>
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this) & creatures;
+	}
+
+	void initObj() override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void newTurn() const override;
+	void setProperty(ui8 what, ui32 val) override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+
+private:
+	void heroAcceptsCreatures(const CGHeroInstance *h) const;
+};
+
+class DLL_LINKAGE CGTownBuilding : public IObjectInterface
+{
+///basic class for town structures handled as map objects
+public:
+	BuildingID ID; //from buildig list
+	si32 id; //identifies its index on towns vector
+	CGTownInstance *town;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & ID & id;
+	}
+};
+class DLL_LINKAGE COPWBonus : public CGTownBuilding
+{///used for OPW bonusing structures
+public:
+	std::set<si32> visitors;
+	void setProperty(ui8 what, ui32 val) override;
+	void onHeroVisit (const CGHeroInstance * h) const override;
+
+	COPWBonus (BuildingID index, CGTownInstance *TOWN);
+	COPWBonus (){ID = BuildingID::NONE; town = nullptr;};
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGTownBuilding&>(*this);
+		h & visitors;
+	}
+};
+
+class DLL_LINKAGE CTownBonus : public CGTownBuilding
+{
+///used for one-time bonusing structures
+///feel free to merge inheritance tree
+public:
+	std::set<ObjectInstanceID> visitors;
+	void setProperty(ui8 what, ui32 val) override;
+	void onHeroVisit (const CGHeroInstance * h) const override;
+
+	CTownBonus (BuildingID index, CGTownInstance *TOWN);
+	CTownBonus (){ID = BuildingID::NONE; town = nullptr;};
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGTownBuilding&>(*this);
+		h & visitors;
+	}
+};
+
+class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode
+{
+public:
+	CTownAndVisitingHero();
+};
+
+struct DLL_LINKAGE GrowthInfo
+{
+	struct Entry
+	{
+		int count;
+		std::string description;
+		Entry(const std::string &format, int _count);
+		Entry(int subID, BuildingID building, int _count);
+	};
+
+	std::vector<Entry> entries;
+	int totalGrowth() const;
+};
+
+class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket
+{
+public:
+	enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3};
+
+	CTownAndVisitingHero townAndVis;
+	const CTown * town;
+	std::string name; // name of town
+	si32 builded; //how many buildings has been built this turn
+	si32 destroyed; //how many buildings has been destroyed this turn
+	ConstTransitivePtr<CGHeroInstance> garrisonHero, visitingHero;
+	ui32 identifier; //special identifier from h3m (only > RoE maps)
+	si32 alignment;
+	std::set<BuildingID> forbiddenBuildings, builtBuildings;
+	std::vector<CGTownBuilding*> bonusingBuildings;
+	std::vector<SpellID> possibleSpells, obligatorySpells;
+	std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
+	std::list<CCastleEvent> events;
+	std::pair<si32, si32> bonusValue;//var to store town bonuses (rampart = resources from mystic pond);
+
+	//////////////////////////////////////////////////////////////////////////
+	static std::vector<const CArtifact *> merchantArtifacts; //vector of artifacts available at Artifact merchant, NULLs possible (for making empty space when artifact is bought)
+	static std::vector<int> universitySkills;//skills for university of magic
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGDwelling&>(*this);
+		h & static_cast<IShipyard&>(*this);
+		h & static_cast<IMarket&>(*this);
+		h & name & builded & destroyed & identifier;
+		h & garrisonHero & visitingHero;
+		h & alignment & forbiddenBuildings & builtBuildings & bonusValue
+			& possibleSpells & obligatorySpells & spells & /*strInfo & */events & bonusingBuildings;
+
+		for (std::vector<CGTownBuilding*>::iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++)
+			(*i)->town = this;
+
+		h & town & townAndVis;
+		BONUS_TREE_DESERIALIZATION_FIX
+
+		vstd::erase_if(builtBuildings, [this](BuildingID building) -> bool
+		{
+			if(!town->buildings.count(building) ||  !town->buildings.at(building))
+			{
+				logGlobal->errorStream() << boost::format("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s")
+					% name % pos % building;
+				return true;
+			}
+			return false;
+		});
+	}
+	//////////////////////////////////////////////////////////////////////////
+
+	CBonusSystemNode *whatShouldBeAttached() override;
+	std::string nodeName() const override;
+	void updateMoraleBonusFromArmy() override;
+	void deserializationFix();
+	void recreateBuildingsBonuses();
+	bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr &prop, int subtype = -1); //returns true if building is built and bonus has been added
+	bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype = -1); //convienence version of above
+	void setVisitingHero(CGHeroInstance *h);
+	void setGarrisonedHero(CGHeroInstance *h);
+	const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself
+
+	//////////////////////////////////////////////////////////////////////////
+
+	ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used
+	int3 getSightCenter() const override; //"center" tile from which the sight distance is calculated
+	int getSightRadious() const override; //returns sight distance
+	int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral
+	void getOutOffsets(std::vector<int3> &offsets) const; //offsets to obj pos when we boat can be placed
+	int getMarketEfficiency() const override; //=market count
+	bool allowsTrade(EMarketMode::EMarketMode mode) const;
+	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
+
+	void setType(si32 ID, si32 subID);
+	void updateAppearance();
+
+	//////////////////////////////////////////////////////////////////////////
+
+	bool needsLastStack() const;
+	CGTownInstance::EFortLevel fortLevel() const;
+	int hallLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
+	int mageGuildLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
+	int getHordeLevel(const int & HID) const; //HID - 0 or 1; returns creature level or -1 if that horde structure is not present
+	int creatureGrowth(const int & level) const;
+	GrowthInfo getGrowthInfo(int level) const;
+	bool hasFort() const;
+	bool hasCapitol() const;
+	//checks if building is constructed and town has same subID
+	bool hasBuilt(BuildingID buildingID) const;
+	bool hasBuilt(BuildingID buildingID, int townID) const;
+	TResources dailyIncome() const; //calculates daily income of this town
+	int spellsAtLevel(int level, bool checkGuild) const; //levels are counted from 1 (1 - 5)
+	bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero
+	int getTownLevel() const;
+
+	void removeCapitols (PlayerColor owner) const;
+	void addHeroToStructureVisitors(const CGHeroInstance *h, si32 structureInstanceID) const; //hero must be visiting or garrisoned in town
+
+	CGTownInstance();
+	virtual ~CGTownInstance();
+
+	///IObjectInterface overrides
+	void newTurn() const override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void onHeroLeave(const CGHeroInstance * h) const override;
+	void initObj() override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+protected:
+	void setPropertyDer(ui8 what, ui32 val) override;
+};

+ 1 - 1
lib/mapObjects/CObjectClassesHandler.cpp

@@ -7,11 +7,11 @@
 #include "GameConstants.h"
 #include "StringConstants.h"
 #include "CGeneralTextHandler.h"
-#include "CObjectHandler.h"
 #include "CModHandler.h"
 #include "JsonNode.h"
 
 #include "CRewardableConstructor.h"
+#include "MapObjects.h"
 
 /*
  * CObjectClassesHandler.cpp, part of VCMI engine

+ 4 - 3
lib/mapObjects/CObjectClassesHandler.h

@@ -1,10 +1,11 @@
 #pragma once
 
-#include "GameConstants.h"
-#include "../lib/ConstTransitivePtr.h"
-#include "IHandlerBase.h"
 #include "ObjectTemplate.h"
 
+#include "../GameConstants.h"
+#include "../ConstTransitivePtr.h"
+#include "../IHandlerBase.h"
+
 /*
  * CObjectClassesHandler.h, part of VCMI engine
  *

+ 60 - 6105
lib/mapObjects/CObjectHandler.cpp

@@ -11,38 +11,16 @@
 #include "StdInc.h"
 #include "CObjectHandler.h"
 
-#include "CObjectClassesHandler.h"
+#include "NetPacks.h"
 #include "CGeneralTextHandler.h"
-#include "CObjectClassesHandler.h"
 #include "CHeroHandler.h"
-#include "CSpellHandler.h"
-#include "CModHandler.h"
 #include "../client/CSoundBase.h"
-#include "CTownHandler.h"
-#include "CCreatureHandler.h"
-#include "VCMI_Lib.h"
-#include "IGameCallback.h"
-#include "CGameState.h"
-#include "NetPacks.h"
-#include "StartInfo.h"
-#include "mapping/CMap.h"
-#include <SDL_stdinc.h>
-#include "CBuildingHandler.h"
-#include "JsonNode.h"
-#include "filesystem/Filesystem.h"
+
+#include "CObjectClassesHandler.h"
 
 using namespace boost::assign;
 
-std::map<Obj, std::map<int, std::vector<ObjectInstanceID> > > CGTeleport::objs;
-std::vector<std::pair<ObjectInstanceID, ObjectInstanceID> > CGTeleport::gates;
 IGameCallback * IObjectInterface::cb = nullptr;
-std::map <PlayerColor, std::set <ui8> > CGKeys::playerKeyMap;
-std::map <si32, std::vector<ObjectInstanceID> > CGMagi::eyelist;
-ui8 CGObelisk::obeliskCount; //how many obelisks are on map
-std::map<TeamID, ui8> CGObelisk::visited; //map: team_id => how many obelisks has been visited
-
-std::vector<const CArtifact *> CGTownInstance::merchantArtifacts;
-std::vector<int> CGTownInstance::universitySkills;
 
 ///helpers
 static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1)
@@ -119,45 +97,16 @@ void IObjectInterface::preInit()
 {}
 
 void IObjectInterface::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-
-}
+{}
 
 void IObjectInterface::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-
-}
+{}
 
 void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const
-{
-
-}
+{}
 
 void IObjectInterface::heroLevelUpDone(const CGHeroInstance *hero) const
-{
-
-}
-
-void CPlayersVisited::setPropertyDer( ui8 what, ui32 val )
-{
-	if(what == 10)
-		players.insert(PlayerColor(val));
-}
-
-bool CPlayersVisited::wasVisited( PlayerColor player ) const
-{
-	return vstd::contains(players,player);
-}
-
-bool CPlayersVisited::wasVisited( TeamID team ) const
-{
-	for(auto i : players)
-	{
-		if(cb->getPlayer(i)->team == team)
-			return true;
-	}
-	return false;
-}
+{}
 
 // Bank helper. Find the creature ID and their number, and store the
 // result in storage (either guards or reward creatures).
@@ -556,6096 +505,102 @@ bool CGObjectInstanceBySubIdFinder::operator()(CGObjectInstance * obj) const
 	return this->obj->subID == obj->subID;
 }
 
-static int lowestSpeed(const CGHeroInstance * chi)
-{
-	if(!chi->Slots().size())
-	{
-        logGlobal->errorStream() << "Error! Hero " << chi->id.getNum() << " ("<<chi->name<<") has no army!";
-		return 20;
-	}
-	auto i = chi->Slots().begin();
-	//TODO? should speed modifiers (eg from artifacts) affect hero movement?
-	int ret = (i++)->second->valOfBonuses(Bonus::STACKS_SPEED);
-	for (;i!=chi->Slots().end();i++)
-	{
-		ret = std::min(ret, i->second->valOfBonuses(Bonus::STACKS_SPEED));
-	}
-	return ret;
-}
-
-ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const
-{
-	//base move cost
-	unsigned ret = 100;
-
-	//if there is road both on dest and src tiles - use road movement cost
-    if(dest.roadType != ERoadType::NO_ROAD && from.roadType != ERoadType::NO_ROAD)
-	{
-        int road = std::min(dest.roadType,from.roadType); //used road ID
-		switch(road)
-		{
-        case ERoadType::DIRT_ROAD:
-			ret = 75;
-			break;
-        case ERoadType::GRAVEL_ROAD:
-			ret = 65;
-			break;
-        case ERoadType::COBBLESTONE_ROAD:
-			ret = 50;
-			break;
-		default:
-            logGlobal->errorStream() << "Unknown road type: " << road << "... Something wrong!";
-			break;
-		}
-	}
-	else
-	{
-		//FIXME: in H3 presence of Nomad in army will remove terrain penalty for sand. Bonus not implemented in VCMI
-
-		// NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army.
-		// This is clearly bug in H3 however intended behaviour is not clear.
-		// Current VCMI behaviour will ignore neutrals in calculations so army in VCMI
-		// will always have best penalty without any influence from player-defined stacks order
-
-		bool nativeArmy = true;
-		for(auto stack : stacks)
-		{
-			int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain;
-
-            if (nativeTerrain != -1 && nativeTerrain != from.terType)
-			{
-				nativeArmy = false;
-				break;
-			}
-		}
-		if (!nativeArmy)
-            ret = VLC->heroh->terrCosts[from.terType];
- 	}
-	return ret;
-}
-
-int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
-{
-	if (toh3m)
-	{
-		src.x+=1;
-		return src;
-	}
-	else
-	{
-		src.x-=1;
-		return src;
-	}
-}
-int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation'
-{
-	if (h3m)
-	{
-		return pos;
-	}
-	else
-	{
-		return convertPosition(pos,false);
-	}
-}
-
-bool CGHeroInstance::canWalkOnSea() const
-{
-	return hasBonusOfType(Bonus::FLYING_MOVEMENT) || hasBonusOfType(Bonus::WATER_WALKING);
-}
-
-ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const
+int3 IBoatGenerator::bestLocation() const
 {
-	for(auto & elem : secSkills)
-		if(elem.first == skill)
-			return elem.second;
-	return 0;
-}
+	std::vector<int3> offsets;
+	getOutOffsets(offsets);
 
-void CGHeroInstance::setSecSkillLevel(SecondarySkill which, int val, bool abs)
-{
-	if(getSecSkillLevel(which) == 0)
-	{
-		secSkills.push_back(std::pair<SecondarySkill,ui8>(which, val));
-		updateSkill(which, val);
-	}
-	else
+	for (auto & offset : offsets)
 	{
-		for (auto & elem : secSkills)
+		if (const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map
 		{
-			if(elem.first == which)
-			{
-				if(abs)
-					elem.second = val;
-				else
-					elem.second += val;
-
-				if(elem.second > 3) //workaround to avoid crashes when same sec skill is given more than once
-				{
-                    logGlobal->warnStream() << "Warning: Skill " << which << " increased over limit! Decreasing to Expert.";
-					elem.second = 3;
-				}
-				updateSkill(which, elem.second); //when we know final value
-			}
+			if (tile->terType == ETerrainType::WATER  &&  (!tile->blocked || tile->blockingObjects.front()->ID == 8)) //and is water and is not blocked or is blocked by boat
+				return o->pos + offset;
 		}
 	}
+	return int3 (-1,-1,-1);
 }
 
-bool CGHeroInstance::canLearnSkill() const
-{
-	return secSkills.size() < GameConstants::SKILL_PER_HERO;
-}
-
-int CGHeroInstance::maxMovePoints(bool onLand) const
+IBoatGenerator::EGeneratorState IBoatGenerator::shipyardStatus() const
 {
-	int base;
-
-	if(onLand)
-	{
-		// used function is f(x) = 66.6x + 1300, rounded to second digit, where x is lowest speed in army
-		static const int baseSpeed = 1300; // base speed from creature with 0 speed
-
-		int armySpeed = lowestSpeed(this) * 20 / 3;
-
-		base = armySpeed * 10 + baseSpeed; // separate *10 is intentional to receive same rounding as in h3
-		vstd::abetween(base, 1500, 2000); // base speed is limited by these values
-	}
+	int3 tile = bestLocation();
+	const TerrainTile *t = IObjectInterface::cb->getTile(tile);
+	if(!t)
+		return TILE_BLOCKED; //no available water
+	else if(!t->blockingObjects.size())
+		return GOOD; //OK
+	else if(t->blockingObjects.front()->ID == Obj::BOAT)
+		return BOAT_ALREADY_BUILT; //blocked with boat
 	else
-	{
-		base = 1500; //on water base movement is always 1500 (speed of army doesn't matter)
-	}
-
-	const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT;
-	const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt);
-
-	const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION;
-	const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0;
-
-	return int(base* (1+modifier)) + bonus;
-}
-
-CGHeroInstance::CGHeroInstance()
- : IBoatGenerator(this)
-{
-	setNodeType(HERO);
-	ID = Obj::HERO;
-	tacticFormationEnabled = inTownGarrison = false;
-	mana = movement = portrait = level = -1;
-	isStanding = true;
-	moveDir = 4;
-	exp = 0xffffffff;
-	visitedTown = nullptr;
-	type = nullptr;
-	boat = nullptr;
-	commander = nullptr;
-	sex = 0xff;
-	secSkills.push_back(std::make_pair(SecondarySkill::DEFAULT, -1));
+		return TILE_BLOCKED; //blocked
 }
 
-void CGHeroInstance::initHero(HeroTypeID SUBID)
+int IBoatGenerator::getBoatType() const
 {
-	subID = SUBID.getNum();
-	initHero();
+	//We make good ships by default
+	return 1;
 }
 
-void CGHeroInstance::setType(si32 ID, si32 subID)
-{
-	assert(ID == Obj::HERO); // just in case
-	CGObjectInstance::setType(ID, subID);
-	type = VLC->heroh->heroes[subID];
-	portrait = type->imageIndex;
-	randomizeArmy(type->heroClass->faction);
-}
 
-void CGHeroInstance::initHero()
+IBoatGenerator::IBoatGenerator(const CGObjectInstance *O)
+: o(O)
 {
-	assert(validTypes(true));
-	if(!type)
-		type = VLC->heroh->heroes[subID];
-
-	if (ID == Obj::HERO)
-		appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->id)->getTemplates().front();
-
-	if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell
-	{
-		for(auto spellID : type->spells)
-			spells.insert(spellID);
-	}
-	else //remove placeholder
-		spells -= SpellID::PRESET;
-
-	if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) //no catapult means we haven't read pre-existent set -> use default rules for spellbook
-		putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0));
-
-	if(!getArt(ArtifactPosition::MACH4))
-		putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createNewArtifactInstance(3)); //everyone has a catapult
-
-	if(portrait < 0 || portrait == 255)
-		portrait = type->imageIndex;
-	if(!hasBonus(Selector::sourceType(Bonus::HERO_BASE_SKILL)))
-	{
-		for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
-		{
-			pushPrimSkill(static_cast<PrimarySkill::PrimarySkill>(g), type->heroClass->primarySkillInitial[g]);
-		}
-	}
-	if(secSkills.size() == 1 && secSkills[0] == std::pair<SecondarySkill,ui8>(SecondarySkill::DEFAULT, -1)) //set secondary skills to default
-		secSkills = type->secSkillsInit;
-	if (!name.length())
-		name = type->name;
-
-	if (sex == 0xFF)//sex is default
-		sex = type->sex;
-
-	setFormation(false);
-	if (!stacksCount()) //standard army//initial army
-	{
-		initArmy();
-	}
-	assert(validTypes());
-
-	level = 1;
-	if(exp == 0xffffffff)
-	{
-		initExp();
-	}
-	else
-	{
-		levelUpAutomatically();
-	}
-
-	if (VLC->modh->modules.COMMANDERS && !commander)
-	{
-		commander = new CCommanderInstance(type->heroClass->commander->idNumber);
-		commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders
-		commander->giveStackExp (exp); //after our exp is set
-	}
-
-	if (mana < 0)
-		mana = manaLimit();
 }
 
-void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/)
+void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visitor) const
 {
-	if(!dst)
-		dst = this;
-
-	int howManyStacks = 0; //how many stacks will hero receives <1 - 3>
-	int pom = cb->gameState()->getRandomGenerator().nextInt(99);
-	int warMachinesGiven = 0;
-
-	if(pom < 9)
-		howManyStacks = 1;
-	else if(pom < 79)
-		howManyStacks = 2;
-	else
-		howManyStacks = 3;
-
-	vstd::amin(howManyStacks, type->initialArmy.size());
-
-	for(int stackNo=0; stackNo < howManyStacks; stackNo++)
+	switch(shipyardStatus())
 	{
-		auto & stack = type->initialArmy[stackNo];
-
-		int count = cb->gameState()->getRandomGenerator().nextInt(stack.minAmount, stack.maxAmount);
-
-		if(stack.creature >= CreatureID::CATAPULT &&
-		   stack.creature <= CreatureID::ARROW_TOWERS) //war machine
+	case BOAT_ALREADY_BUILT:
+		out.addTxt(MetaString::GENERAL_TXT, 51);
+		break;
+	case TILE_BLOCKED:
+		if(visitor)
 		{
-			warMachinesGiven++;
-			if(dst != this)
-				continue;
-
-			int slot = -1;
-			ArtifactID aid = ArtifactID::NONE;
-			switch (stack.creature)
-			{
-			case CreatureID::CATAPULT:
-				slot = ArtifactPosition::MACH4;
-				aid = ArtifactID::CATAPULT;
-				break;
-			default:
-				aid = CArtHandler::creatureToMachineID(stack.creature);
-				slot = 9 + aid;
-				break;
-			}
-			auto convSlot = ArtifactPosition(slot);
-			if(!getArt(convSlot))
-				putArtifact(convSlot, CArtifactInstance::createNewArtifactInstance(aid));
-			else
-                logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid;
+			out.addTxt(MetaString::GENERAL_TXT, 134);
+			out.addReplacement(visitor->name);
 		}
 		else
-			dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count);
+			out.addTxt(MetaString::ADVOB_TXT, 189);
+		break;
+	case NO_WATER:
+		logGlobal->errorStream() << "Shipyard without water!!! " << o->pos << "\t" << o->id;
+		return;
 	}
 }
 
-CGHeroInstance::~CGHeroInstance()
+void IShipyard::getBoatCost( std::vector<si32> &cost ) const
 {
-	commander.dellNull();
+	cost.resize(GameConstants::RESOURCE_QUANTITY);
+	cost[Res::WOOD] = 10;
+	cost[Res::GOLD] = 1000;
 }
 
-bool CGHeroInstance::needsLastStack() const
+IShipyard::IShipyard(const CGObjectInstance *O)
+	: IBoatGenerator(O)
 {
-	return true;
 }
 
-void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
+IShipyard * IShipyard::castFrom( CGObjectInstance *obj )
 {
-	if(h == this) return; //exclude potential self-visiting
+	if(!obj)
+		return nullptr;
 
-	if (ID == Obj::HERO)
-	{
-		if( cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) //our or ally hero
-		{
-			//exchange
-			cb->heroExchange(h->id, id);
-		}
-		else //battle
-		{
-			if(visitedTown) //we're in town
-				visitedTown->onHeroVisit(h); //town will handle attacking
-			else
-				cb->startBattleI(h,	this);
-		}
-	}
-	else if(ID == Obj::PRISON)
+	if(obj->ID == Obj::TOWN)
 	{
-		int txt_id;
-
-		if (cb->getHeroCount(h->tempOwner, false) < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)//GameConstants::MAX_HEROES_PER_PLAYER) //free hero slot
-		{
-			cb->changeObjPos(id,pos+int3(1,0,0),0);
-			//update hero parameters
-			SetMovePoints smp;
-			smp.hid = id;
-			smp.val = maxMovePoints (true); //TODO: hota prison on water?
-			cb->setMovePoints (&smp);
-			cb->setManaPoints (id, manaLimit());
-
-			cb->setObjProperty(id, ObjProperty::ID, Obj::HERO); //set ID to 34
-			cb->giveHero(id,h->tempOwner); //recreates def and adds hero to player
-
-			txt_id = 102;
-		}
-		else //already 8 wandering heroes
-		{
-			txt_id = 103;
-		}
-
-		showInfoDialog(h,txt_id,soundBase::ROGUE);
+		return static_cast<CGTownInstance*>(obj);
 	}
-}
-
-const std::string & CGHeroInstance::getHoverText() const
-{
-	if(ID != Obj::PRISON)
+	else if(obj->ID == Obj::SHIPYARD)
 	{
-		hoverName = VLC->generaltexth->allTexts[15];
-		boost::algorithm::replace_first(hoverName,"%s",name);
-		boost::algorithm::replace_first(hoverName,"%s", type->heroClass->name);
-		return hoverName;
+		return static_cast<CGShipyard*>(obj);
 	}
 	else
-		hoverName = VLC->objtypeh->getObjectName(ID);
-
-	return hoverName;
-}
-
-const std::string & CGHeroInstance::getBiography() const
-{
-	if (biography.length())
-		return biography;
-	return type->biography;
-}
-
-ui8 CGHeroInstance::maxlevelsToMagicSchool() const
-{
-	return type->heroClass->isMagicHero() ? 3 : 4;
-}
-ui8 CGHeroInstance::maxlevelsToWisdom() const
-{
-	return type->heroClass->isMagicHero() ? 3 : 6;
-}
-
-void CGHeroInstance::SecondarySkillsInfo::resetMagicSchoolCounter()
-{
-	magicSchoolCounter = 1;
-}
-void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter()
-{
-	wisdomCounter = 1;
-}
-
-void CGHeroInstance::initObj()
-{
-	blockVisit = true;
-	auto  hs = new HeroSpecial();
-	hs->setNodeType(CBonusSystemNode::SPECIALTY);
-	attachTo(hs); //do we ever need to detach it?
-
-	if(!type)
-		initHero(); //TODO: set up everything for prison before specialties are configured
-
-	skillsInfo.rand.setSeed(cb->gameState()->getRandomGenerator().nextInt());
-	skillsInfo.resetMagicSchoolCounter();
-	skillsInfo.resetWisdomCounter();
-
-	for(const auto &spec : type->spec) //TODO: unfity with bonus system
-	{
-		auto bonus = new Bonus();
-		bonus->val = spec.val;
-		bonus->sid = id.getNum(); //from the hero, specialty has no unique id
-		bonus->duration = Bonus::PERMANENT;
-		bonus->source = Bonus::HERO_SPECIAL;
-		switch (spec.type)
-		{
-			case 1:// creature specialty
-				{
-					hs->growsWithLevel = true;
-
-					const CCreature &specCreature = *VLC->creh->creatures[spec.additionalinfo]; //creature in which we have specialty
-
-					//int creLevel = specCreature.level;
-					//if(!creLevel)
-					//{
-					//	if(spec.additionalinfo == 146)
-					//		creLevel = 5; //treat ballista as 5-level
-					//	else
-					//	{
-					//        logGlobal->warnStream() << "Warning: unknown level of " << specCreature.namePl;
-					//		continue;
-					//	}
-					//}
-
-					//bonus->additionalInfo = spec.additionalinfo; //creature id, should not be used again - this works only with limiter
-					bonus->limiter.reset(new CCreatureTypeLimiter (specCreature, true)); //with upgrades
-					bonus->type = Bonus::PRIMARY_SKILL;
-					bonus->valType = Bonus::ADDITIVE_VALUE;
-
-					bonus->subtype = PrimarySkill::ATTACK;
-					hs->addNewBonus(bonus);
-
-					bonus = new Bonus(*bonus);
-					bonus->subtype = PrimarySkill::DEFENSE;
-					hs->addNewBonus(bonus);
-					//values will be calculated later
-
-					bonus = new Bonus(*bonus);
-					bonus->type = Bonus::STACKS_SPEED;
-					bonus->val = 1; //+1 speed
-					hs->addNewBonus(bonus);
-				}
-				break;
-			case 2://secondary skill
-				hs->growsWithLevel = true;
-				bonus->type = Bonus::SPECIAL_SECONDARY_SKILL; //needs to be recalculated with level, based on this value
-				bonus->valType = Bonus::BASE_NUMBER; // to receive nonzero value
-				bonus->subtype = spec.subtype; //skill id
-				bonus->val = spec.val; //value per level, in percent
-				hs->addNewBonus(bonus);
-				bonus = new Bonus(*bonus);
-
-				switch (spec.additionalinfo)
-				{
-					case 0: //normal
-						bonus->valType = Bonus::PERCENT_TO_BASE;
-						break;
-					case 1: //when it's navigation or there's no 'base' at all
-						bonus->valType = Bonus::PERCENT_TO_ALL;
-						break;
-				}
-				bonus->type = Bonus::SECONDARY_SKILL_PREMY; //value will be calculated later
-				hs->addNewBonus(bonus);
-				break;
-			case 3://spell damage bonus, level dependent but calculated elsewhere
-				bonus->type = Bonus::SPECIAL_SPELL_LEV;
-				bonus->subtype = spec.subtype;
-				hs->addNewBonus(bonus);
-				break;
-			case 4://creature stat boost
-				switch (spec.subtype)
-				{
-					case 1://attack
-						bonus->type = Bonus::PRIMARY_SKILL;
-						bonus->subtype = PrimarySkill::ATTACK;
-						break;
-					case 2://defense
-						bonus->type = Bonus::PRIMARY_SKILL;
-						bonus->subtype = PrimarySkill::DEFENSE;
-						break;
-					case 3:
-						bonus->type = Bonus::CREATURE_DAMAGE;
-						bonus->subtype = 0; //both min and max
-						break;
-					case 4://hp
-						bonus->type = Bonus::STACK_HEALTH;
-						break;
-					case 5:
-						bonus->type = Bonus::STACKS_SPEED;
-						break;
-					default:
-						continue;
-				}
-				bonus->additionalInfo = spec.additionalinfo; //creature id
-				bonus->valType = Bonus::ADDITIVE_VALUE;
-				bonus->limiter.reset(new CCreatureTypeLimiter (*VLC->creh->creatures[spec.additionalinfo], true));
-				hs->addNewBonus(bonus);
-				break;
-			case 5://spell damage bonus in percent
-				bonus->type = Bonus::SPECIFIC_SPELL_DAMAGE;
-				bonus->valType = Bonus::BASE_NUMBER; // current spell system is screwed
-				bonus->subtype = spec.subtype; //spell id
-				hs->addNewBonus(bonus);
-				break;
-			case 6://damage bonus for bless (Adela)
-				bonus->type = Bonus::SPECIAL_BLESS_DAMAGE;
-				bonus->subtype = spec.subtype; //spell id if you ever wanted to use it otherwise
-				bonus->additionalInfo = spec.additionalinfo; //damage factor
-				hs->addNewBonus(bonus);
-				break;
-			case 7://maxed mastery for spell
-				bonus->type = Bonus::MAXED_SPELL;
-				bonus->subtype = spec.subtype; //spell i
-				hs->addNewBonus(bonus);
-				break;
-			case 8://peculiar spells - enchantments
-				bonus->type = Bonus::SPECIAL_PECULIAR_ENCHANT;
-				bonus->subtype = spec.subtype; //spell id
-				bonus->additionalInfo = spec.additionalinfo;//0, 1 for Coronius
-				hs->addNewBonus(bonus);
-				break;
-			case 9://upgrade creatures
-			{
-				const auto &creatures = VLC->creh->creatures;
-				bonus->type = Bonus::SPECIAL_UPGRADE;
-				bonus->subtype = spec.subtype; //base id
-				bonus->additionalInfo = spec.additionalinfo; //target id
-				hs->addNewBonus(bonus);
-				bonus = new Bonus(*bonus);
-
-				for(auto cre_id : creatures[spec.subtype]->upgrades)
-				{
-					bonus->subtype = cre_id; //propagate for regular upgrades of base creature
-					hs->addNewBonus(bonus);
-					bonus = new Bonus(*bonus);
-				}
-				vstd::clear_pointer(bonus);
-				break;
-			}
-			case 10://resource generation
-				bonus->type = Bonus::GENERATE_RESOURCE;
-				bonus->subtype = spec.subtype;
-				hs->addNewBonus(bonus);
-				break;
-			case 11://starting skill with mastery (Adrienne)
-				setSecSkillLevel(SecondarySkill(spec.val), spec.additionalinfo, true);
-				break;
-			case 12://army speed
-				bonus->type = Bonus::STACKS_SPEED;
-				hs->addNewBonus(bonus);
-				break;
-			case 13://Dragon bonuses (Mutare)
-				bonus->type = Bonus::PRIMARY_SKILL;
-				bonus->valType = Bonus::ADDITIVE_VALUE;
-				switch (spec.subtype)
-				{
-					case 1:
-						bonus->subtype = PrimarySkill::ATTACK;
-						break;
-					case 2:
-						bonus->subtype = PrimarySkill::DEFENSE;
-						break;
-				}
-				bonus->limiter.reset(new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE));
-				hs->addNewBonus(bonus);
-				break;
-			default:
-                logGlobal->warnStream() << "Unexpected hero specialty " << type;
-		}
-	}
-	specialty.push_back(hs); //will it work?
-
-	for (auto hs2 : type->specialty) //copy active (probably growing) bonuses from hero prootype to hero object
 	{
-		auto  hs = new HeroSpecial();
-		attachTo(hs); //do we ever need to detach it?
-
-		hs->setNodeType(CBonusSystemNode::SPECIALTY);
-		for (auto bonus : hs2.bonuses)
-		{
-			hs->addNewBonus (bonus);
-		}
-		hs->growsWithLevel = hs2.growsWithLevel;
-
-		specialty.push_back(hs); //will it work?
+		return nullptr;
 	}
-
-	//initialize bonuses
-	recreateSecondarySkillsBonuses();
-	Updatespecialty();
-
-	mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one
-	type->name = name;
 }
-void CGHeroInstance::Updatespecialty() //TODO: calculate special value of bonuses on-the-fly?
-{
-	for (auto hs : specialty)
-	{
-		if (hs->growsWithLevel)
-		{
-			//const auto &creatures = VLC->creh->creatures;
 
-			for(Bonus * b : hs->getBonusList())
-			{
-				switch (b->type)
-				{
-					case Bonus::SECONDARY_SKILL_PREMY:
-						b->val = (hs->valOfBonuses(Bonus::SPECIAL_SECONDARY_SKILL, b->subtype) * level);
-						break; //use only hero skills as bonuses to avoid feedback loop
-					case Bonus::PRIMARY_SKILL: //for creatures, that is
-					{
-						const CCreature * cre = nullptr;
-						int creLevel = 0;
-						if (auto creatureLimiter = std::dynamic_pointer_cast<CCreatureTypeLimiter>(b->limiter)) //TODO: more general eveluation of bonuses?
-						{
-							cre = creatureLimiter->creature;
-							creLevel = cre->level;
-							if (!creLevel)
-							{
-								creLevel = 5; //treat ballista as tier 5
-							}
-						}
-						else //no creature found, can't calculate value
-						{
-                            logGlobal->warnStream() << "Primary skill specialty growth supported only with creature type limiters";
-							break;
-						}
-
-						double primSkillModifier = (int)(level / creLevel) / 20.0;
-						int param;
-						switch (b->subtype)
-						{
-							case PrimarySkill::ATTACK:
-								param = cre->Attack();
-								break;
-							case PrimarySkill::DEFENSE:
-								param = cre->Defense();
-								break;
-							default:
-								continue;
-						}
-						b->val = ceil(param * (1 + primSkillModifier)) - param; //yep, overcomplicated but matches original
-						break;
-					}
-				}
-			}
-		}
-	}
-}
-
-void CGHeroInstance::recreateSecondarySkillsBonuses()
-{
-	auto secondarySkillsBonuses = getBonuses(Selector::sourceType(Bonus::SECONDARY_SKILL));
-	for(auto bonus : *secondarySkillsBonuses)
-		removeBonus(bonus);
-
-	for(auto skill_info : secSkills)
-		updateSkill(SecondarySkill(skill_info.first), skill_info.second);
-}
-
-void CGHeroInstance::updateSkill(SecondarySkill which, int val)
-{
-	if(which == SecondarySkill::LEADERSHIP || which == SecondarySkill::LUCK)
-	{ //luck-> VLC->generaltexth->arraytxt[73+luckSkill]; VLC->generaltexth->arraytxt[104+moraleSkill]
-		bool luck = which == SecondarySkill::LUCK;
-		Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK};
-
-		Bonus *b = getBonusLocalFirst(Selector::type(type[luck]).And(Selector::sourceType(Bonus::SECONDARY_SKILL)));
-		if(!b)
-		{
-			b = new Bonus(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER);
-			addNewBonus(b);
-		}
-		else
-			b->val = +val;
-	}
-	else if(which == SecondarySkill::DIPLOMACY) //surrender discount: 20% per level
-	{
-
-		if(Bonus *b = getBonusLocalFirst(Selector::type(Bonus::SURRENDER_DISCOUNT).And(Selector::sourceType(Bonus::SECONDARY_SKILL))))
-			b->val = +val;
-		else
-			addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which));
-	}
-
-	int skillVal = 0;
-	switch (which)
-	{
-	case SecondarySkill::ARCHERY:
-		switch (val)
-		{
-		case 1:
-			skillVal = 10; break;
-		case 2:
-			skillVal = 25; break;
-		case 3:
-			skillVal = 50; break;
-		}
-		break;
-	case SecondarySkill::LOGISTICS:
-		skillVal = 10 * val; break;
-	case SecondarySkill::NAVIGATION:
-		skillVal = 50 * val; break;
-	case SecondarySkill::MYSTICISM:
-		skillVal = val; break;
-	case SecondarySkill::EAGLE_EYE:
-		skillVal = 30 + 10 * val; break;
-	case SecondarySkill::NECROMANCY:
-		skillVal = 10 * val; break;
-	case SecondarySkill::LEARNING:
-		skillVal = 5 * val; break;
-	case SecondarySkill::OFFENCE:
-		skillVal = 10 * val; break;
-	case SecondarySkill::ARMORER:
-		skillVal = 5 * val; break;
-	case SecondarySkill::INTELLIGENCE:
-		skillVal = 25 << (val-1); break;
-	case SecondarySkill::SORCERY:
-		skillVal = 5 * val; break;
-	case SecondarySkill::RESISTANCE:
-		skillVal = 5 << (val-1); break;
-	case SecondarySkill::FIRST_AID:
-		skillVal = 25 + 25*val; break;
-	case SecondarySkill::ESTATES:
-		skillVal = 125 << (val-1); break;
-	}
-
-
-	Bonus::ValueType skillValType = skillVal ? Bonus::BASE_NUMBER : Bonus::INDEPENDENT_MIN;
-	if(Bonus * b = getBonusList().getFirst(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, which)
-										.And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) //only local hero bonus
-	{
-		b->val = skillVal;
-		b->valType = skillValType;
-	}
-	else
-	{
-		auto bonus = new Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, Bonus::SECONDARY_SKILL, skillVal, id.getNum(), which, skillValType);
-		bonus->source = Bonus::SECONDARY_SKILL;
-		addNewBonus(bonus);
-	}
-
-}
-void CGHeroInstance::setPropertyDer( ui8 what, ui32 val )
-{
-	if(what == ObjProperty::PRIMARY_STACK_COUNT)
-		setStackCount(SlotID(0), val);
-}
-
-double CGHeroInstance::getFightingStrength() const
-{
-	return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE)));
-}
-
-double CGHeroInstance::getMagicStrength() const
-{
-	return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER)));
-}
-
-double CGHeroInstance::getHeroStrength() const
-{
-	return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrength(), 2.0));
-}
-
-ui64 CGHeroInstance::getTotalStrength() const
-{
-	double ret = getFightingStrength() * getArmyStrength();
-	return (ui64) ret;
-}
-
-TExpType CGHeroInstance::calculateXp(TExpType exp) const
-{
-	return exp * (100 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::LEARNING))/100.0;
-}
-
-ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool) const
-{
-	si16 skill = -1; //skill level
-
-#define TRY_SCHOOL(schoolName, schoolMechanicsId, schoolOutId)	\
-	if(spell-> schoolName)									\
-	{															\
-		int thisSchool = std::max<int>(getSecSkillLevel( \
-			SecondarySkill(14 + (schoolMechanicsId))), \
-			valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << (schoolMechanicsId))); \
-		if(thisSchool > skill)									\
-		{														\
-			skill = thisSchool;									\
-			if(outSelectedSchool)								\
-				*outSelectedSchool = schoolOutId;				\
-		}														\
-	}
-	TRY_SCHOOL(fire, 0, 1)
-	TRY_SCHOOL(air, 1, 0)
-	TRY_SCHOOL(water, 2, 2)
-	TRY_SCHOOL(earth, 3, 3)
-#undef TRY_SCHOOL
-
-
-
-	vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus
-	vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect
-	if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero specialty (Daremyth, Melodia)
-		skill = 3;
-	assert(skill >= 0 && skill <= 3);
-	return skill;
-}
-
-bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
-{
-	if(!getArt(ArtifactPosition::SPELLBOOK)) //if hero has no spellbook
-		return false;
-
-    if (spell->isSpecialSpell())
-    {
-        if (vstd::contains(spells, spell->id))
-        {//hero has this spell in spellbook
-            logGlobal->errorStream() << "Special spell in spellbook "<<spell->name;
-        }
-
-        if (hasBonusOfType(Bonus::SPELL, spell->id))
-            return true;
-
-        return false;
-    }
-    else
-    {
-        if(vstd::contains(spells, spell->id) //hero has this spell in spellbook
-            || (spell->air && hasBonusOfType(Bonus::AIR_SPELLS)) // this is air spell and hero can cast all air spells
-            || (spell->fire && hasBonusOfType(Bonus::FIRE_SPELLS)) // this is fire spell and hero can cast all fire spells
-            || (spell->water && hasBonusOfType(Bonus::WATER_SPELLS)) // this is water spell and hero can cast all water spells
-            || (spell->earth && hasBonusOfType(Bonus::EARTH_SPELLS)) // this is earth spell and hero can cast all earth spells
-            || hasBonusOfType(Bonus::SPELL, spell->id)
-            || hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->level)
-            )
-            return true;
-
-        return false;
-    }
-}
-
-/**
- * Calculates what creatures and how many to be raised from a battle.
- * @param battleResult The results of the battle.
- * @return Returns a pair with the first value indicating the ID of the creature
- * type and second value the amount. Both values are returned as -1 if necromancy
- * could not be applied.
- */
-CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &battleResult) const
-{
-	const ui8 necromancyLevel = getSecSkillLevel(SecondarySkill::NECROMANCY);
-
-	// Hero knows necromancy or has Necromancer Cloak
-	if (necromancyLevel > 0 || hasBonusOfType(Bonus::IMPROVED_NECROMANCY))
-	{
-		double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::NECROMANCY)/100.0;
-		vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all...
-		const std::map<ui32,si32> &casualties = battleResult.casualties[!battleResult.winner];
-		ui32 raisedUnits = 0;
-
-		// Figure out what to raise and how many.
-		const CreatureID creatureTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES};
-		const bool improvedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY);
-		const CCreature *raisedUnitType = VLC->creh->creatures[creatureTypes[improvedNecromancy ? necromancyLevel : 0]];
-		const ui32 raisedUnitHP = raisedUnitType->valOfBonuses(Bonus::STACK_HEALTH);
-
-		//calculate creatures raised from each defeated stack
-		for (auto & casualtie : casualties)
-		{
-			// Get lost enemy hit points convertible to units.
-			CCreature * c = VLC->creh->creatures[casualtie.first];
-
-			const ui32 raisedHP = c->valOfBonuses(Bonus::STACK_HEALTH) * casualtie.second * necromancySkill;
-			raisedUnits += std::min<ui32>(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count
-		}
-
-		// Make room for new units.
-		SlotID slot = getSlotFor(raisedUnitType->idNumber);
-		if (slot == SlotID())
-		{
-			// If there's no room for unit, try it's upgraded version 2/3rds the size.
-			raisedUnitType = VLC->creh->creatures[*raisedUnitType->upgrades.begin()];
-			raisedUnits = (raisedUnits*2)/3;
-
-			slot = getSlotFor(raisedUnitType->idNumber);
-		}
-		if (raisedUnits <= 0)
-			raisedUnits = 1;
-
-		return CStackBasicDescriptor(raisedUnitType->idNumber, raisedUnits);
-	}
-
-	return CStackBasicDescriptor();
-}
-
-/**
- * Show the necromancy dialog with information about units raised.
- * @param raisedStack Pair where the first element represents ID of the raised creature
- * and the second element the amount.
- */
-void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const
-{
-	InfoWindow iw;
-	iw.soundID = soundBase::pickup01 + cb->gameState()->getRandomGenerator().nextInt(6);
-	iw.player = tempOwner;
-	iw.components.push_back(Component(raisedStack));
-
-	if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural)
-	{
-		iw.text.addTxt(MetaString::GENERAL_TXT, 145);
-		iw.text.addReplacement(raisedStack.count);
-	}
-	else // Practicing the dark arts of necromancy, ... (singular)
-	{
-		iw.text.addTxt(MetaString::GENERAL_TXT, 146);
-	}
-	iw.text.addReplacement(raisedStack);
-
-	cb->showInfoDialog(&iw);
-}
-
-int3 CGHeroInstance::getSightCenter() const
-{
-	return getPosition(false);
-}
-
-int CGHeroInstance::getSightRadious() const
-{
-	return 5 + getSecSkillLevel(SecondarySkill::SCOUTING) + valOfBonuses(Bonus::SIGHT_RADIOUS); //default + scouting
-}
-
-si32 CGHeroInstance::manaRegain() const
-{
-	if (hasBonusOfType(Bonus::FULL_MANA_REGENERATION))
-		return manaLimit();
-
-	return 1 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 8) + valOfBonuses(Bonus::MANA_REGENERATION); //1 + Mysticism level
-}
-
-// /**
-//  * Places an artifact in hero's backpack. If it's a big artifact equips it
-//  * or discards it if it cannot be equipped.
-//  */
-// void CGHeroInstance::giveArtifact (ui32 aid) //use only for fixed artifacts
-// {
-// 	CArtifact * const artifact = VLC->arth->artifacts[aid]; //pointer to constant object
-// 	CArtifactInstance *ai = CArtifactInstance::createNewArtifactInstance(artifact);
-// 	ai->putAt(this, ai->firstAvailableSlot(this));
-// }
-
-int CGHeroInstance::getBoatType() const
-{
-	switch(type->heroClass->getAlignment())
-	{
-	case EAlignment::GOOD:
-		return 1;
-	case EAlignment::EVIL:
-		return 0;
-	case EAlignment::NEUTRAL:
-		return 2;
-	default:
-		throw std::runtime_error("Wrong alignment!");
-	}
-}
-
-void CGHeroInstance::getOutOffsets(std::vector<int3> &offsets) const
-{
-	static int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
-	for (auto & dir : dirs)
-		offsets += dir;
-}
-
-int CGHeroInstance::getSpellCost(const CSpell *sp) const
-{
-	return sp->getCost(getSpellSchoolLevel(sp));
-}
-
-void CGHeroInstance::pushPrimSkill( PrimarySkill::PrimarySkill which, int val )
-{
-	assert(!hasBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, which)
-						.And(Selector::sourceType(Bonus::HERO_BASE_SKILL))));
-	addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::HERO_BASE_SKILL, val, id.getNum(), which));
-}
-
-EAlignment::EAlignment CGHeroInstance::getAlignment() const
-{
-	return type->heroClass->getAlignment();
-}
-
-void CGHeroInstance::initExp()
-{
-	exp = cb->gameState()->getRandomGenerator().nextInt(40, 89);
-}
-
-std::string CGHeroInstance::nodeName() const
-{
-	return "Hero " + name;
-}
-
-void CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance *art)
-{
-	assert(!getArt(pos));
-	art->putAt(ArtifactLocation(this, pos));
-}
-
-void CGHeroInstance::putInBackpack(CArtifactInstance *art)
-{
-	putArtifact(art->firstBackpackSlot(this), art);
-}
-
-bool CGHeroInstance::hasSpellbook() const
-{
-	return getArt(ArtifactPosition::SPELLBOOK);
-}
-
-void CGHeroInstance::deserializationFix()
-{
-	artDeserializationFix(this);
-
-	for (auto hs : specialty)
-	{
-		attachTo (hs);
-	}
-}
-
-CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs)
-{
-	if(visitedTown)
-	{
-		if(inTownGarrison)
-			return visitedTown;
-		else
-			return &visitedTown->townAndVis;
-	}
-	else
-		return CArmedInstance::whereShouldBeAttached(gs);
-}
-
-int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const
-{
-	if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
-		return (MPsBefore - basicCost) * static_cast<double>(maxMovePoints(disembark)) / maxMovePoints(!disembark);
-
-	return 0; //take all MPs otherwise
-}
-
-CGHeroInstance::ECanDig CGHeroInstance::diggingStatus() const
-{
-	if(movement < maxMovePoints(true))
-		return LACK_OF_MOVEMENT;
-    else if(cb->getTile(getPosition(false))->terType == ETerrainType::WATER)
-		return WRONG_TERRAIN;
-	else
-	{
-		const TerrainTile *t = cb->getTile(getPosition());
-		//TODO look for hole
-		//CGI->mh->getTerrainDescr(h->getPosition(false), hlp, false);
-		if(/*hlp.length() || */t->blockingObjects.size() > 1)
-			return TILE_OCCUPIED;
-		else
-			return CAN_DIG;
-	}
-}
-
-ArtBearer::ArtBearer CGHeroInstance::bearerType() const
-{
-	return ArtBearer::HERO;
-}
-
-std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills() const
-{
-	std::vector<SecondarySkill> obligatorySkills; //hero is offered magic school or wisdom if possible
-	if (!skillsInfo.wisdomCounter)
-	{
-		if (cb->isAllowed(2, SecondarySkill::WISDOM) && !getSecSkillLevel(SecondarySkill::WISDOM))
-			obligatorySkills.push_back(SecondarySkill::WISDOM);
-	}
-	if (!skillsInfo.magicSchoolCounter)
-	{
-		std::vector<SecondarySkill> ss;
-		ss += SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC;
-
-		std::shuffle(ss.begin(), ss.end(), skillsInfo.rand.getStdGenerator());
-
-		for (auto skill : ss)
-		{
-			if (cb->isAllowed(2, skill) && !getSecSkillLevel(skill)) //only schools hero doesn't know yet
-			{
-				obligatorySkills.push_back(skill);
-				break; //only one
-			}
-		}
-	}
-
-	std::vector<SecondarySkill> skills;
-	//picking sec. skills for choice
-	std::set<SecondarySkill> basicAndAdv, expert, none;
-	for(int i=0;i<GameConstants::SKILL_QUANTITY;i++)
-		if (cb->isAllowed(2,i))
-			none.insert(SecondarySkill(i));
-
-	for(auto & elem : secSkills)
-	{
-		if(elem.second < SecSkillLevel::EXPERT)
-			basicAndAdv.insert(elem.first);
-		else
-			expert.insert(elem.first);
-		none.erase(elem.first);
-	}
-	for (auto s : obligatorySkills) //don't duplicate them
-	{
-		none.erase (s);
-		basicAndAdv.erase (s);
-		expert.erase (s);
-	}
-
-	//first offered skill:
-	// 1) give obligatory skill
-	// 2) give any other new skill
-	// 3) upgrade existing
-	if (canLearnSkill() && obligatorySkills.size() > 0)
-	{
-		skills.push_back (obligatorySkills[0]);
-	}
-	else if(none.size() && canLearnSkill()) //hero have free skill slot
-	{
-		skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //new skill
-		none.erase(skills.back());
-	}
-	else if(!basicAndAdv.empty())
-	{
-		skills.push_back(type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand)); //upgrade existing
-		basicAndAdv.erase(skills.back());
-	}
-
-	//second offered skill:
-	//1) upgrade existing
-	//2) give obligatory skill
-	//3) give any other new skill
-	if(!basicAndAdv.empty())
-	{
-		SecondarySkill s = type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand);//upgrade existing
-		skills.push_back(s);
-		basicAndAdv.erase(s);
-	}
-	else if (canLearnSkill() && obligatorySkills.size() > 1)
-	{
-		skills.push_back (obligatorySkills[1]);
-	}
-	else if(none.size() && canLearnSkill())
-	{
-		skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //give new skill
-		none.erase(skills.back());
-	}
-
-	return skills;
-}
-
-PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill() const
-{
-	assert(gainsLevel());
-	int randomValue = cb->gameState()->getRandomGenerator().nextInt(99), pom = 0, primarySkill = 0;
-	const auto & skillChances = (level > 9) ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel;
-
-	for(; primarySkill < GameConstants::PRIMARY_SKILLS; ++primarySkill)
-	{
-		pom += skillChances[primarySkill];
-		if(randomValue < pom)
-		{
-			break;
-		}
-	}
-
-	logGlobal->traceStream() << "The hero gets the primary skill " << primarySkill << " with a probability of " << randomValue << "%.";
-	return static_cast<PrimarySkill::PrimarySkill>(primarySkill);
-}
-
-boost::optional<SecondarySkill> CGHeroInstance::nextSecondarySkill() const
-{
-	assert(gainsLevel());
-
-	boost::optional<SecondarySkill> chosenSecondarySkill;
-	const auto proposedSecondarySkills = getLevelUpProposedSecondarySkills();
-	if(!proposedSecondarySkills.empty())
-	{
-		std::vector<SecondarySkill> learnedSecondarySkills;
-		for(auto secondarySkill : proposedSecondarySkills)
-		{
-			if(getSecSkillLevel(secondarySkill) > 0)
-			{
-				learnedSecondarySkills.push_back(secondarySkill);
-			}
-		}
-
-		auto & rand = cb->gameState()->getRandomGenerator();
-		if(learnedSecondarySkills.empty())
-		{
-			// there are only new skills to learn, so choose anyone of them
-			chosenSecondarySkill = *RandomGeneratorUtil::nextItem(proposedSecondarySkills, rand);
-		}
-		else
-		{
-			// preferably upgrade a already learned secondary skill
-			chosenSecondarySkill = *RandomGeneratorUtil::nextItem(learnedSecondarySkills, rand);
-		}
-	}
-	return chosenSecondarySkill;
-}
-
-void CGHeroInstance::setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs)
-{
-	if(primarySkill < PrimarySkill::EXPERIENCE)
-	{
-		Bonus * skill = getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL)
-											.And(Selector::subtype(primarySkill))
-											.And(Selector::sourceType(Bonus::HERO_BASE_SKILL)));
-		assert(skill);
-
-		if(abs)
-		{
-			skill->val = value;
-		}
-		else
-		{
-			skill->val += value;
-		}
-	}
-	else if(primarySkill == PrimarySkill::EXPERIENCE)
-	{
-		if(abs)
-		{
-			exp = value;
-		}
-		else
-		{
-			exp += value;
-		}
-	}
-}
-
-bool CGHeroInstance::gainsLevel() const
-{
-	return exp >= VLC->heroh->reqExp(level+1);
-}
-
-void CGHeroInstance::levelUp(std::vector<SecondarySkill> skills)
-{
-	++level;
-
-	//deterministic secondary skills
-	skillsInfo.magicSchoolCounter = (skillsInfo.magicSchoolCounter + 1) % maxlevelsToMagicSchool();
-	skillsInfo.wisdomCounter = (skillsInfo.wisdomCounter + 1) % maxlevelsToWisdom();
-	if(vstd::contains(skills, SecondarySkill::WISDOM))
-	{
-		skillsInfo.resetWisdomCounter();
-	}
-
-	SecondarySkill spellSchools[] = {
-		SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC};
-	for(auto skill : spellSchools)
-	{
-		if(vstd::contains(skills, skill))
-		{
-			skillsInfo.resetMagicSchoolCounter();
-			break;
-		}
-	}
-
-	//specialty
-	Updatespecialty();
-}
-
-void CGHeroInstance::levelUpAutomatically()
-{
-	while(gainsLevel())
-	{
-		const auto primarySkill = nextPrimarySkill();
-		setPrimarySkill(primarySkill, 1, false);
-
-		auto proposedSecondarySkills = getLevelUpProposedSecondarySkills();
-
-		const auto secondarySkill = nextSecondarySkill();
-		if(secondarySkill)
-		{
-			setSecSkillLevel(*secondarySkill, 1, false);
-		}
-
-		//TODO why has the secondary skills to be passed to the method?
-		levelUp(proposedSecondarySkills);
-	}
-}
-
-void CGDwelling::initObj()
-{
-	switch(ID)
-	{
-	case Obj::CREATURE_GENERATOR1:
-		{
-			CreatureID crid = VLC->objh->cregens[subID];
-			const CCreature *crs = VLC->creh->creatures[crid];
-
-			creatures.resize(1);
-			creatures[0].second.push_back(crid);
-			if (subID >= VLC->generaltexth->creGens.size()) //very messy workaround
-			{
-				auto & dwellingNames = VLC->townh->factions[crs->faction]->town->dwellingNames;
-				assert (dwellingNames.size() > crs->level - 1);
-				hoverName = dwellingNames[crs->level - 1];
-			}
-			else
-				hoverName = VLC->generaltexth->creGens[subID];
-			if(crs->level > 4)
-				putStack(SlotID(0), new CStackInstance(crs, (crs->growth) * 3));
-			if (getOwner() != PlayerColor::NEUTRAL)
-				cb->gameState()->players[getOwner()].dwellings.push_back (this);
-		}
-		break;
-
-	case Obj::CREATURE_GENERATOR4:
-		creatures.resize(4);
-		if(subID == 1) //Golem Factory
-		{
-			creatures[0].second.push_back(CreatureID::STONE_GOLEM);
-			creatures[1].second.push_back(CreatureID::IRON_GOLEM);
-			creatures[2].second.push_back(CreatureID::GOLD_GOLEM);
-			creatures[3].second.push_back(CreatureID::DIAMOND_GOLEM);
-			//guards
-			putStack(SlotID(0), new CStackInstance(CreatureID::GOLD_GOLEM, 9));
-			putStack(SlotID(1), new CStackInstance(CreatureID::DIAMOND_GOLEM, 6));
-		}
-		else if(subID == 0) // Elemental Conflux
-		{
-			creatures[0].second.push_back(CreatureID::AIR_ELEMENTAL);
-			creatures[1].second.push_back(CreatureID::FIRE_ELEMENTAL);
-			creatures[2].second.push_back(CreatureID::EARTH_ELEMENTAL);
-			creatures[3].second.push_back(CreatureID::WATER_ELEMENTAL);
-			//guards
-			putStack(SlotID(0), new CStackInstance(CreatureID::EARTH_ELEMENTAL, 12));
-		}
-		else
-		{
-			assert(0);
-		}
-		hoverName = VLC->generaltexth->creGens4[subID];
-		break;
-
-	case Obj::REFUGEE_CAMP:
-		//is handled within newturn func
-		break;
-
-	case Obj::WAR_MACHINE_FACTORY:
-		creatures.resize(3);
-		creatures[0].second.push_back(CreatureID::BALLISTA);
-		creatures[1].second.push_back(CreatureID::FIRST_AID_TENT);
-		creatures[2].second.push_back(CreatureID::AMMO_CART);
-		break;
-
-	default:
-		assert(0);
-		break;
-	}
-}
-
-void CGDwelling::setProperty(ui8 what, ui32 val)
-{
-	switch (what)
-	{
-		case ObjProperty::OWNER: //change owner
-			if (ID == Obj::CREATURE_GENERATOR1) //single generators
-			{
-				if (tempOwner != PlayerColor::NEUTRAL)
-				{
-					std::vector<ConstTransitivePtr<CGDwelling> >* dwellings = &cb->gameState()->players[tempOwner].dwellings;
-					dwellings->erase (std::find(dwellings->begin(), dwellings->end(), this));
-				}
-				if (PlayerColor(val) != PlayerColor::NEUTRAL) //can new owner be neutral?
-					cb->gameState()->players[PlayerColor(val)].dwellings.push_back (this);
-			}
-			break;
-		case ObjProperty::AVAILABLE_CREATURE:
-			creatures.resize(1);
-			creatures[0].second.resize(1);
-			creatures[0].second[0] = CreatureID(val);
-			break;
-	}
-	CGObjectInstance::setProperty(what,val);
-}
-void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if(ID == Obj::REFUGEE_CAMP && !creatures[0].first) //Refugee Camp, no available cres
-	{
-		InfoWindow iw;
-		iw.player = h->tempOwner;
-		iw.text.addTxt(MetaString::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted.  Perhaps you should try next week.
-		iw.text.addReplacement(MetaString::OBJ_NAMES, ID);
-		cb->sendAndApply(&iw);
-		return;
-	}
-
-	PlayerRelations::PlayerRelations relations = cb->gameState()->getPlayerRelations( h->tempOwner, tempOwner );
-
-	if ( relations == PlayerRelations::ALLIES )
-		return;//do not allow recruiting or capturing
-
-	if( !relations  &&  stacksCount() > 0) //object is guarded, owned by enemy
-	{
-		BlockingDialog bd(true,false);
-		bd.player = h->tempOwner;
-		bd.text.addTxt(MetaString::GENERAL_TXT, 421); //Much to your dismay, the %s is guarded by %s %s. Do you wish to fight the guards?
-		bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID);
-		bd.text.addReplacement(MetaString::ARRAY_TXT, 176 + Slots().begin()->second->getQuantityID()*3);
-		bd.text.addReplacement(*Slots().begin()->second);
-		cb->showBlockingDialog(&bd);
-		return;
-	}
-
-	if(!relations  &&  ID != Obj::WAR_MACHINE_FACTORY)
-	{
-		cb->setOwner(this, h->tempOwner);
-	}
-
-	BlockingDialog bd (true,false);
-	bd.player = h->tempOwner;
-	if(ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR4)
-	{
-		bd.text.addTxt(MetaString::ADVOB_TXT, ID == Obj::CREATURE_GENERATOR1 ? 35 : 36); //{%s} Would you like to recruit %s? / {%s} Would you like to recruit %s, %s, %s, or %s?
-		bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID);
-		for(auto & elem : creatures)
-			bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]);
-	}
-	else if(ID == Obj::REFUGEE_CAMP)
-	{
-		bd.text.addTxt(MetaString::ADVOB_TXT, 35); //{%s} Would you like to recruit %s?
-		bd.text.addReplacement(MetaString::OBJ_NAMES, ID);
-		for(auto & elem : creatures)
-			bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]);
-	}
-	else if(ID == Obj::WAR_MACHINE_FACTORY)
-		bd.text.addTxt(MetaString::ADVOB_TXT, 157); //{War Machine Factory} Would you like to purchase War Machines?
-	else
-		throw std::runtime_error("Illegal dwelling!");
-
-	cb->showBlockingDialog(&bd);
-}
-
-void CGDwelling::newTurn() const
-{
-	if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week
-		return;
-
-	//town growths and War Machines Factories are handled separately
-	if(ID == Obj::TOWN  ||  ID == Obj::WAR_MACHINE_FACTORY)
-		return;
-
-	if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature
-	{
-		cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(cb->gameState()->getRandomGenerator()));
-	}
-
-	bool change = false;
-
-	SetAvailableCreatures sac;
-	sac.creatures = creatures;
-	sac.tid = id;
-	for (size_t i = 0; i < creatures.size(); i++)
-	{
-		if(creatures[i].second.size())
-		{
-			CCreature *cre = VLC->creh->creatures[creatures[i].second[0]];
-			TQuantity amount = cre->growth * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH);
-			if (VLC->modh->settings.DWELLINGS_ACCUMULATE_CREATURES && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures
-				sac.creatures[i].first += amount;
-			else
-				sac.creatures[i].first = amount;
-			change = true;
-		}
-	}
-
-	if(change)
-		cb->sendAndApply(&sac);
-}
-
-void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const
-{
-	CreatureID crid = creatures[0].second[0];
-	CCreature *crs = VLC->creh->creatures[crid];
-	TQuantity count = creatures[0].first;
-
-	if(crs->level == 1  &&  ID != Obj::REFUGEE_CAMP) //first level - creatures are for free
-	{
-		if(count) //there are available creatures
-		{
-			SlotID slot = h->getSlotFor(crid);
-			if(!slot.validSlot()) //no available slot
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 425);//The %s would join your hero, but there aren't enough provisions to support them.
-				iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid);
-				cb->showInfoDialog(&iw);
-			}
-			else //give creatures
-			{
-				SetAvailableCreatures sac;
-				sac.tid = id;
-				sac.creatures = creatures;
-				sac.creatures[0].first = 0;
-
-
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 423); //%d %s join your army.
-				iw.text.addReplacement(count);
-				iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid);
-
-				cb->showInfoDialog(&iw);
-				cb->sendAndApply(&sac);
-				cb->addToSlot(StackLocation(h, slot), crs, count);
-			}
-		}
-		else //there no creatures
-		{
-			InfoWindow iw;
-			iw.text.addTxt(MetaString::GENERAL_TXT, 422); //There are no %s here to recruit.
-			iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid);
-			iw.player = h->tempOwner;
-			cb->sendAndApply(&iw);
-		}
-	}
-	else
-	{
-		if(ID == Obj::WAR_MACHINE_FACTORY) //pick available War Machines
-		{
-			//there is 1 war machine available to recruit if hero doesn't have one
-			SetAvailableCreatures sac;
-			sac.tid = id;
-			sac.creatures = creatures;
-			sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista
-			sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent
-			sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart
-			cb->sendAndApply(&sac);
-		}
-
-		OpenWindow ow;
-		ow.id1 = id.getNum();
-		ow.id2 = h->id.getNum();
-		ow.window = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP)
-			? OpenWindow::RECRUITMENT_FIRST
-			: OpenWindow::RECRUITMENT_ALL;
-		cb->sendAndApply(&ow);
-	}
-}
-
-void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if (result.winner == 0)
-	{
-		onHeroVisit(hero);
-	}
-}
-
-void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner());
-	if(stacksCount() > 0  && relations == PlayerRelations::ENEMIES) //guards present
-	{
-		if(answer)
-			cb->startBattleI(hero, this);
-	}
-	else if(answer)
-	{
-		heroAcceptsCreatures(hero);
-	}
-}
-
-int CGTownInstance::getSightRadious() const //returns sight distance
-{
-	if (subID == ETownType::TOWER)
-	{
-		if (hasBuilt(BuildingID::GRAIL)) //skyship
-			return -1; //entire map
-		if (hasBuilt(BuildingID::LOOKOUT_TOWER)) //lookout tower
-			return 20;
-	}
-	return 5;
-}
-
-void CGTownInstance::setPropertyDer(ui8 what, ui32 val)
-{
-///this is freakin' overcomplicated solution
-	switch (what)
-	{
-	case ObjProperty::STRUCTURE_ADD_VISITING_HERO:
-			bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, visitingHero->id.getNum());
-			break;
-		case ObjProperty::STRUCTURE_CLEAR_VISITORS:
-			bonusingBuildings[val]->setProperty (ObjProperty::STRUCTURE_CLEAR_VISITORS, 0);
-			break;
-		case ObjProperty::STRUCTURE_ADD_GARRISONED_HERO: //add garrisoned hero to visitors
-			bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, garrisonHero->id.getNum());
-			break;
-		case ObjProperty::BONUS_VALUE_FIRST:
-			bonusValue.first = val;
-			break;
-		case ObjProperty::BONUS_VALUE_SECOND:
-			bonusValue.second = val;
-			break;
-	}
-}
-CGTownInstance::EFortLevel CGTownInstance::fortLevel() const //0 - none, 1 - fort, 2 - citadel, 3 - castle
-{
-	if (hasBuilt(BuildingID::CASTLE))
-		return CASTLE;
-	if (hasBuilt(BuildingID::CITADEL))
-		return CITADEL;
-	if (hasBuilt(BuildingID::FORT))
-		return FORT;
-	return NONE;
-}
-
-int CGTownInstance::hallLevel() const // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
-{
-	
-	if (hasBuilt(BuildingID::CAPITOL))
-		return 3;
-	if (hasBuilt(BuildingID::CITY_HALL))
-		return 2;
-	if (hasBuilt(BuildingID::TOWN_HALL))
-		return 1;
-	if (hasBuilt(BuildingID::VILLAGE_HALL))
-		return 0;
-	return -1;
-}
-int CGTownInstance::mageGuildLevel() const
-{
-	if (hasBuilt(BuildingID::MAGES_GUILD_5))
-		return 5;
-	if (hasBuilt(BuildingID::MAGES_GUILD_4))
-		return 4;
-	if (hasBuilt(BuildingID::MAGES_GUILD_3))
-		return 3;
-	if (hasBuilt(BuildingID::MAGES_GUILD_2))
-		return 2;
-	if (hasBuilt(BuildingID::MAGES_GUILD_1))
-		return 1;
-	return 0;
-}
-
-int CGTownInstance::getHordeLevel(const int & HID)  const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present
-{
-	return town->hordeLvl.at(HID);
-}
-
-int CGTownInstance::creatureGrowth(const int & level) const
-{
-	return getGrowthInfo(level).totalGrowth();
-}
-
-GrowthInfo CGTownInstance::getGrowthInfo(int level) const
-{
-	GrowthInfo ret;
-
-	if (level<0 || level >=GameConstants::CREATURES_PER_TOWN)
-		return ret;
-	if (creatures[level].second.empty())
-		return ret; //no dwelling
-
-	const CCreature *creature = VLC->creh->creatures[creatures[level].second.back()];
-	const int base = creature->growth;
-	int castleBonus = 0;
-
-	ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[590], base));// \n\nBasic growth %d"
-
-	if (hasBuilt(BuildingID::CASTLE))
-		ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CASTLE, castleBonus = base));
-	else if (hasBuilt(BuildingID::CITADEL))
-		ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CITADEL, castleBonus = base / 2));
-
-	if(town->hordeLvl.at(0) == level)//horde 1
-		if(hasBuilt(BuildingID::HORDE_1))
-			ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_1, creature->hordeGrowth));
-
-	if(town->hordeLvl.at(1) == level)//horde 2
-		if(hasBuilt(BuildingID::HORDE_2))
-			ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_2, creature->hordeGrowth));
-
-	int dwellingBonus = 0;
-	if(const PlayerState *p = cb->getPlayer(tempOwner, false))
-	{
-		for(const CGDwelling *dwelling : p->dwellings)
-			if(vstd::contains(creatures[level].second, dwelling->creatures[0].second[0]))
-				dwellingBonus++;
-	}
-
-	if(dwellingBonus)
-		ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d
-
-	//other *-of-legion-like bonuses (%d to growth cumulative with grail)
-	TBonusListPtr bonuses = getBonuses(Selector::type(Bonus::CREATURE_GROWTH).And(Selector::subtype(level)));
-	for(const Bonus *b : *bonuses)
-		ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val));
-
-	//statue-of-legion-like bonus: % to base+castle
-	TBonusListPtr bonuses2 = getBonuses(Selector::type(Bonus::CREATURE_GROWTH_PERCENT));
-	for(const Bonus *b : *bonuses2)
-		ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val * (base + castleBonus) / 100));
-
-	if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth
-		ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2));
-
-	return ret;
-}
-
-TResources CGTownInstance::dailyIncome() const
-{
-	TResources ret;
-
-	for (auto & p : town->buildings) 
-	{ 
-		BuildingID buildingUpgrade;
-
-		for (auto & p2 : town->buildings) 
-		{ 
-			if (p2.second->upgrade == p.first)
-			{
-				buildingUpgrade = p2.first;
-			}
-		}
-
-		if (!hasBuilt(buildingUpgrade)&&(hasBuilt(p.first)))
-		{
-			ret += p.second->produce;
-		}
-	
-	}
-
-	return ret;
-}
-bool CGTownInstance::hasFort() const
-{
-	return hasBuilt(BuildingID::FORT);
-}
-bool CGTownInstance::hasCapitol() const
-{
-	return hasBuilt(BuildingID::CAPITOL);
-}
-CGTownInstance::CGTownInstance()
-	:IShipyard(this), IMarket(this), town(nullptr), builded(0), destroyed(0), identifier(0), alignment(0xff)
-{
-
-}
-
-CGTownInstance::~CGTownInstance()
-{
-	for (auto & elem : bonusingBuildings)
-		delete elem;
-}
-
-int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const
-{
-	if(checkGuild && mageGuildLevel() < level)
-		return 0;
-	int ret = 6 - level; //how many spells are available at this level
-
-	if (hasBuilt(BuildingID::LIBRARY, ETownType::TOWER))
-		ret++;
-
-	return ret;
-}
-
-bool CGTownInstance::needsLastStack() const
-{
-	if(garrisonHero)
-		return true;
-	else return false;
-}
-
-void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const
-{
-	if( !cb->gameState()->getPlayerRelations( getOwner(), h->getOwner() ))//if this is enemy
-	{
-		if(armedGarrison() || visitingHero)
-		{
-			const CGHeroInstance *defendingHero = nullptr;
-			const CArmedInstance *defendingArmy = this;
-
-			if(visitingHero)
-				defendingHero = visitingHero;
-			else if(garrisonHero)
-				defendingHero = garrisonHero;
-
-			if(defendingHero)
-				defendingArmy = defendingHero;
-
-			bool outsideTown = (defendingHero == visitingHero && garrisonHero);
-
-			//TODO
-			//"borrowing" army from garrison to visiting hero
-
-			cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (outsideTown ? nullptr : this));
-		}
-		else
-		{
-			cb->setOwner(this, h->tempOwner);
-			removeCapitols(h->getOwner());
-			cb->heroVisitCastle(this, h);
-		}
-	}
-	else if(h->visitablePos() == visitablePos())
-	{
-		if (h->commander && !h->commander->alive) //rise commander. TODO: interactive script
-		{
-			SetCommanderProperty scp;
-			scp.heroid = h->id;
-			scp.which = SetCommanderProperty::ALIVE;
-			scp.amount = 1;
-			cb->sendAndApply (&scp);
-		}
-		cb->heroVisitCastle(this, h);
-	}
-	else
-	{
-		logGlobal->errorStream() << h->name << " visits allied town of " << name << " from different pos?";
-	}
-}
-
-void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const
-{
-	cb->stopHeroVisitCastle(this, h);
-}
-
-void CGTownInstance::initObj()
-///initialize town structures
-{
-	blockVisit = true;
-	hoverName = name + ", " + town->faction->name;
-
-	if (subID == ETownType::DUNGEON)
-		creatures.resize(GameConstants::CREATURES_PER_TOWN+1);//extra dwelling for Dungeon
-	else
-		creatures.resize(GameConstants::CREATURES_PER_TOWN);
-	for (int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++)
-	{
-		BuildingID buildID = BuildingID(BuildingID::DWELL_FIRST).advance(level);
-		int upgradeNum = 0;
-
-		for (; town->buildings.count(buildID); upgradeNum++, buildID.advance(GameConstants::CREATURES_PER_TOWN))
-		{
-			if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum)
-				creatures[level].second.push_back(town->creatures[level][upgradeNum]);
-		}
-	}
-
-	switch (subID)
-	{ //add new visitable objects
-		case 0:
-			bonusingBuildings.push_back (new COPWBonus(BuildingID::STABLES, this));
-			break;
-		case 5:
-			bonusingBuildings.push_back (new COPWBonus(BuildingID::MANA_VORTEX, this));
-			//fallthrough
-		case 2: case 3: case 6:
-			bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_4, this));
-			break;
-		case 7:
-			bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_1, this));
-			break;
-	}
-	//add special bonuses from buildings
-
-	recreateBuildingsBonuses();
-}
-
-void CGTownInstance::newTurn() const
-{
-	if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week
-	{
-		auto & rand = cb->gameState()->getRandomGenerator();
-
-		//give resources for Rampart, Mystic Pond
-		if (hasBuilt(BuildingID::MYSTIC_POND, ETownType::RAMPART)
-			&& cb->getDate(Date::DAY) != 1 && (tempOwner < PlayerColor::PLAYER_LIMIT))
-		{
-			int resID = rand.nextInt(2, 5); //bonus to random rare resource
-			resID = (resID==2)?1:resID;
-			int resVal = rand.nextInt(1, 4);//with size 1..4
-			cb->giveResource(tempOwner, static_cast<Res::ERes>(resID), resVal);
-			cb->setObjProperty (id, ObjProperty::BONUS_VALUE_FIRST, resID);
-			cb->setObjProperty (id, ObjProperty::BONUS_VALUE_SECOND, resVal);
-		}
-
-		if ( subID == ETownType::DUNGEON )
-			for (auto & elem : bonusingBuildings)
-		{
-			if ((elem)->ID == BuildingID::MANA_VORTEX)
-				cb->setObjProperty (id, ObjProperty::STRUCTURE_CLEAR_VISITORS, (elem)->id); //reset visitors for Mana Vortex
-		}
-
-		if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns
-			{
-				std::vector<SlotID> nativeCrits; //slots
-				for (auto & elem : Slots())
-				{
-					if (elem.second->type->faction == subID) //native
-					{
-						nativeCrits.push_back(elem.first); //collect matching slots
-					}
-				}
-				if (nativeCrits.size())
-				{
-					SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand);
-					StackLocation sl(this, pos);
-
-					const CCreature *c = getCreature(pos);
-					if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available
-					{
-						cb->changeStackCount(sl, c->growth);
-					}
-					else //upgrade
-					{
-						cb->changeStackType(sl, VLC->creh->creatures[*c->upgrades.begin()]);
-					}
-				}
-				if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack
-				{
-					int i = rand.nextInt(std::min(GameConstants::CREATURES_PER_TOWN, cb->getDate(Date::MONTH) << 1) - 1);
-					if (!town->creatures[i].empty())
-					{
-						CreatureID c = town->creatures[i][0];
-						SlotID n;
-
-						TQuantity count = creatureGrowth(i);
-						if (!count) // no dwelling
-							count = VLC->creh->creatures[c]->growth;
-
-						{//no lower tiers or above current month
-
-							if ((n = getSlotFor(c)).validSlot())
-							{
-								StackLocation sl(this, n);
-								if (slotEmpty(n))
-									cb->insertNewStack(sl, VLC->creh->creatures[c], count);
-								else //add to existing
-									cb->changeStackCount(sl, count);
-							}
-						}
-					}
-				}
-			}
-	}
-}
-
-int3 CGTownInstance::getSightCenter() const
-{
-	return pos - int3(2,0,0);
-}
-
-ui8 CGTownInstance::getPassableness() const
-{
-	if (!armedGarrison())//empty castle - anyone can visit
-		return GameConstants::ALL_PLAYERS;
-	if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit
-		return 0;
-
-	ui8 mask = 0;
-	TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner);
-	for(PlayerColor it : ts->players)
-		mask |= 1<<it.getNum();//allies - add to possible visitors
-
-	return mask;
-}
-
-void CGTownInstance::getOutOffsets( std::vector<int3> &offsets ) const
-{
-	offsets += int3(-1,2,0), int3(-3,2,0);
-}
-
-void CGTownInstance::removeCapitols (PlayerColor owner) const
-{
-	if (hasCapitol()) // search if there's an older capitol
-	{
-		PlayerState* state = cb->gameState()->getPlayer (owner); //get all towns owned by player
-		for (auto i = state->towns.cbegin(); i < state->towns.cend(); ++i)
-		{
-			if (*i != this && (*i)->hasCapitol())
-			{
-				RazeStructures rs;
-				rs.tid = id;
-				rs.bid.insert(BuildingID::CAPITOL);
-				rs.destroyed = destroyed;
-				cb->sendAndApply(&rs);
-				return;
-			}
-		}
-	}
-}
-
-int CGTownInstance::getBoatType() const
-{
-	switch (town->faction->alignment)
-	{
-	case EAlignment::EVIL : return 0;
-	case EAlignment::GOOD : return 1;
-	case EAlignment::NEUTRAL : return 2;
-	}
-	assert(0);
-	return -1;
-}
-
-int CGTownInstance::getMarketEfficiency() const
-{
-	if (!hasBuilt(BuildingID::MARKETPLACE))
-		return 0;
-
-	const PlayerState *p = cb->getPlayer(tempOwner);
-	assert(p);
-
-	int marketCount = 0;
-	for(const CGTownInstance *t : p->towns)
-		if(t->hasBuilt(BuildingID::MARKETPLACE))
-			marketCount++;
-
-	return marketCount;
-}
-
-bool CGTownInstance::allowsTrade(EMarketMode::EMarketMode mode) const
-{
-	switch(mode)
-	{
-	case EMarketMode::RESOURCE_RESOURCE:
-	case EMarketMode::RESOURCE_PLAYER:
-		return hasBuilt(BuildingID::MARKETPLACE);
-
-	case EMarketMode::ARTIFACT_RESOURCE:
-	case EMarketMode::RESOURCE_ARTIFACT:
-		return hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::TOWER)
-		    || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::DUNGEON)
-		    || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::CONFLUX);
-
-	case EMarketMode::CREATURE_RESOURCE:
-		return hasBuilt(BuildingID::FREELANCERS_GUILD, ETownType::STRONGHOLD);
-
-	case EMarketMode::CREATURE_UNDEAD:
-		return hasBuilt(BuildingID::SKELETON_TRANSFORMER, ETownType::NECROPOLIS);
-
-	case EMarketMode::RESOURCE_SKILL:
-		return hasBuilt(BuildingID::MAGIC_UNIVERSITY, ETownType::CONFLUX);
-	default:
-		assert(0);
-		return false;
-	}
-}
-
-std::vector<int> CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode) const
-{
-	if(mode == EMarketMode::RESOURCE_ARTIFACT)
-	{
-		std::vector<int> ret;
-		for(const CArtifact *a : merchantArtifacts)
-			if(a)
-				ret.push_back(a->id);
-			else
-				ret.push_back(-1);
-		return ret;
-	}
-	else if ( mode == EMarketMode::RESOURCE_SKILL )
-	{
-		return universitySkills;
-	}
-	else
-		return IMarket::availableItemsIds(mode);
-}
-
-void CGTownInstance::setType(si32 ID, si32 subID)
-{
-	assert(ID == Obj::TOWN); // just in case
-	CGObjectInstance::setType(ID, subID);
-	town = VLC->townh->factions[subID]->town;
-	randomizeArmy(subID);
-	updateAppearance();
-}
-
-void CGTownInstance::updateAppearance()
-{
-	if (!hasFort())
-		appearance.animationFile = town->clientInfo.advMapVillage;
-	else if(hasCapitol())
-		appearance.animationFile = town->clientInfo.advMapCapitol;
-	else
-		appearance.animationFile = town->clientInfo.advMapCastle;
-}
-
-std::string CGTownInstance::nodeName() const
-{
-	return "Town (" + (town ? town->faction->name : "unknown") + ") of " +  name;
-}
-
-void CGTownInstance::deserializationFix()
-{
-	attachTo(&townAndVis);
-
-	//Hero is already handled by CGameState::attachArmedObjects
-
-// 	if(visitingHero)
-// 		visitingHero->attachTo(&townAndVis);
-// 	if(garrisonHero)
-// 		garrisonHero->attachTo(this);
-}
-
-void CGTownInstance::updateMoraleBonusFromArmy()
-{
-	Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE)));
-	if(!b)
-	{
-		b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1);
-		addNewBonus(b);
-	}
-
-	if (garrisonHero)
-		b->val = 0;
-	else
-		CArmedInstance::updateMoraleBonusFromArmy();
-}
-
-void CGTownInstance::recreateBuildingsBonuses()
-{
-	static TPropagatorPtr playerProp(new CPropagatorNodeType(PLAYER));
-
-	BonusList bl;
-	getExportedBonusList().getBonuses(bl, Selector::sourceType(Bonus::TOWN_STRUCTURE));
-	for(Bonus *b : bl)
-		removeBonus(b);
-
-	//tricky! -> checks tavern only if no bratherhood of sword or not a castle
-	if(subID != ETownType::CASTLE || !addBonusIfBuilt(BuildingID::BROTHERHOOD, Bonus::MORALE, +2))
-		addBonusIfBuilt(BuildingID::TAVERN, Bonus::MORALE, +1);
-
-	if(subID == ETownType::CASTLE) //castle
-	{
-		addBonusIfBuilt(BuildingID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp);
-		addBonusIfBuilt(BuildingID::GRAIL,      Bonus::MORALE, +2, playerProp); //colossus
-	}
-	else if(subID == ETownType::RAMPART) //rampart
-	{
-		addBonusIfBuilt(BuildingID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::LUCK, +2, playerProp); //guardian spirit
-	}
-	else if(subID == ETownType::TOWER) //tower
-	{
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail
-	}
-	else if(subID == ETownType::INFERNO) //Inferno
-	{
-		addBonusIfBuilt(BuildingID::STORMCLOUDS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER); //Brimstone Clouds
-	}
-	else if(subID == ETownType::NECROPOLIS) //necropolis
-	{
-		addBonusIfBuilt(BuildingID::COVER_OF_DARKNESS,    Bonus::DARKNESS, +20);
-		addBonusIfBuilt(BuildingID::NECROMANCY_AMPLIFIER, Bonus::SECONDARY_SKILL_PREMY, +10, playerProp, SecondarySkill::NECROMANCY); //necromancy amplifier
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::SECONDARY_SKILL_PREMY, +20, playerProp, SecondarySkill::NECROMANCY); //Soul prison
-	}
-	else if(subID == ETownType::DUNGEON) //Dungeon
-	{
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail
-	}
-	else if(subID == ETownType::STRONGHOLD) //Stronghold
-	{
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail
-	}
-	else if(subID == ETownType::FORTRESS) //Fortress
-	{
-		addBonusIfBuilt(BuildingID::GLYPHS_OF_FEAR, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE); //Glyphs of Fear
-		addBonusIfBuilt(BuildingID::BLOOD_OBELISK,  Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK); //Blood Obelisk
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail
-		addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail
-	}
-	else if(subID == ETownType::CONFLUX)
-	{
-
-	}
-}
-
-bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype /*= -1*/)
-{
-	static auto emptyPropagator = TPropagatorPtr();
-	return addBonusIfBuilt(building, type, val, emptyPropagator, subtype);
-}
-
-bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype /*= -1*/)
-{
-	if(hasBuilt(building))
-	{
-		std::ostringstream descr;
-		descr << town->buildings.at(building)->Name() << " ";
-		if(val > 0)
-			descr << "+";
-		else if(val < 0)
-			descr << "-";
-		descr << val;
-
-		Bonus *b = new Bonus(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, descr.str(), subtype);
-		if(prop)
-			b->addPropagator(prop);
-		addNewBonus(b);
-		return true;
-	}
-
-	return false;
-}
-
-void CGTownInstance::setVisitingHero(CGHeroInstance *h)
-{
-	assert(!!visitingHero == !h);
-	if(h)
-	{
-		PlayerState *p = cb->gameState()->getPlayer(h->tempOwner);
-		assert(p);
-		h->detachFrom(p);
-		h->attachTo(&townAndVis);
-		visitingHero = h;
-		h->visitedTown = this;
-		h->inTownGarrison = false;
-	}
-	else
-	{
-		PlayerState *p = cb->gameState()->getPlayer(visitingHero->tempOwner);
-		visitingHero->visitedTown = nullptr;
-		visitingHero->detachFrom(&townAndVis);
-		visitingHero->attachTo(p);
-		visitingHero = nullptr;
-	}
-}
-
-void CGTownInstance::setGarrisonedHero(CGHeroInstance *h)
-{
-	assert(!!garrisonHero == !h);
-	if(h)
-	{
-		PlayerState *p = cb->gameState()->getPlayer(h->tempOwner);
-		assert(p);
-		h->detachFrom(p);
-		h->attachTo(this);
-		garrisonHero = h;
-		h->visitedTown = this;
-		h->inTownGarrison = true;
-	}
-	else
-	{
-		PlayerState *p = cb->gameState()->getPlayer(garrisonHero->tempOwner);
-		garrisonHero->visitedTown = nullptr;
-		garrisonHero->inTownGarrison = false;
-		garrisonHero->detachFrom(this);
-		garrisonHero->attachTo(p);
-		garrisonHero = nullptr;
-	}
-	updateMoraleBonusFromArmy(); //avoid giving morale bonus for same army twice
-}
-
-bool CGTownInstance::armedGarrison() const
-{
-	return stacksCount() || garrisonHero;
-}
-
-int CGTownInstance::getTownLevel() const
-{
-	// count all buildings that are not upgrades
-	return boost::range::count_if(builtBuildings, [&](const BuildingID & build)
-	{
-		return town->buildings.at(build) && town->buildings.at(build)->upgrade == -1;
-	});
-}
-
-CBonusSystemNode * CGTownInstance::whatShouldBeAttached()
-{
-	return &townAndVis;
-}
-
-const CArmedInstance * CGTownInstance::getUpperArmy() const
-{
-	if(garrisonHero)
-		return garrisonHero;
-	return this;
-}
-
-bool CGTownInstance::hasBuilt(BuildingID buildingID, int townID) const
-{
-	if (townID == town->faction->index || townID == ETownType::ANY)
-		return hasBuilt(buildingID);
-	return false;
-}
-
-bool CGTownInstance::hasBuilt(BuildingID buildingID) const
-{
-	return vstd::contains(builtBuildings, buildingID);
-}
-
-void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 structureInstanceID ) const
-{
-	if(visitingHero == h)
-		cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_VISITING_HERO, structureInstanceID); //add to visitors
-	else if(garrisonHero == h)
-		cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_GARRISONED_HERO, structureInstanceID); //then it must be garrisoned hero
-	else
-	{
-		//should never ever happen
-        logGlobal->errorStream() << "Cannot add hero " << h->name << " to visitors of structure #" << structureInstanceID;
-		assert(0);
-	}
-}
-
-void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if(result.winner == 0)
-	{
-		removeCapitols(hero->getOwner());
-		cb->setOwner (this, hero->tempOwner); //give control after checkout is done
-		FoWChange fw;
-		fw.player = hero->tempOwner;
-		fw.mode = 1;
-		getSightTiles (fw.tiles); //update visibility for castle structures
-		cb->sendAndApply (&fw);
-	}
-}
-
-COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN)
-{
-	ID = index;
-	town = TOWN;
-	id = town->bonusingBuildings.size();
-}
-void COPWBonus::setProperty(ui8 what, ui32 val)
-{
-	switch (what)
-	{
-		case ObjProperty::VISITORS:
-			visitors.insert(val);
-			break;
-		case ObjProperty::STRUCTURE_CLEAR_VISITORS:
-			visitors.clear();
-			break;
-	}
-}
-void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
-{
-	ObjectInstanceID heroID = h->id;
-	if (town->hasBuilt(ID))
-	{
-		InfoWindow iw;
-		iw.player = h->tempOwner;
-		switch (town->subID)
-		{
-			case ETownType::CASTLE: //Stables
-				if (!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables
-				{
-					GiveBonus gb;
-					gb.bonus = Bonus(Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, Bonus::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100]);
-					gb.id = heroID.getNum();
-					cb->giveHeroBonus(&gb);
-					iw.text << VLC->generaltexth->allTexts[580];
-					cb->showInfoDialog(&iw);
-				}
-				break;
-			case ETownType::DUNGEON: //Mana Vortex
-				if (visitors.empty() && h->mana <= h->manaLimit() * 2)
-				{
-					cb->setManaPoints (heroID, 2 * h->manaLimit());
-					//TODO: investigate line below
-					//cb->setObjProperty (town->id, ObjProperty::VISITED, true);
-					iw.text << VLC->generaltexth->allTexts[579];
-					cb->showInfoDialog(&iw);
-					town->addHeroToStructureVisitors(h, id);
-				}
-				break;
-		}
-	}
-}
-CTownBonus::CTownBonus (BuildingID index, CGTownInstance *TOWN)
-{
-	ID = index;
-	town = TOWN;
-	id = town->bonusingBuildings.size();
-}
-void CTownBonus::setProperty (ui8 what, ui32 val)
-{
-	if(what == ObjProperty::VISITORS)
-		visitors.insert(ObjectInstanceID(val));
-}
-void CTownBonus::onHeroVisit (const CGHeroInstance * h) const
-{
-	ObjectInstanceID heroID = h->id;
-	if (town->hasBuilt(ID) && visitors.find(heroID) == visitors.end())
-	{
-		InfoWindow iw;
-		PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK;
-		int val=0, mid=0;
-		switch (ID)
-		{
-			case BuildingID::SPECIAL_4:
-				switch(town->subID)
-				{
-					case ETownType::TOWER: //wall
-						what = PrimarySkill::KNOWLEDGE;
-						val = 1;
-						mid = 581;
-						iw.components.push_back (Component(Component::PRIM_SKILL, 3, 1, 0));
-						break;
-					case ETownType::INFERNO: //order of fire
-						what = PrimarySkill::SPELL_POWER;
-						val = 1;
-						mid = 582;
-						iw.components.push_back (Component(Component::PRIM_SKILL, 2, 1, 0));
-						break;
-					case ETownType::STRONGHOLD://hall of Valhalla
-						what = PrimarySkill::ATTACK;
-						val = 1;
-						mid = 584;
-						iw.components.push_back (Component(Component::PRIM_SKILL, 0, 1, 0));
-						break;
-					case ETownType::DUNGEON://academy of battle scholars
-						what = PrimarySkill::EXPERIENCE;
-						val = h->calculateXp(1000);
-						mid = 583;
-						iw.components.push_back (Component(Component::EXPERIENCE, 0, val, 0));
-						break;
-				}
-				break;
-			case BuildingID::SPECIAL_1:
-				switch(town->subID)
-				{
-					case ETownType::FORTRESS: //cage of warlords
-						what = PrimarySkill::DEFENSE;
-						val = 1;
-						mid = 585;
-						iw.components.push_back (Component(Component::PRIM_SKILL, 1, 1, 0));
-						break;
-				}
-				break;
-		}
-		assert(mid);
-		iw.player = cb->getOwner(heroID);
-		iw.text << VLC->generaltexth->allTexts[mid];
-		cb->showInfoDialog(&iw);
-		cb->changePrimSkill (cb->getHero(heroID), what, val);
-		town->addHeroToStructureVisitors(h, id);
-	}
-}
-const std::string & CGCreature::getHoverText() const
-{
-	if(stacks.empty())
-	{
-		static const std::string errorValue("!!!INVALID_STACK!!!");
-
-		//should not happen...
-		logGlobal->errorStream() << "Invalid stack at tile " << pos << ": subID=" << subID << "; id=" << id;
-		return errorValue; // references to temporary are illegal - use pre-constructed string
-	}
-
-	MetaString ms;
-	int pom = stacks.begin()->second->getQuantityID();
-	pom = 172 + 3*pom;
-	ms.addTxt(MetaString::ARRAY_TXT,pom);
-	ms << " " ;
-	ms.addTxt(MetaString::CRE_PL_NAMES,subID);
-	ms.toString(hoverName);
-
-	if(const CGHeroInstance *selHero = cb->getSelectedHero(cb->getCurrentPlayer()))
-	{
-		const JsonNode & texts = VLC->generaltexth->localizedTexts["adventureMap"]["monsterThreat"];
-
-		hoverName += texts["title"].String();
-		int choice;
-		double ratio = ((double)getArmyStrength() / selHero->getTotalStrength());
-		     if (ratio < 0.1)  choice = 0;
-		else if (ratio < 0.25) choice = 1;
-		else if (ratio < 0.6)  choice = 2;
-		else if (ratio < 0.9)  choice = 3;
-		else if (ratio < 1.1)  choice = 4;
-		else if (ratio < 1.3)  choice = 5;
-		else if (ratio < 1.8)  choice = 6;
-		else if (ratio < 2.5)  choice = 7;
-		else if (ratio < 4)    choice = 8;
-		else if (ratio < 8)    choice = 9;
-		else if (ratio < 20)   choice = 10;
-		else                   choice = 11;
-		hoverName += texts["levels"].Vector()[choice].String();
-	}
-	return hoverName;
-}
-void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
-{
-	int action = takenAction(h);
-	switch( action ) //decide what we do...
-	{
-	case FIGHT:
-		fight(h);
-		break;
-	case FLEE: //flee
-		{
-			flee(h);
-			break;
-		}
-	case JOIN_FOR_FREE: //join for free
-		{
-			BlockingDialog ynd(true,false);
-			ynd.player = h->tempOwner;
-			ynd.text.addTxt(MetaString::ADVOB_TXT, 86);
-			ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID);
-			cb->showBlockingDialog(&ynd);
-			break;
-		}
-	default: //join for gold
-		{
-			assert(action > 0);
-
-			//ask if player agrees to pay gold
-			BlockingDialog ynd(true,false);
-			ynd.player = h->tempOwner;
-			std::string tmp = VLC->generaltexth->advobtxt[90];
-			boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast<std::string>(getStackCount(SlotID(0))));
-			boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast<std::string>(action));
-			boost::algorithm::replace_first(tmp,"%s",VLC->creh->creatures[subID]->namePl);
-			ynd.text << tmp;
-			cb->showBlockingDialog(&ynd);
-			break;
-		}
-	}
-
-}
-
-void CGCreature::initObj()
-{
-	blockVisit = true;
-	switch(character)
-	{
-	case 0:
-		character = -4;
-		break;
-	case 1:
-		character = cb->gameState()->getRandomGenerator().nextInt(1, 7);
-		break;
-	case 2:
-		character = cb->gameState()->getRandomGenerator().nextInt(1, 10);
-		break;
-	case 3:
-		character = cb->gameState()->getRandomGenerator().nextInt(4, 10);
-		break;
-	case 4:
-		character = 10;
-		break;
-	}
-
-	stacks[SlotID(0)]->setType(CreatureID(subID));
-	TQuantity &amount = stacks[SlotID(0)]->count;
-	CCreature &c = *VLC->creh->creatures[subID];
-	if(amount == 0)
-	{
-		amount = cb->gameState()->getRandomGenerator().nextInt(c.ammMin, c.ammMax);
-
-		if(amount == 0) //armies with 0 creatures are illegal
-		{
-            logGlobal->warnStream() << "Problem: stack " << nodeName() << " cannot have 0 creatures. Check properties of " << c.nodeName();
-			amount = 1;
-		}
-	}
-	formation.randomFormation = cb->gameState()->getRandomGenerator().nextInt();
-
-	temppower = stacks[SlotID(0)]->count * 1000;
-	refusedJoining = false;
-}
-
-void CGCreature::newTurn() const
-{//Works only for stacks of single type of size up to 2 millions
-	if (stacks.begin()->second->count < VLC->modh->settings.CREEP_SIZE && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1)
-	{
-		ui32 power = temppower * (100 +  VLC->modh->settings.WEEKLY_GROWTH)/100;
-		cb->setObjProperty(id, ObjProperty::MONSTER_COUNT, std::min (power/1000 , (ui32)VLC->modh->settings.CREEP_SIZE)); //set new amount
-		cb->setObjProperty(id, ObjProperty::MONSTER_POWER, power); //increase temppower
-	}
-	if (VLC->modh->modules.STACK_EXP)
-		cb->setObjProperty(id, ObjProperty::MONSTER_EXP, VLC->modh->settings.NEUTRAL_STACK_EXP); //for testing purpose
-}
-void CGCreature::setPropertyDer(ui8 what, ui32 val)
-{
-	switch (what)
-	{
-		case ObjProperty::MONSTER_COUNT:
-			stacks[SlotID(0)]->count = val;
-			break;
-		case ObjProperty::MONSTER_POWER:
-			temppower = val;
-			break;
-		case ObjProperty::MONSTER_EXP:
-			giveStackExp(val);
-			break;
-		case ObjProperty::MONSTER_RESTORE_TYPE:
-			formation.basicType = val;
-			break;
-		case ObjProperty::MONSTER_REFUSED_JOIN:
-			refusedJoining = val;
-			break;
-	}
-}
-
-int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
-{
-	//calculate relative strength of hero and creatures armies
-	double relStrength = double(h->getTotalStrength()) / getArmyStrength();
-
-	int powerFactor;
-	if(relStrength >= 7)
-		powerFactor = 11;
-
-	else if(relStrength >= 1)
-		powerFactor = (int)(2*(relStrength-1));
-
-	else if(relStrength >= 0.5)
-		powerFactor = -1;
-
-	else if(relStrength >= 0.333)
-		powerFactor = -2;
-	else
-		powerFactor = -3;
-
-	std::set<CreatureID> myKindCres; //what creatures are the same kind as we
-	const CCreature * myCreature = VLC->creh->creatures[subID];
-	myKindCres.insert(myCreature->idNumber); //we
-	myKindCres.insert(myCreature->upgrades.begin(), myCreature->upgrades.end()); //our upgrades
-
-	for(ConstTransitivePtr<CCreature> &crea : VLC->creh->creatures)
-	{
-		if(vstd::contains(crea->upgrades, myCreature->idNumber)) //it's our base creatures
-			myKindCres.insert(crea->idNumber);
-	}
-
-	int count = 0, //how many creatures of similar kind has hero
-		totalCount = 0;
-
-	for (auto & elem : h->Slots())
-	{
-		if(vstd::contains(myKindCres,elem.second->type->idNumber))
-			count += elem.second->count;
-		totalCount += elem.second->count;
-	}
-
-	int sympathy = 0; // 0 if hero have no similar creatures
-	if(count)
-		sympathy++; // 1 - if hero have at least 1 similar creature
-	if(count*2 > totalCount)
-		sympathy++; // 2 - hero have similar creatures more that 50%
-
-	int charisma = powerFactor + h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy;
-
-	if(charisma < character) //creatures will fight
-		return -2;
-
-	if (allowJoin)
-	{
-		if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy + 1 >= character)
-			return 0; //join for free
-
-		else if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) * 2  +  sympathy  +  1 >= character)
-			return VLC->creh->creatures[subID]->cost[6] * getStackCount(SlotID(0)); //join for gold
-	}
-
-	//we are still here - creatures have not joined hero, flee or fight
-
-	if (charisma > character)
-		return -1; //flee
-	else
-		return -2; //fight
-}
-
-void CGCreature::fleeDecision(const CGHeroInstance *h, ui32 pursue) const
-{
-	if(refusedJoining)
-		cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, false);
-
-	if(pursue)
-	{
-		fight(h);
-	}
-	else
-	{
-		cb->removeObject(this);
-	}
-}
-
-void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const
-{
-	if(!accept)
-	{
-		if(takenAction(h,false) == -1) //they flee
-		{
-			cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, true);
-			flee(h);
-		}
-		else //they fight
-		{
-			showInfoDialog(h,87,0);//Insulted by your refusal of their offer, the monsters attack!
-			fight(h);
-		}
-	}
-	else //accepted
-	{
-		if (cb->getResource(h->tempOwner, Res::GOLD) < cost) //player don't have enough gold!
-		{
-			InfoWindow iw;
-			iw.player = h->tempOwner;
-			iw.text << std::pair<ui8,ui32>(1,29);  //You don't have enough gold
-			cb->showInfoDialog(&iw);
-
-			//act as if player refused
-			joinDecision(h,cost,false);
-			return;
-		}
-
-		//take gold
-		if(cost)
-			cb->giveResource(h->tempOwner,Res::GOLD,-cost);
-
-		cb->tryJoiningArmy(this, h, true, true);
-	}
-}
-
-void CGCreature::fight( const CGHeroInstance *h ) const
-{
-	//split stacks
-	//TODO: multiple creature types in a stack?
-	int basicType = stacks.begin()->second->type->idNumber;
-	cb->setObjProperty(id, ObjProperty::MONSTER_RESTORE_TYPE, basicType); //store info about creature stack
-
-	double relativePower = static_cast<double>(h->getTotalStrength()) / getArmyStrength();
-	int stacksCount;
-	//TODO: number depends on tile type
-	if (relativePower < 0.5)
-	{
-		stacksCount = 7;
-	}
-	else if (relativePower < 0.67)
-	{
-		stacksCount = 7;
-	}
-	else if (relativePower < 1)
-	{
-		stacksCount = 6;
-	}
-	else if (relativePower < 1.5)
-	{
-		stacksCount = 5;
-	}
-	else if (relativePower < 2)
-	{
-		stacksCount = 4;
-	}
-	else
-	{
-		stacksCount = 3;
-	}
-	SlotID sourceSlot = stacks.begin()->first;
-	SlotID destSlot;
-	for (int stacksLeft = stacksCount; stacksLeft > 1; --stacksLeft)
-	{
-		int stackSize = stacks.begin()->second->count / stacksLeft;
-		if (stackSize)
-		{
-			if ((destSlot = getFreeSlot()).validSlot())
-				cb->moveStack(StackLocation(this, sourceSlot), StackLocation(this, destSlot), stackSize);
-			else
-			{
-                logGlobal->warnStream() <<"Warning! Not enough empty slots to split stack!";
-				break;
-			}
-		}
-		else break;
-	}
-	if (stacksCount > 1)
-	{
-		if (formation.randomFormation % 100 < 50) //upgrade
-		{
-			SlotID slotId = SlotID(stacks.size() / 2);
-			const auto & upgrades = getStack(slotId).type->upgrades;
-			if(!upgrades.empty())
-			{
-				auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator());
-				cb->changeStackType(StackLocation(this, slotId), VLC->creh->creatures[*it]);
-			}
-		}
-	}
-
-	cb->startBattleI(h, this);
-
-}
-
-void CGCreature::flee( const CGHeroInstance * h ) const
-{
-	BlockingDialog ynd(true,false);
-	ynd.player = h->tempOwner;
-	ynd.text.addTxt(MetaString::ADVOB_TXT,91);
-	ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID);
-	cb->showBlockingDialog(&ynd);
-}
-
-void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-
-	if(result.winner==0)
-	{
-		cb->removeObject(this);
-	}
-	else
-	{
-		//int killedAmount=0;
-		//for(std::set<std::pair<ui32,si32> >::iterator i=result->casualties[1].begin(); i!=result->casualties[1].end(); i++)
-		//	if(i->first == subID)
-		//		killedAmount += i->second;
-		//cb->setAmount(id, slots.find(0)->second.second - killedAmount);
-
-		/*
-		MetaString ms;
-		int pom = slots.find(0)->second.getQuantityID();
-		pom = 174 + 3*pom + 1;
-		ms << std::pair<ui8,ui32>(6,pom) << " " << std::pair<ui8,ui32>(7,subID);
-		cb->setHoverName(id,&ms);
-		cb->setObjProperty(id, 11, slots.begin()->second.count * 1000);
-		*/
-
-		//merge stacks into one
-		TSlots::const_iterator i;
-		CCreature * cre = VLC->creh->creatures[formation.basicType];
-		for (i = stacks.begin(); i != stacks.end(); i++)
-		{
-			if (cre->isMyUpgrade(i->second->type))
-			{
-				cb->changeStackType (StackLocation(this, i->first), cre); //un-upgrade creatures
-			}
-		}
-
-		//first stack has to be at slot 0 -> if original one got killed, move there first remaining stack
-		if(!hasStackAtSlot(SlotID(0)))
-			cb->moveStack(StackLocation(this, stacks.begin()->first), StackLocation(this, SlotID(0)), stacks.begin()->second->count);
-
-		while (stacks.size() > 1) //hopefully that's enough
-		{
-			// TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere)
-			i = stacks.end();
-			i--;
-			SlotID slot = getSlotFor(i->second->type);
-			if (slot == i->first) //no reason to move stack to its own slot
-				break;
-			else
-				cb->moveStack (StackLocation(this, i->first), StackLocation(this, slot), i->second->count);
-		}
-
-		cb->setObjProperty(id, ObjProperty::MONSTER_POWER, stacks.begin()->second->count * 1000); //remember casualties
-	}
-}
-
-void CGCreature::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	auto action = takenAction(hero);
-	if(!refusedJoining && action >= JOIN_FOR_FREE) //higher means price
-		joinDecision(hero, action, answer);
-	else if(action != FIGHT)
-		fleeDecision(hero, answer);
-	else
-		assert(0);
-}
-
-void CGMine::onHeroVisit( const CGHeroInstance * h ) const
-{
-	int relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);
-
-	if(relations == 2) //we're visiting our mine
-	{
-		cb->showGarrisonDialog(id,h->id,true);
-		return;
-	}
-	else if (relations == 1)//ally
-		return;
-
-	if(stacksCount()) //Mine is guarded
-	{
-		BlockingDialog ynd(true,false);
-		ynd.player = h->tempOwner;
-		ynd.text.addTxt(MetaString::ADVOB_TXT, subID == 7 ? 84 : 187);
-		cb->showBlockingDialog(&ynd);
-		return;
-	}
-
-	flagMine(h->tempOwner);
-
-}
-
-void CGMine::newTurn() const
-{
-	if(cb->getDate() == 1)
-		return;
-
-	if (tempOwner == PlayerColor::NEUTRAL)
-		return;
-
-	cb->giveResource(tempOwner, producedResource, producedQuantity);
-}
-
-void CGMine::initObj()
-{
-	if(subID >= 7) //Abandoned Mine
-	{
-		//set guardians
-		int howManyTroglodytes = cb->gameState()->getRandomGenerator().nextInt(100, 199);
-		auto troglodytes = new CStackInstance(CreatureID::TROGLODYTES, howManyTroglodytes);
-		putStack(SlotID(0), troglodytes);
-
-		//after map reading tempOwner placeholds bitmask for allowed resources
-		std::vector<Res::ERes> possibleResources;
-		for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
-			if(tempOwner.getNum() & 1<<i) //NOTE: reuse of tempOwner
-				possibleResources.push_back(static_cast<Res::ERes>(i));
-
-		assert(!possibleResources.empty());
-		producedResource = *RandomGeneratorUtil::nextItem(possibleResources, cb->gameState()->getRandomGenerator());
-		tempOwner = PlayerColor::NEUTRAL;
-		hoverName = VLC->generaltexth->mines[7].first + "\n" + VLC->generaltexth->allTexts[202] + " " + troglodytes->getQuantityTXT(false) + " " + troglodytes->type->namePl;
-	}
-	else
-	{
-		producedResource = static_cast<Res::ERes>(subID);
-
-		MetaString ms;
-		ms << std::pair<ui8,ui32>(9,producedResource);
-		if(tempOwner >= PlayerColor::PLAYER_LIMIT)
-			tempOwner = PlayerColor::NEUTRAL;
-		else
-			ms << " (" << std::pair<ui8,ui32>(6,23+tempOwner.getNum()) << ")";
-		ms.toString(hoverName);
-	}
-
-	producedQuantity = defaultResProduction();
-}
-
-void CGMine::flagMine(PlayerColor player) const
-{
-	assert(tempOwner != player);
-	cb->setOwner(this, player); //not ours? flag it!
-
-	MetaString ms;
-	ms << std::pair<ui8,ui32>(9,subID) << "\n(" << std::pair<ui8,ui32>(6,23+player.getNum()) << ")";
-	if(subID == 7)
-	{
-		ms << "(%s)";
-		ms.addReplacement(MetaString::RES_NAMES, producedResource);
-	}
-	cb->setHoverName(this,&ms);
-
-	InfoWindow iw;
-	iw.soundID = soundBase::FLAGMINE;
-	iw.text.addTxt(MetaString::MINE_EVNTS,producedResource); //not use subID, abandoned mines uses default mine texts
-	iw.player = player;
-	iw.components.push_back(Component(Component::RESOURCE,producedResource,producedQuantity,-1));
-	cb->showInfoDialog(&iw);
-}
-
-ui32 CGMine::defaultResProduction()
-{
-	switch(producedResource)
-	{
-	case Res::WOOD:
-	case Res::ORE:
-		return 2;
-	case Res::GOLD:
-		return 1000;
-	default:
-		return 1;
-	}
-}
-
-void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if(result.winner == 0) //attacker won
-	{
-		if(subID == 7)
-		{
-			showInfoDialog(hero->tempOwner, 85, 0);
-		}
-		flagMine(hero->tempOwner);
-	}
-}
-
-void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	if(answer)
-		cb->startBattleI(hero, this);
-}
-
-void CGResource::initObj()
-{
-	blockVisit = true;
-	hoverName = VLC->generaltexth->restypes[subID];
-
-	if(!amount)
-	{
-		switch(subID)
-		{
-		case 6:
-			amount = cb->gameState()->getRandomGenerator().nextInt(500, 1000);
-			break;
-		case 0: case 2:
-			amount = cb->gameState()->getRandomGenerator().nextInt(6, 10);
-			break;
-		default:
-			amount = cb->gameState()->getRandomGenerator().nextInt(3, 5);
-			break;
-		}
-	}
-}
-
-void CGResource::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if(stacksCount())
-	{
-		if(message.size())
-		{
-			BlockingDialog ynd(true,false);
-			ynd.player = h->getOwner();
-			ynd.text << message;
-			cb->showBlockingDialog(&ynd);
-		}
-		else
-		{
-			blockingDialogAnswered(h, true); //behave as if player accepted battle
-		}
-	}
-	else
-	{
-		if(message.length())
-		{
-			InfoWindow iw;
-			iw.player = h->tempOwner;
-			iw.text << message;
-			cb->showInfoDialog(&iw);
-		}
-		collectRes(h->getOwner());
-	}
-}
-
-void CGResource::collectRes( PlayerColor player ) const
-{
-	cb->giveResource(player, static_cast<Res::ERes>(subID), amount);
-	ShowInInfobox sii;
-	sii.player = player;
-	sii.c = Component(Component::RESOURCE,subID,amount,0);
-	sii.text.addTxt(MetaString::ADVOB_TXT,113);
-	sii.text.addReplacement(MetaString::RES_NAMES, subID);
-	cb->showCompInfo(&sii);
-	cb->removeObject(this);
-}
-
-void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if(result.winner == 0) //attacker won
-		collectRes(hero->getOwner());
-}
-
-void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	if(answer)
-		cb->startBattleI(hero, this);
-}
-
-void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const
-{
-	ObjectInstanceID destinationid;
-	switch(ID)
-	{
-	case Obj::MONOLITH_ONE_WAY_ENTRANCE: //one way - find corresponding exit monolith
-	{
-		if(vstd::contains(objs,Obj::MONOLITH_ONE_WAY_EXIT) && vstd::contains(objs[Obj::MONOLITH_ONE_WAY_EXIT],subID) && objs[Obj::MONOLITH_ONE_WAY_EXIT][subID].size())
-		{
-			destinationid = *RandomGeneratorUtil::nextItem(objs[Obj::MONOLITH_ONE_WAY_EXIT][subID], cb->gameState()->getRandomGenerator());
-		}
-		else
-		{
-			logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id;
-		}
-		break;
-	}
-	case Obj::MONOLITH_TWO_WAY://two way monolith - pick any other one
-	case Obj::WHIRLPOOL: //Whirlpool
-		if(vstd::contains(objs,ID) && vstd::contains(objs[ID],subID) && objs[ID][subID].size()>1)
-		{
-			//choose another exit
-			do
-			{
-				destinationid = *RandomGeneratorUtil::nextItem(objs[ID][subID], cb->gameState()->getRandomGenerator());
-			} while(destinationid == id);
-
-			if (ID == Obj::WHIRLPOOL)
-			{
-				if (!h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION))
-				{
-					if (h->Slots().size() > 1 || h->Slots().begin()->second->count > 1)
-					{ //we can't remove last unit
-						SlotID targetstack = h->Slots().begin()->first; //slot numbers may vary
-						for(auto i = h->Slots().rbegin(); i != h->Slots().rend(); i++)
-						{
-							if (h->getPower(targetstack) > h->getPower(i->first))
-							{
-								targetstack = (i->first);
-							}
-						}
-
-						TQuantity countToTake = h->getStackCount(targetstack) * 0.5;
-						vstd::amax(countToTake, 1);
-
-
-						InfoWindow iw;
-						iw.player = h->tempOwner;
-						iw.text.addTxt (MetaString::ADVOB_TXT, 168);
-						iw.components.push_back (Component(CStackBasicDescriptor(h->getCreature(targetstack), countToTake)));
-						cb->showInfoDialog(&iw);
-						cb->changeStackCount(StackLocation(h, targetstack), -countToTake);
-					}
-				}
-			}
-		}
-		else
-			logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id;
-		break;
-	case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level
-		{
-			destinationid = getMatchingGate(id);
-			if(destinationid == ObjectInstanceID()) //no exit
-			{
-				showInfoDialog(h,153,0);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged.
-			}
-			break;
-		}
-	}
-	if(destinationid == ObjectInstanceID())
-	{
-		logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( ";
-		return;
-	}
-	if (ID == Obj::WHIRLPOOL)
-	{
-		std::set<int3> tiles = cb->getObj(destinationid)->getBlockedPos();
-		auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator());
-		cb->moveHero(h->id, tile + int3(1,0,0), true);
-	}
-	else
-		cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true);
-}
-
-void CGTeleport::initObj()
-{
-	int si = subID;
-	switch (ID)
-	{
-	case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid
-	case Obj::WHIRLPOOL:
-		{
-			si = 0;
-			break;
-		}
-	default:
-		break;
-	}
-	objs[ID][si].push_back(id);
-}
-
-void CGTeleport::postInit() //matches subterranean gates into pairs
-{
-	//split on underground and surface gates
-	std::vector<const CGObjectInstance *> gatesSplit[2]; //surface and underground gates
-	for(auto & elem : objs[Obj::SUBTERRANEAN_GATE][0])
-	{
-		const CGObjectInstance *hlp = cb->getObj(elem);
-		gatesSplit[hlp->pos.z].push_back(hlp);
-	}
-
-	//sort by position
-	std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](const CGObjectInstance * a, const CGObjectInstance * b)
-	{
-		return a->pos < b->pos;
-	});
-
-	for(size_t i = 0; i < gatesSplit[0].size(); i++)
-	{
-		const CGObjectInstance *cur = gatesSplit[0][i];
-
-		//find nearest underground exit
-		std::pair<int, si32> best(-1, std::numeric_limits<si32>::max()); //pair<pos_in_vector, distance^2>
-		for(int j = 0; j < gatesSplit[1].size(); j++)
-		{
-			const CGObjectInstance *checked = gatesSplit[1][j];
-			if(!checked)
-				continue;
-			si32 hlp = checked->pos.dist2dSQ(cur->pos);
-			if(hlp < best.second)
-			{
-				best.first = j;
-				best.second = hlp;
-			}
-		}
-
-		if(best.first >= 0) //found pair
-		{
-			gates.push_back(std::make_pair(cur->id, gatesSplit[1][best.first]->id));
-			gatesSplit[1][best.first] = nullptr;
-		}
-		else
-			gates.push_back(std::make_pair(cur->id, ObjectInstanceID()));
-	}
-	objs.erase(Obj::SUBTERRANEAN_GATE);
-}
-
-ObjectInstanceID CGTeleport::getMatchingGate(ObjectInstanceID id)
-{
-	for(auto & gate : gates)
-	{
-		if(gate.first == id)
-			return gate.second;
-		if(gate.second == id)
-			return gate.first;
-	}
-
-	return ObjectInstanceID();
-}
-
-void CGArtifact::initObj()
-{
-	blockVisit = true;
-	if(ID == Obj::ARTIFACT)
-	{
-		hoverName = VLC->arth->artifacts[subID]->Name();
-		if(!storedArtifact->artType)
-			storedArtifact->setType(VLC->arth->artifacts[subID]);
-	}
-	if(ID == Obj::SPELL_SCROLL)
-		subID = 1;
-
-	assert(storedArtifact->artType);
-	assert(storedArtifact->getParentNodes().size());
-
-	//assert(storedArtifact->artType->id == subID); //this does not stop desync
-}
-
-void CGArtifact::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if(!stacksCount())
-	{
-		InfoWindow iw;
-		iw.player = h->tempOwner;
-		switch(ID)
-		{
-		case Obj::ARTIFACT:
-			{
-				iw.soundID = soundBase::treasure; //play sound only for non-scroll arts
-				iw.components.push_back(Component(Component::ARTIFACT,subID,0,0));
-				if(message.length())
-					iw.text <<  message;
-				else
-				{
-					if (VLC->arth->artifacts[subID]->EventText().size())
-						iw.text << std::pair<ui8, ui32> (MetaString::ART_EVNTS, subID);
-					else //fix for mod artifacts with no event text
-					{
-						iw.text.addTxt (MetaString::ADVOB_TXT, 183); //% has found treasure
-						iw.text.addReplacement (h->name);
-					}
-
-				}
-			}
-			break;
-		case Obj::SPELL_SCROLL:
-			{
-				int spellID = storedArtifact->getGivenSpellID();
-				iw.components.push_back (Component(Component::SPELL, spellID,0,0));
-				iw.text.addTxt (MetaString::ADVOB_TXT,135);
-				iw.text.addReplacement(MetaString::SPELL_NAME, spellID);
-			}
-			break;
-		}
-		cb->showInfoDialog(&iw);
-		pick(h);
-	}
-	else
-	{
-		if(message.size())
-		{
-			BlockingDialog ynd(true,false);
-			ynd.player = h->getOwner();
-			ynd.text << message;
-			cb->showBlockingDialog(&ynd);
-		}
-		else
-		{
-			blockingDialogAnswered(h, true);
-		}
-	}
-}
-
-void CGArtifact::pick(const CGHeroInstance * h) const
-{
-	cb->giveHeroArtifact(h, storedArtifact, ArtifactPosition::FIRST_AVAILABLE);
-	cb->removeObject(this);
-}
-
-void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if(result.winner == 0) //attacker won
-		pick(hero);
-}
-
-void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	if(answer)
-		cb->startBattleI(hero, this);
-}
-
-bool CQuest::checkQuest (const CGHeroInstance * h) const
-{
-	switch (missionType)
-	{
-		case MISSION_NONE:
-			return true;
-		case MISSION_LEVEL:
-			if (m13489val <= h->level)
-				return true;
-			return false;
-		case MISSION_PRIMARY_STAT:
-			for (int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
-			{
-				if (h->getPrimSkillLevel(static_cast<PrimarySkill::PrimarySkill>(i)) < m2stats[i])
-					return false;
-			}
-			return true;
-		case MISSION_KILL_HERO:
-		case MISSION_KILL_CREATURE:
-			if (!h->cb->getObjByQuestIdentifier(m13489val))
-				return true;
-			return false;
-		case MISSION_ART:
-			for (auto & elem : m5arts)
-			{
-				if (h->hasArt(elem))
-					continue;
-				return false; //if the artifact was not found
-			}
-			return true;
-		case MISSION_ARMY:
-			{
-				std::vector<CStackBasicDescriptor>::const_iterator cre;
-				TSlots::const_iterator it;
-				ui32 count;
-				for (cre = m6creatures.begin(); cre != m6creatures.end(); ++cre)
-				{
-					for (count = 0, it = h->Slots().begin(); it !=  h->Slots().end(); ++it)
-					{
-						if (it->second->type == cre->type)
-							count += it->second->count;
-					}
-					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 ?
-			{	//Quest has no direct access to callback
-				if (h->cb->getResource (h->tempOwner, i) < m7resources[i])
-					return false;
-			}
-			return true;
-		case MISSION_HERO:
-			if (m13489val == h->type->ID.getNum())
-				return true;
-			return false;
-		case MISSION_PLAYER:
-			if (m13489val == h->getOwner().getNum())
-				return true;
-			return false;
-		default:
-			return false;
-	}
-}
-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)
-	{
-		isCustom = isCustomFirst;
-		iwText << (text = firstVisitText);
-	}
-	else if (failRequirements)
-	{
-		isCustom = isCustomNext;
-		iwText << (text = nextVisitText);
-	}
-	switch (missionType)
-	{
-		case MISSION_LEVEL:
-			components.push_back(Component (Component::EXPERIENCE, 0, m13489val, 0));
-			if (!isCustom)
-				iwText.addReplacement(m13489val);
-			break;
-		case MISSION_PRIMARY_STAT:
-		{
-			MetaString loot;
-			for (int i = 0; i < 4; ++i)
-			{
-				if (m2stats[i])
-				{
-					components.push_back(Component (Component::PRIM_SKILL, i, m2stats[i], 0));
-					loot << "%d %s";
-					loot.addReplacement(m2stats[i]);
-					loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
-				}
-			}
-			if (!isCustom)
-				iwText.addReplacement(loot.buildList());
-		}
-			break;
-		case MISSION_KILL_HERO:
-			components.push_back(Component(Component::HERO_PORTRAIT, heroPortrait, 0, 0));
-			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)
-				iwText.addReplacement(VLC->heroh->heroes[m13489val]->name);
-			break;
-		case MISSION_KILL_CREATURE:
-			{
-				components.push_back(Component(stackToKill));
-				if (!isCustom)
-				{
-					addReplacements(iwText, text);
-				}
-			}
-			break;
-		case MISSION_ART:
-		{
-			MetaString loot;
-			for (auto & elem : m5arts)
-			{
-				components.push_back(Component (Component::ARTIFACT, elem, 0, 0));
-				loot << "%s";
-				loot.addReplacement(MetaString::ART_NAMES, elem);
-			}
-			if (!isCustom)
-				iwText.addReplacement(loot.buildList());
-		}
-			break;
-		case MISSION_ARMY:
-		{
-			MetaString loot;
-			for (auto & elem : m6creatures)
-			{
-				components.push_back(Component(elem));
-				loot << "%s";
-				loot.addReplacement(elem);
-			}
-			if (!isCustom)
-				iwText.addReplacement(loot.buildList());
-		}
-			break;
-		case MISSION_RESOURCES:
-		{
-			MetaString loot;
-			for (int i = 0; i < 7; ++i)
-			{
-				if (m7resources[i])
-				{
-					components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0));
-					loot << "%d %s";
-					loot.addReplacement(m7resources[i]);
-					loot.addReplacement(MetaString::RES_NAMES, i);
-				}
-			}
-			if (!isCustom)
-				iwText.addReplacement(loot.buildList());
-		}
-			break;
-		case MISSION_PLAYER:
-			components.push_back(Component (Component::FLAG, m13489val, 0, 0));
-			if (!isCustom)
-				iwText.addReplacement(VLC->generaltexth->colors[m13489val]);
-			break;
-	}
-}
-void CQuest::getRolloverText (MetaString &ms, bool onHover) const
-{
-	if (onHover)
-		ms << "\n\n";
-
-	ms << VLC->generaltexth->quests[missionType-1][onHover ? 3 : 4][textOption];
-
-	switch (missionType)
-	{
-		case MISSION_LEVEL:
-			ms.addReplacement(m13489val);
-			break;
-		case MISSION_PRIMARY_STAT:
-			{
-				MetaString loot;
-				for (int i = 0; i < 4; ++i)
-				{
-					if (m2stats[i])
-					{
-						loot << "%d %s";
-						loot.addReplacement(m2stats[i]);
-						loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
-					}
-				}
-				ms.addReplacement(loot.buildList());
-			}
-			break;
-		case MISSION_KILL_HERO:
-			ms.addReplacement(heroName);
-			break;
-		case MISSION_KILL_CREATURE:
-			ms.addReplacement(stackToKill);
-			break;
-		case MISSION_ART:
-			{
-				MetaString loot;
-				for (auto & elem : m5arts)
-				{
-					loot << "%s";
-					loot.addReplacement(MetaString::ART_NAMES, elem);
-				}
-				ms.addReplacement(loot.buildList());
-			}
-			break;
-		case MISSION_ARMY:
-			{
-				MetaString loot;
-				for (auto & elem : m6creatures)
-				{
-					loot << "%s";
-					loot.addReplacement(elem);
-				}
-				ms.addReplacement(loot.buildList());
-			}
-			break;
-		case MISSION_RESOURCES:
-			{
-				MetaString loot;
-				for (int i = 0; i < 7; ++i)
-				{
-					if (m7resources[i])
-					{
-						loot << "%d %s";
-						loot.addReplacement(m7resources[i]);
-						loot.addReplacement(MetaString::RES_NAMES, i);
-					}
-				}
-				ms.addReplacement(loot.buildList());
-			}
-			break;
-		case MISSION_HERO:
-			ms.addReplacement(VLC->heroh->heroes[m13489val]->name);
-			break;
-		case MISSION_PLAYER:
-			ms.addReplacement(VLC->generaltexth->colors[m13489val]);
-			break;
-		default:
-			break;
-	}
-}
-
-void CQuest::getCompletionText (MetaString &iwText, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
-{
-	iwText << completedText;
-	switch (missionType)
-	{
-		case CQuest::MISSION_LEVEL:
-			if (!isCustomComplete)
-				iwText.addReplacement(m13489val);
-			break;
-		case CQuest::MISSION_PRIMARY_STAT:
-			if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace
-			{
-				MetaString loot;
-				for (int i = 0; i < 4; ++i)
-				{
-					if (m2stats[i])
-					{
-						loot << "%d %s";
-						loot.addReplacement(m2stats[i]);
-						loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
-					}
-				}
-				if (!isCustomComplete)
-					iwText.addReplacement(loot.buildList());
-			}
-			break;
-		case CQuest::MISSION_ART:
-		{
-			MetaString loot;
-			for (auto & elem : m5arts)
-			{
-				loot << "%s";
-				loot.addReplacement(MetaString::ART_NAMES, elem);
-			}
-			if (!isCustomComplete)
-				iwText.addReplacement(loot.buildList());
-		}
-			break;
-		case CQuest::MISSION_ARMY:
-		{
-			MetaString loot;
-			for (auto & elem : m6creatures)
-			{
-				loot << "%s";
-				loot.addReplacement(elem);
-			}
-			if (!isCustomComplete)
-				iwText.addReplacement(loot.buildList());
-		}
-			break;
-		case CQuest::MISSION_RESOURCES:
-		{
-			MetaString loot;
-			for (int i = 0; i < 7; ++i)
-			{
-				if (m7resources[i])
-				{
-					loot << "%d %s";
-					loot.addReplacement(m7resources[i]);
-					loot.addReplacement(MetaString::RES_NAMES, i);
-				}
-			}
-			if (!isCustomComplete)
-				iwText.addReplacement(loot.buildList());
-		}
-			break;
-		case MISSION_KILL_HERO:
-		case MISSION_KILL_CREATURE:
-			if (!isCustomComplete)
-				addReplacements(iwText, completedText);
-			break;
-		case MISSION_HERO:
-			if (!isCustomComplete)
-				iwText.addReplacement(VLC->heroh->heroes[m13489val]->name);
-			break;
-		case MISSION_PLAYER:
-			if (!isCustomComplete)
-				iwText.addReplacement(VLC->generaltexth->colors[m13489val]);
-			break;
-	}
-}
-void CGSeerHut::setObjToKill()
-{
-	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)
-	{
-		quest->heroName = getHeroToKill(false)->name;
-		quest->heroPortrait = getHeroToKill(false)->portrait;
-	}
-}
-
-void CGSeerHut::init()
-{
-	seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, cb->gameState()->getRandomGenerator());
-	quest->textOption = cb->gameState()->getRandomGenerator().nextInt(2);
-}
-
-void CGSeerHut::initObj()
-{
-	init();
-
-	quest->progress = CQuest::NOT_ACTIVE;
-	if (quest->missionType)
-	{
-		if (!quest->isCustomFirst)
-			quest->firstVisitText = VLC->generaltexth->quests[quest->missionType-1][0][quest->textOption];
-		if (!quest->isCustomNext)
-			quest->nextVisitText = VLC->generaltexth->quests[quest->missionType-1][1][quest->textOption];
-		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];
-	}
-
-}
-
-void CGSeerHut::getRolloverText (MetaString &text, bool onHover) const
-{
-	quest->getRolloverText (text, onHover);//TODO: simplify?
-	if (!onHover)
-		text.addReplacement(seerName);
-}
-
-const std::string & CGSeerHut::getHoverText() const
-{
-	switch (ID)
-	{
-	case Obj::SEER_HUT:
-		if (quest->progress != CQuest::NOT_ACTIVE)
-		{
-			hoverName = VLC->generaltexth->allTexts[347];
-			boost::algorithm::replace_first(hoverName,"%s", seerName);
-		}
-		else //just seer hut
-			hoverName = VLC->objtypeh->getObjectName(ID);
-		break;
-	case Obj::QUEST_GUARD:
-		hoverName = VLC->objtypeh->getObjectName(ID);
-		break;
-	default:
-        logGlobal->debugStream() << "unrecognized quest object";
-	}
-	if (quest->progress & quest->missionType) //rollover when the quest is active
-	{
-		MetaString ms;
-		getRolloverText (ms, true);
-		hoverName += ms.toString();
-	}
-	return hoverName;
-}
-
-void CQuest::addReplacements(MetaString &out, const std::string &base) const
-{
-	switch(missionType)
-	{
-	case MISSION_KILL_CREATURE:
-		out.addReplacement(stackToKill);
-		if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster
-		{
-			out.addReplacement(VLC->generaltexth->arraytxt[147+stackDirection]);
-		}
-		break;
-	case MISSION_KILL_HERO:
-		out.addReplacement(heroName);
-		break;
-	}
-}
-
-bool IQuestObject::checkQuest(const CGHeroInstance* h) const
-{
-	return quest->checkQuest(h);
-}
-
-void IQuestObject::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
-{
-	quest->getVisitText (text,components, isCustom, FirstVisit, h);
-}
-
-void CGSeerHut::getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
-{
-	quest->getCompletionText (text, components, isCustom, h);
-	switch (rewardType)
-	{
-		case EXPERIENCE: components.push_back(Component (Component::EXPERIENCE, 0, h->calculateXp(rVal), 0));
-			break;
-		case MANA_POINTS: components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0));
-			break;
-		case MORALE_BONUS: components.push_back(Component (Component::MORALE, 0, rVal, 0));
-			break;
-		case LUCK_BONUS: components.push_back(Component (Component::LUCK, 0, rVal, 0));
-			break;
-		case RESOURCES: components.push_back(Component (Component::RESOURCE, rID, rVal, 0));
-			break;
-		case PRIMARY_SKILL: components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0));
-			break;
-		case SECONDARY_SKILL: components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0));
-			break;
-		case ARTIFACT: components.push_back(Component (Component::ARTIFACT, rID, 0, 0));
-			break;
-		case SPELL: components.push_back(Component (Component::SPELL, rID, 0, 0));
-			break;
-		case CREATURE: components.push_back(Component (Component::CREATURE, rID, rVal, 0));
-			break;
-	}
-}
-
-void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
-{
-	switch (what)
-	{
-		case 10:
-			quest->progress = static_cast<CQuest::Eprogress>(val);
-			break;
-	}
-}
-void CGSeerHut::newTurn() const
-{
-	if (quest->lastDay >= 0 && quest->lastDay < cb->getDate()-1) //time is up
-	{
-		cb->setObjProperty (id, 10, CQuest::COMPLETE);
-	}
-
-}
-void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
-{
-	InfoWindow iw;
-	iw.player = h->getOwner();
-	if (quest->progress < CQuest::COMPLETE)
-	{
-		bool firstVisit = !quest->progress;
-		bool failRequirements = !checkQuest(h);
-		bool isCustom=false;
-
-		if (firstVisit)
-		{
-			isCustom = quest->isCustomFirst;
-			cb->setObjProperty (id, 10, CQuest::IN_PROGRESS);
-
-			AddQuest aq;
-			aq.quest = QuestInfo (quest, this, visitablePos());
-			aq.player = h->tempOwner;
-			cb->sendAndApply (&aq); //TODO: merge with setObjProperty?
-		}
-		else if (failRequirements)
-		{
-			isCustom = quest->isCustomNext;
-		}
-
-		if (firstVisit || failRequirements)
-		{
-			getVisitText (iw.text, iw.components, isCustom, firstVisit, h);
-
-			cb->showInfoDialog(&iw);
-		}
-		if (!failRequirements) // propose completion, also on first visit
-		{
-			BlockingDialog bd (true, false);
-			bd.player = h->getOwner();
-			bd.soundID = soundBase::QUEST;
-
-			getCompletionText (bd.text, bd.components, isCustom, h);
-
-			cb->showBlockingDialog (&bd);
-			return;
-		}
-	}
-	else
-	{
-		iw.text << VLC->generaltexth->seerEmpty[quest->textOption];
-		if (ID == Obj::SEER_HUT)
-			iw.text.addReplacement(seerName);
-		cb->showInfoDialog(&iw);
-	}
-}
-int CGSeerHut::checkDirection() const
-{
-	int3 cord = getCreatureToKill()->pos;
-	if ((double)cord.x/(double)cb->getMapSize().x < 0.34) //north
-	{
-		if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //northwest
-			return 8;
-		else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //north
-			return 1;
-		else //northeast
-			return 2;
-	}
-	else if ((double)cord.x/(double)cb->getMapSize().x < 0.67) //horizontal
-	{
-		if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //west
-			return 7;
-		else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //central
-			return 9;
-		else //east
-			return 3;
-	}
-	else //south
-	{
-		if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //southwest
-			return 6;
-		else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //south
-			return 5;
-		else //southeast
-			return 4;
-	}
-}
-void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const
-{
-	if (accept)
-	{
-		switch (quest->missionType)
-		{
-			case CQuest::MISSION_ART:
-				for (auto & elem : quest->m5arts)
-				{
-					cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false)));
-				}
-				break;
-			case CQuest::MISSION_ARMY:
-					cb->takeCreatures(h->id, quest->m6creatures);
-				break;
-			case CQuest::MISSION_RESOURCES:
-				for (int i = 0; i < 7; ++i)
-				{
-					cb->giveResource(h->getOwner(), static_cast<Res::ERes>(i), -quest->m7resources[i]);
-				}
-				break;
-			default:
-				break;
-		}
-		cb->setObjProperty (id, 10, CQuest::COMPLETE); //mission complete
-		completeQuest(h); //make sure to remove QuestGuard at the very end
-	}
-}
-void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward
-{
-	switch (rewardType)
-	{
-		case EXPERIENCE:
-		{
-			TExpType expVal = h->calculateXp(rVal);
-			cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false);
-			break;
-		}
-		case MANA_POINTS:
-		{
-			cb->setManaPoints(h->id, h->mana+rVal);
-			break;
-		}
-		case MORALE_BONUS: case LUCK_BONUS:
-		{
-			Bonus hb(Bonus::ONE_WEEK, (rewardType == 3 ? Bonus::MORALE : Bonus::LUCK),
-				Bonus::OBJECT, rVal, h->id.getNum(), "", -1);
-			GiveBonus gb;
-			gb.id = h->id.getNum();
-			gb.bonus = hb;
-			cb->giveHeroBonus(&gb);
-		}
-			break;
-		case RESOURCES:
-			cb->giveResource(h->getOwner(), static_cast<Res::ERes>(rID), rVal);
-			break;
-		case PRIMARY_SKILL:
-			cb->changePrimSkill(h, static_cast<PrimarySkill::PrimarySkill>(rID), rVal, false);
-			break;
-		case SECONDARY_SKILL:
-			cb->changeSecSkill(h, SecondarySkill(rID), rVal, false);
-			break;
-		case ARTIFACT:
-			cb->giveHeroNewArtifact(h, VLC->arth->artifacts[rID],ArtifactPosition::FIRST_AVAILABLE);
-			break;
-		case SPELL:
-		{
-			std::set<SpellID> spell;
-			spell.insert (SpellID(rID));
-			cb->changeSpells(h, true, spell);
-		}
-			break;
-		case CREATURE:
-			{
-				CCreatureSet creatures;
-				creatures.setCreature(SlotID(0), CreatureID(rID), rVal);
-				cb->giveCreatures(this, h, creatures, false);
-			}
-			break;
-		default:
-			break;
-	}
-}
-
-const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
-{
-	const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
-	if(allowNull && !o)
-		return nullptr;
-	assert(o && (o->ID == Obj::HERO  ||  o->ID == Obj::PRISON));
-	return static_cast<const CGHeroInstance*>(o);
-}
-
-const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
-{
-	const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
-	if(allowNull && !o)
-		return nullptr;
-	assert(o && o->ID == Obj::MONSTER);
-	return static_cast<const CGCreature*>(o);
-}
-
-void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	finishQuest(hero, answer);
-}
-
-void CGQuestGuard::init()
-{
-	blockVisit = true;
-	quest->textOption = cb->gameState()->getRandomGenerator().nextInt(3, 5);
-}
-void CGQuestGuard::completeQuest(const CGHeroInstance *h) const
-{
-	cb->removeObject(this);
-}
-void CGWitchHut::initObj()
-{
-	ability = *RandomGeneratorUtil::nextItem(allowedAbilities, cb->gameState()->getRandomGenerator());
-}
-
-void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const
-{
-	InfoWindow iw;
-	iw.soundID = soundBase::gazebo;
-	iw.player = h->getOwner();
-	if(!wasVisited(h->tempOwner))
-		cb->setObjProperty(id, 10, h->tempOwner.getNum());
-	ui32 txt_id;
-	if(h->getSecSkillLevel(SecondarySkill(ability))) //you already know this skill
-	{
-		txt_id =172;
-	}
-	else if(!h->canLearnSkill()) //already all skills slots used
-	{
-		txt_id = 173;
-	}
-	else //give sec skill
-	{
-		iw.components.push_back(Component(Component::SEC_SKILL, ability, 1, 0));
-		txt_id = 171;
-		cb->changeSecSkill(h, SecondarySkill(ability), 1, true);
-	}
-
-	iw.text.addTxt(MetaString::ADVOB_TXT,txt_id);
-	iw.text.addReplacement(MetaString::SEC_SKILL_NAME, ability);
-	cb->showInfoDialog(&iw);
-}
-
-const std::string & CGWitchHut::getHoverText() const
-{
-	hoverName = VLC->objtypeh->getObjectName(ID);
-	if(wasVisited(cb->getLocalPlayer()))
-	{
-		hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s)
-		boost::algorithm::replace_first(hoverName,"%s",VLC->generaltexth->skillName[ability]);
-		const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
-		if(h && h->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability
-			hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned)
-	}
-	return hoverName;
-}
-
-void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const
-{
-	int message;
-
-	if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Well today
-	{
-		message = 78;//"A second drink at the well in one day will not help you."
-	}
-	else if(h->mana < h->manaLimit())
-	{
-		giveDummyBonus(h->id);
-		cb->setManaPoints(h->id,h->manaLimit());
-		message = 77;
-	}
-	else
-	{
-		message = 79;
-	}
-	showInfoDialog(h,message,soundBase::faerie);
-}
-
-const std::string & CGMagicWell::getHoverText() const
-{
-	getNameVis(hoverName);
-	return hoverName;
-}
-
-void CGPandoraBox::initObj()
-{
-	blockVisit = (ID==Obj::PANDORAS_BOX); //block only if it's really pandora's box (events also derive from that class)
-	hasGuardians = stacks.size();
-}
-
-void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const
-{
-		BlockingDialog bd (true, false);
-		bd.player = h->getOwner();
-		bd.soundID = soundBase::QUEST;
-		bd.text.addTxt (MetaString::ADVOB_TXT, 14);
-		cb->showBlockingDialog (&bd);
-}
-
-void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const
-{
-	cb->removeAfterVisit(this);
-
-	InfoWindow iw;
-	iw.player = h->getOwner();
-
-	bool changesPrimSkill = false;
-	for (auto & elem : primskills)
-	{
-		if(elem)
-		{
-			changesPrimSkill = true;
-			break;
-		}
-	}
-
-	if(gainedExp || changesPrimSkill || abilities.size())
-	{
-		TExpType expVal = h->calculateXp(gainedExp);
-		//getText(iw,afterBattle,175,h); //wtf?
-		iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something
-		iw.text.addReplacement(h->name);
-
-		if(expVal)
-			iw.components.push_back(Component(Component::EXPERIENCE,0,expVal,0));
-		for(int i=0; i<primskills.size(); i++)
-			if(primskills[i])
-				iw.components.push_back(Component(Component::PRIM_SKILL,i,primskills[i],0));
-
-		for(int i=0; i<abilities.size(); i++)
-			iw.components.push_back(Component(Component::SEC_SKILL,abilities[i],abilityLevels[i],0));
-
-		cb->showInfoDialog(&iw);
-
-		//give sec skills
-		for(int i=0; i<abilities.size(); i++)
-		{
-			int curLev = h->getSecSkillLevel(abilities[i]);
-
-			if( (curLev  &&  curLev < abilityLevels[i])	|| (h->canLearnSkill() ))
-			{
-				cb->changeSecSkill(h,abilities[i],abilityLevels[i],true);
-			}
-		}
-
-		//give prim skills
-		for(int i=0; i<primskills.size(); i++)
-			if(primskills[i])
-				cb->changePrimSkill(h,static_cast<PrimarySkill::PrimarySkill>(i),primskills[i],false);
-
-		assert(!cb->isVisitCoveredByAnotherQuery(this, h));
-
-		//give exp
-		if(expVal)
-			cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false);
-	}
-
-	if(!cb->isVisitCoveredByAnotherQuery(this, h))
-		giveContentsAfterExp(h);
-	//Otherwise continuation occurs via post-level-up callback.
-}
-
-void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const
-{
-	bool hadGuardians = hasGuardians; //copy, because flag will be emptied after issuing first post-battle message
-
-	std::string msg = message; //in case box is removed in the meantime
-	InfoWindow iw;
-	iw.player = h->getOwner();
-
-	if(spells.size())
-	{
-		std::set<SpellID> spellsToGive;
-		iw.components.clear();
-		if (spells.size() > 1)
-		{
-			iw.text.addTxt(MetaString::ADVOB_TXT, 188); //%s learns spells
-		}
-		else
-		{
-			iw.text.addTxt(MetaString::ADVOB_TXT, 184); //%s learns a spell
-		}
-		iw.text.addReplacement(h->name);
-		std::vector<ConstTransitivePtr<CSpell> > * sp = &VLC->spellh->objects;
-		for(auto i=spells.cbegin(); i != spells.cend(); i++)
-		{
-			if ((*sp)[*i]->level <= h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) //enough wisdom
-			{
-				iw.components.push_back(Component(Component::SPELL,*i,0,0));
-				spellsToGive.insert(*i);
-			}
-		}
-		if(!spellsToGive.empty())
-		{
-			cb->changeSpells(h,true,spellsToGive);
-			cb->showInfoDialog(&iw);
-		}
-	}
-
-	if(manaDiff)
-	{
-		getText(iw,hadGuardians,manaDiff,176,177,h);
-		iw.components.push_back(Component(Component::PRIM_SKILL,5,manaDiff,0));
-		cb->showInfoDialog(&iw);
-		cb->setManaPoints(h->id, h->mana + manaDiff);
-	}
-
-	if(moraleDiff)
-	{
-		getText(iw,hadGuardians,moraleDiff,178,179,h);
-		iw.components.push_back(Component(Component::MORALE,0,moraleDiff,0));
-		cb->showInfoDialog(&iw);
-		GiveBonus gb;
-		gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,moraleDiff,id.getNum(),"");
-		gb.id = h->id.getNum();
-		cb->giveHeroBonus(&gb);
-	}
-
-	if(luckDiff)
-	{
-		getText(iw,hadGuardians,luckDiff,180,181,h);
-		iw.components.push_back(Component(Component::LUCK,0,luckDiff,0));
-		cb->showInfoDialog(&iw);
-		GiveBonus gb;
-		gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,luckDiff,id.getNum(),"");
-		gb.id = h->id.getNum();
-		cb->giveHeroBonus(&gb);
-	}
-
-	iw.components.clear();
-	iw.text.clear();
-	for(int i=0; i<resources.size(); i++)
-	{
-		if(resources[i] < 0)
-			iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0));
-	}
-	if(iw.components.size())
-	{
-		getText(iw,hadGuardians,182,h);
-		cb->showInfoDialog(&iw);
-	}
-
-	iw.components.clear();
-	iw.text.clear();
-	for(int i=0; i<resources.size(); i++)
-	{
-		if(resources[i] > 0)
-			iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0));
-	}
-	if(iw.components.size())
-	{
-		getText(iw,hadGuardians,183,h);
-		cb->showInfoDialog(&iw);
-	}
-
-	iw.components.clear();
-	// 	getText(iw,afterBattle,183,h);
-	iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure
-	iw.text.addReplacement(h->name);
-	for(auto & elem : artifacts)
-	{
-		iw.components.push_back(Component(Component::ARTIFACT,elem,0,0));
-		if(iw.components.size() >= 14)
-		{
-			cb->showInfoDialog(&iw);
-			iw.components.clear();
-			iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - once more?
-			iw.text.addReplacement(h->name);
-		}
-	}
-	if(iw.components.size())
-	{
-		cb->showInfoDialog(&iw);
-	}
-
-	for(int i=0; i<resources.size(); i++)
-		if(resources[i])
-			cb->giveResource(h->getOwner(),static_cast<Res::ERes>(i),resources[i]);
-
-	for(auto & elem : artifacts)
-		cb->giveHeroNewArtifact(h, VLC->arth->artifacts[elem],ArtifactPosition::FIRST_AVAILABLE);
-
-	iw.components.clear();
-	iw.text.clear();
-
-	if (creatures.Slots().size())
-	{ //this part is taken straight from creature bank
-		MetaString loot;
-		for(auto & elem : creatures.Slots())
-		{ //build list of joined creatures
-			iw.components.push_back(Component(*elem.second));
-			loot << "%s";
-			loot.addReplacement(*elem.second);
-		}
-
-		if (creatures.Slots().size() == 1 && creatures.Slots().begin()->second->count == 1)
-			iw.text.addTxt(MetaString::ADVOB_TXT, 185);
-		else
-			iw.text.addTxt(MetaString::ADVOB_TXT, 186);
-
-		iw.text.addReplacement(loot.buildList());
-		iw.text.addReplacement(h->name);
-
-		cb->showInfoDialog(&iw);
-		cb->giveCreatures(this, h, creatures, true);
-	}
-	if(!hasGuardians && msg.size())
-	{
-		iw.text << msg;
-		cb->showInfoDialog(&iw);
-	}
-}
-
-void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const
-{
-	if(afterBattle || !message.size())
-	{
-		iw.text.addTxt(MetaString::ADVOB_TXT,text);//%s has lost treasure.
-		iw.text.addReplacement(h->name);
-	}
-	else
-	{
-		iw.text << message;
-		afterBattle = true;
-	}
-}
-
-void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const
-{
-	iw.components.clear();
-	iw.text.clear();
-	if(afterBattle || !message.size())
-	{
-		iw.text.addTxt(MetaString::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases
-		iw.text.addReplacement(h->name);
-	}
-	else
-	{
-		iw.text << message;
-		afterBattle = true;
-	}
-}
-
-void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if(result.winner)
-		return;
-
-	giveContentsUpToExp(hero);
-}
-
-void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	if (answer)
-	{
-		if (stacksCount() > 0) //if pandora's box is protected by army
-		{
-			showInfoDialog(hero,16,0);
-			cb->startBattleI(hero, this); //grants things after battle
-		}
-		else if (message.size() == 0 && resources.size() == 0
-			&& primskills.size() == 0 && abilities.size() == 0
-			&& abilityLevels.size() == 0 &&  artifacts.size() == 0
-			&& spells.size() == 0 && creatures.Slots().size() > 0
-			&& gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle
-		{
-			showInfoDialog(hero,15,0);
-			cb->removeObject(this);
-		}
-		else //if it gives something without battle
-		{
-			giveContentsUpToExp(hero);
-		}
-	}
-}
-
-void CGPandoraBox::heroLevelUpDone(const CGHeroInstance *hero) const
-{
-	giveContentsAfterExp(hero);
-}
-
-void CGEvent::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if(!(availableFor & (1 << h->tempOwner.getNum())))
-		return;
-	if(cb->getPlayerSettings(h->tempOwner)->playerID)
-	{
-		if(humanActivate)
-			activated(h);
-	}
-	else if(computerActivate)
-		activated(h);
-}
-
-void CGEvent::activated( const CGHeroInstance * h ) const
-{
-	if(stacksCount() > 0)
-	{
-		InfoWindow iw;
-		iw.player = h->tempOwner;
-		if(message.size())
-			iw.text << message;
-		else
-			iw.text.addTxt(MetaString::ADVOB_TXT, 16);
-		cb->showInfoDialog(&iw);
-		cb->startBattleI(h, this);
-	}
-	else
-	{
-		giveContentsUpToExp(h);
-	}
-}
-
-void CGObservatory::onHeroVisit( const CGHeroInstance * h ) const
-{
-	InfoWindow iw;
-	iw.player = h->tempOwner;
-	switch (ID)
-	{
-	case Obj::REDWOOD_OBSERVATORY:
-	case Obj::PILLAR_OF_FIRE:
-	{
-		iw.soundID = soundBase::LIGHTHOUSE;
-		iw.text.addTxt(MetaString::ADVOB_TXT,98 + (ID==Obj::PILLAR_OF_FIRE));
-
-		FoWChange fw;
-		fw.player = h->tempOwner;
-		fw.mode = 1;
-		cb->getTilesInRange (fw.tiles, pos, 20, h->tempOwner, 1);
-		cb->sendAndApply (&fw);
-		break;
-	}
-	case Obj::COVER_OF_DARKNESS:
-	{
-		iw.text.addTxt (MetaString::ADVOB_TXT, 31);
-		hideTiles(h->tempOwner, 20);
-		break;
-	}
-	}
-	cb->showInfoDialog(&iw);
-}
-
-void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if(spell == SpellID::NONE)
-	{
-        logGlobal->errorStream() << "Not initialized shrine visited!";
-		return;
-	}
-
-	if(!wasVisited(h->tempOwner))
-		cb->setObjProperty(id, 10, h->tempOwner.getNum());
-
-	InfoWindow iw;
-	iw.soundID = soundBase::temple;
-	iw.player = h->getOwner();
-	iw.text.addTxt(MetaString::ADVOB_TXT,127 + ID - 88);
-	iw.text.addTxt(MetaString::SPELL_NAME,spell);
-	iw.text << ".";
-
-	if(!h->getArt(ArtifactPosition::SPELLBOOK))
-	{
-		iw.text.addTxt(MetaString::ADVOB_TXT,131);
-	}
-	else if(ID == Obj::SHRINE_OF_MAGIC_THOUGHT  && !h->getSecSkillLevel(SecondarySkill::WISDOM)) //it's third level spell and hero doesn't have wisdom
-	{
-		iw.text.addTxt(MetaString::ADVOB_TXT,130);
-	}
-	else if(vstd::contains(h->spells,spell))//hero already knows the spell
-	{
-		iw.text.addTxt(MetaString::ADVOB_TXT,174);
-	}
-	else //give spell
-	{
-		std::set<SpellID> spells;
-		spells.insert(spell);
-		cb->changeSpells(h, true, spells);
-
-		iw.components.push_back(Component(Component::SPELL,spell,0,0));
-	}
-
-	cb->showInfoDialog(&iw);
-}
-
-void CGShrine::initObj()
-{
-	if(spell == SpellID::NONE) //spell not set
-	{
-		int level = ID-87;
-		std::vector<SpellID> possibilities;
-		cb->getAllowedSpells (possibilities, level);
-
-		if(possibilities.empty())
-		{
-            logGlobal->errorStream() << "Error: cannot init shrine, no allowed spells!";
-			return;
-		}
-
-		spell = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator());
-	}
-}
-
-const std::string & CGShrine::getHoverText() const
-{
-	hoverName = VLC->objtypeh->getObjectName(ID);
-	if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current
-	{
-		hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s)
-		boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->name);
-		const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
-		if(h && vstd::contains(h->spells,spell)) //hero knows that ability
-			hoverName += "\n\n" + VLC->generaltexth->allTexts[354]; // (Already learned)
-	}
-	return hoverName;
-}
-
-void CGSignBottle::initObj()
-{
-	//if no text is set than we pick random from the predefined ones
-	if(message.empty())
-	{
-		message = *RandomGeneratorUtil::nextItem(VLC->generaltexth->randsign, cb->gameState()->getRandomGenerator());
-	}
-
-	if(ID == Obj::OCEAN_BOTTLE)
-	{
-		blockVisit = true;
-	}
-}
-
-void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const
-{
-	InfoWindow iw;
-	iw.soundID = soundBase::STORE;
-	iw.player = h->getOwner();
-	iw.text << message;
-	cb->showInfoDialog(&iw);
-
-	if(ID == Obj::OCEAN_BOTTLE)
-		cb->removeObject(this);
-}
-
-//TODO: remove
-//void CGScholar::giveAnyBonus( const CGHeroInstance * h ) const
-//{
-//
-//}
-
-void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
-{
-
-	EBonusType type = bonusType;
-	int bid = bonusID;
-	//check if the bonus if applicable, if not - give primary skill (always possible)
-	int ssl = h->getSecSkillLevel(SecondarySkill(bid)); //current sec skill level, used if bonusType == 1
-	if((type == SECONDARY_SKILL
-			&& ((ssl == 3)  ||  (!ssl  &&  !h->canLearnSkill()))) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot)
-		|| (type == SPELL  &&  (!h->getArt(ArtifactPosition::SPELLBOOK) || vstd::contains(h->spells, (ui32) bid)
-		|| ( SpellID(bid).toSpell()->level > h->getSecSkillLevel(SecondarySkill::WISDOM) + 2)
-		))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom
-	{
-		type = PRIM_SKILL;
-		bid = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS - 1);
-	}
-
-	InfoWindow iw;
-	iw.soundID = soundBase::gazebo;
-	iw.player = h->getOwner();
-	iw.text.addTxt(MetaString::ADVOB_TXT,115);
-
-	switch (type)
-	{
-	case PRIM_SKILL:
-		cb->changePrimSkill(h,static_cast<PrimarySkill::PrimarySkill>(bid),+1);
-		iw.components.push_back(Component(Component::PRIM_SKILL,bid,+1,0));
-		break;
-	case SECONDARY_SKILL:
-		cb->changeSecSkill(h,SecondarySkill(bid),+1);
-		iw.components.push_back(Component(Component::SEC_SKILL,bid,ssl+1,0));
-		break;
-	case SPELL:
-		{
-			std::set<SpellID> hlp;
-			hlp.insert(SpellID(bid));
-			cb->changeSpells(h,true,hlp);
-			iw.components.push_back(Component(Component::SPELL,bid,0,0));
-		}
-		break;
-	default:
-		logGlobal->errorStream() << "Error: wrong bonus type (" << (int)type << ") for Scholar!\n";
-		return;
-	}
-
-	cb->showInfoDialog(&iw);
-	cb->removeObject(this);
-}
-
-void CGScholar::initObj()
-{
-	blockVisit = true;
-	if(bonusType == RANDOM)
-	{
-		bonusType = static_cast<EBonusType>(cb->gameState()->getRandomGenerator().nextInt(2));
-		switch(bonusType)
-		{
-		case PRIM_SKILL:
-			bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS -1);
-			break;
-		case SECONDARY_SKILL:
-			bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::SKILL_QUANTITY -1);
-			break;
-		case SPELL:
-			std::vector<SpellID> possibilities;
-			for (int i = 1; i < 6; ++i)
-				cb->getAllowedSpells (possibilities, i);
-			bonusID = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator());
-			break;
-		}
-	}
-}
-
-void CGGarrison::onHeroVisit (const CGHeroInstance *h) const
-{
-	int ally = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);
-	if (!ally && stacksCount() > 0) {
-		//TODO: Find a way to apply magic garrison effects in battle.
-		cb->startBattleI(h, this);
-		return;
-	}
-
-	//New owner.
-	if (!ally)
-		cb->setOwner(this, h->tempOwner);
-
-	cb->showGarrisonDialog(id, h->id, removableUnits);
-}
-
-ui8 CGGarrison::getPassableness() const
-{
-	if ( !stacksCount() )//empty - anyone can visit
-		return GameConstants::ALL_PLAYERS;
-	if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit
-		return 0;
-
-	ui8 mask = 0;
-	TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner);
-	for(PlayerColor it : ts->players)
-		mask |= 1<<it.getNum(); //allies - add to possible visitors
-
-	return mask;
-}
-
-void CGGarrison::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if (result.winner == 0)
-		onHeroVisit(hero);
-}
-
-void CBank::initObj()
-{
-	index = VLC->objh->bankObjToIndex(this);
-	bc = nullptr;
-	daycounter = 0;
-	multiplier = 1;
-}
-const std::string & CBank::getHoverText() const
-{
-	bool visited = (bc == nullptr);
-	hoverName = VLC->objh->creBanksNames[index] + " " + visitedTxt(visited);
-	return hoverName;
-}
-void CBank::reset(ui16 var1) //prevents desync
-{
-	ui8 chance = 0;
-	for (auto & elem : VLC->objh->banksInfo[index])
-	{
-		if (var1 < (chance += elem->chance))
-		{
- 			bc = elem;
-			break;
-		}
-	}
-	artifacts.clear();
-}
-
-void CBank::initialize() const
-{
-	cb->setObjProperty(id, ObjProperty::BANK_RESET, cb->gameState()->getRandomGenerator().nextInt()); //synchronous reset
-
-	for (ui8 i = 0; i <= 3; i++)
-	{
-		for (ui8 n = 0; n < bc->artifacts[i]; n++)
-		{
-			CArtifact::EartClass artClass;
-			switch(i)
-			{
-			case 0: artClass = CArtifact::ART_TREASURE; break;
-			case 1: artClass = CArtifact::ART_MINOR; break;
-			case 2: artClass = CArtifact::ART_MAJOR; break;
-			case 3: artClass = CArtifact::ART_RELIC; break;
-			default: assert(0); continue;
-			}
-
-			int artID = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), artClass);
-			cb->setObjProperty(id, ObjProperty::BANK_ADD_ARTIFACT, artID);
-		}
-	}
-
-	cb->setObjProperty(id, ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //get army
-}
-void CBank::setPropertyDer (ui8 what, ui32 val)
-/// random values are passed as arguments and processed identically on all clients
-{
-	switch (what)
-	{
-		case ObjProperty::BANK_DAYCOUNTER: //daycounter
-			if (val == 0)
-				daycounter = 1; //yes, 1
-			else
-				daycounter++;
-			break;
-		case ObjProperty::BANK_MULTIPLIER: //multiplier, in percent
-			multiplier = val / 100.0;
-			break;
-		case 13: //bank preset
-			bc = VLC->objh->banksInfo[index][val];
-			break;
-		case ObjProperty::BANK_RESET:
-			reset (val%100);
-			break;
-		case ObjProperty::BANK_CLEAR_CONFIG:
-			bc = nullptr;
-			break;
-		case ObjProperty::BANK_CLEAR_ARTIFACTS: //remove rewards from Derelict Ship
-			artifacts.clear();
-			break;
-		case ObjProperty::BANK_INIT_ARMY: //set ArmedInstance army
-			{
-				int upgraded = 0;
-				if (val%100 < bc->upgradeChance) //once again anti-desync
-					upgraded = 1;
-				switch (bc->guards.size())
-				{
-					case 1:
-						for	(int i = 0; i < 4; ++i)
-							setCreature (SlotID(i), bc->guards[0].first, bc->guards[0].second  / 5 );
-						setCreature (SlotID(4), CreatureID(bc->guards[0].first + upgraded), bc->guards[0].second  / 5 );
-						break;
-					case 4:
-					{
-						if (bc->guards.back().second) //all stacks are present
-						{
-							for (auto & elem : bc->guards)
-							{
-								setCreature (SlotID(stacksCount()), elem.first, elem.second);
-							}
-						}
-						else if (bc->guards[2].second)//Wraiths are present, split two stacks in Crypt
-						{
-							setCreature (SlotID(0), bc->guards[0].first, bc->guards[0].second  / 2 );
-							setCreature (SlotID(1), bc->guards[1].first, bc->guards[1].second / 2);
-							setCreature (SlotID(2), CreatureID(bc->guards[2].first + upgraded), bc->guards[2].second);
-							setCreature (SlotID(3), bc->guards[1].first, bc->guards[1].second / 2 );
-							setCreature (SlotID(4), bc->guards[0].first, bc->guards[0].second - (bc->guards[0].second  / 2) );
-
-						}
-						else //split both stacks
-						{
-							for	(int i = 0; i < 3; ++i) //skellies
-								setCreature (SlotID(2*i), bc->guards[0].first, bc->guards[0].second  / 3);
-							for	(int i = 0; i < 2; ++i) //zombies
-								setCreature (SlotID(2*i+1), bc->guards[1].first, bc->guards[1].second  / 2);
-						}
-					}
-						break;
-					default:
-                        logGlobal->warnStream() << "Error: Unexpected army data: " << bc->guards.size() <<" items found";
-						return;
-				}
-			}
-			break;
-		case ObjProperty::BANK_ADD_ARTIFACT: //add Artifact
-		{
-			artifacts.push_back (val);
-			break;
-		}
-	}
-}
-
-void CBank::newTurn() const
-{
-	if (bc == nullptr)
-	{
-		if (cb->getDate() == 1)
-			initialize(); //initialize on first day
-		else if (daycounter >= 28 && (subID < 13 || subID > 16)) //no reset for Emissaries
-		{
-			initialize();
-			cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 0); //daycounter 0
-			if (ID == Obj::DERELICT_SHIP && cb->getDate() > 1)
-			{
-				cb->setObjProperty (id, ObjProperty::BANK_MULTIPLIER, 0);//ugly hack to make derelict ships usable only once
-				cb->setObjProperty (id, ObjProperty::BANK_CLEAR_ARTIFACTS, 0);
-			}
-		}
-		else
-			cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++
-	}
-}
-bool CBank::wasVisited (PlayerColor player) const
-{
-	return !bc;
-}
-
-void CBank::onHeroVisit (const CGHeroInstance * h) const
-{
-	if (bc)
-	{
-		int banktext = 0;
-		switch (ID)
-		{
-		case Obj::CREATURE_BANK:
-			banktext = 32;
-			break;
-		case Obj::DERELICT_SHIP:
-			banktext = 41;
-			break;
-		case Obj::DRAGON_UTOPIA:
-			banktext = 47;
-			break;
-		case Obj::CRYPT:
-			banktext = 119;
-			break;
-		case Obj::SHIPWRECK:
-			banktext = 122;
-			break;
-		}
-		BlockingDialog bd (true, false);
-		bd.player = h->getOwner();
-		bd.soundID = soundBase::ROGUE;
-		bd.text.addTxt(MetaString::ADVOB_TXT,banktext);
-		if (ID == Obj::CREATURE_BANK)
-			bd.text.addReplacement(VLC->objh->creBanksNames[index]);
-		cb->showBlockingDialog (&bd);
-	}
-	else
-	{
-		InfoWindow iw;
-		iw.soundID = soundBase::GRAVEYARD;
-		iw.player = h->getOwner();
-		if (ID == Obj::CRYPT) //morale penalty for empty Crypt
-		{
-			GiveBonus gbonus;
-			gbonus.id = h->id.getNum();
-			gbonus.bonus.duration = Bonus::ONE_BATTLE;
-			gbonus.bonus.source = Bonus::OBJECT;
-			gbonus.bonus.sid = ID;
-			gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[98];
-			gbonus.bonus.type = Bonus::MORALE;
-			gbonus.bonus.val = -1;
-			cb->giveHeroBonus(&gbonus);
-			iw.text << VLC->generaltexth->advobtxt[120];
-			iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
-		}
-		else
-		{
-			iw.text << VLC->generaltexth->advobtxt[33];
-			iw.text.addReplacement(VLC->objh->creBanksNames[index]);
-		}
-		cb->showInfoDialog(&iw);
-	}
-}
-
-void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if (result.winner == 0)
-	{
-		int textID = -1;
-		InfoWindow iw;
-		iw.player = hero->getOwner();
-		MetaString loot;
-
-		switch (ID)
-		{
-		case Obj::CREATURE_BANK: case Obj::DRAGON_UTOPIA:
-			textID = 34;
-			break;
-		case Obj::DERELICT_SHIP:
-			if (multiplier)
-				textID = 43;
-			else
-			{
-				GiveBonus gbonus;
-				gbonus.id = hero->id.getNum();
-				gbonus.bonus.duration = Bonus::ONE_BATTLE;
-				gbonus.bonus.source = Bonus::OBJECT;
-				gbonus.bonus.sid = ID;
-				gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101];
-				gbonus.bonus.type = Bonus::MORALE;
-				gbonus.bonus.val = -1;
-				cb->giveHeroBonus(&gbonus);
-				textID = 42;
-				iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
-			}
-			break;
-		case Obj::CRYPT:
-			if (bc->resources.size() != 0)
-				textID = 121;
-			else
-			{
-				iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
-				GiveBonus gbonus;
-				gbonus.id = hero->id.getNum();
-				gbonus.bonus.duration = Bonus::ONE_BATTLE;
-				gbonus.bonus.source = Bonus::OBJECT;
-				gbonus.bonus.sid = ID;
-				gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID];
-				gbonus.bonus.type = Bonus::MORALE;
-				gbonus.bonus.val = -1;
-				cb->giveHeroBonus(&gbonus);
-				textID = 120;
-				iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
-			}
-			break;
-		case Obj::SHIPWRECK:
-			if (bc->resources.size())
-				textID = 124;
-			else
-				textID = 123;
-			break;
-		}
-
-		//grant resources
-		if (textID != 42) //empty derelict ship gives no cash
-		{
-			for (int it = 0; it < bc->resources.size(); it++)
-			{
-				if (bc->resources[it] != 0)
-				{
-					iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0));
-					loot << "%d %s";
-					loot.addReplacement(iw.components.back().val);
-					loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype);
-					cb->giveResource (hero->getOwner(), static_cast<Res::ERes>(it), bc->resources[it]);
-				}
-			}
-		}
-		//grant artifacts
-		for (auto & elem : artifacts)
-		{
-			iw.components.push_back (Component (Component::ARTIFACT, elem, 0, 0));
-			loot << "%s";
-			loot.addReplacement(MetaString::ART_NAMES, elem);
-			cb->giveHeroNewArtifact (hero, VLC->arth->artifacts[elem], ArtifactPosition::FIRST_AVAILABLE);
-		}
-		//display loot
-		if (!iw.components.empty())
-		{
-			iw.text.addTxt (MetaString::ADVOB_TXT, textID);
-			if (textID == 34)
-			{
-				iw.text.addReplacement(MetaString::CRE_PL_NAMES, result.casualties[1].begin()->first);
-				iw.text.addReplacement(loot.buildList());
-			}
-			cb->showInfoDialog(&iw);
-		}
-		loot.clear();
-		iw.components.clear();
-		iw.text.clear();
-
-		//grant creatures
-		CCreatureSet ourArmy;
-		for (auto it = bc->creatures.cbegin(); it != bc->creatures.cend(); it++)
-		{
-			SlotID slot = ourArmy.getSlotFor(it->first);
-			ourArmy.addToSlot(slot, it->first, it->second);
-		}
-		for (auto & elem : ourArmy.Slots())
-		{
-			iw.components.push_back(Component(*elem.second));
-			loot << "%s";
-			loot.addReplacement(*elem.second);
-		}
-
-		if (ourArmy.Slots().size())
-		{
-			if (ourArmy.Slots().size() == 1 && ourArmy.Slots().begin()->second->count == 1)
-				iw.text.addTxt (MetaString::ADVOB_TXT, 185);
-			else
-				iw.text.addTxt (MetaString::ADVOB_TXT, 186);
-
-			iw.text.addReplacement(loot.buildList());
-			iw.text.addReplacement(hero->name);
-			cb->showInfoDialog(&iw);
-			cb->giveCreatures(this, hero, ourArmy, false);
-		}
-		cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); //bc = nullptr
-	}
-}
-
-void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	if (answer)
-	{
-		cb->startBattleI(hero, this, true);
-	}
-}
-
-void CGPyramid::initObj()
-{
-	std::vector<SpellID> available;
-	cb->getAllowedSpells (available, 5);
-	if (available.size())
-	{
-		bc = VLC->objh->banksInfo[21].front(); //TODO: remove hardcoded value?
-		spell = *RandomGeneratorUtil::nextItem(available, cb->gameState()->getRandomGenerator());
-	}
-	else
-	{
-        logGlobal->errorStream() <<"No spells available for Pyramid! Object set to empty.";
-	}
-	setPropertyDer(ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //set guards at game start
-}
-const std::string & CGPyramid::getHoverText() const
-{
-	hoverName = VLC->objh->creBanksNames[21]+ " " + visitedTxt((bc==nullptr));
-	return hoverName;
-}
-void CGPyramid::onHeroVisit (const CGHeroInstance * h) const
-{
-	if (bc)
-	{
-		BlockingDialog bd (true, false);
-		bd.player = h->getOwner();
-		bd.soundID = soundBase::MYSTERY;
-		bd.text << VLC->generaltexth->advobtxt[105];
-		cb->showBlockingDialog(&bd);
-	}
-	else
-	{
-		InfoWindow iw;
-		iw.player = h->getOwner();
-		iw.text << VLC->generaltexth->advobtxt[107];
-		iw.components.push_back (Component (Component::LUCK, 0 , -2, 0));
-		GiveBonus gb;
-		gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]);
-		gb.id = h->id.getNum();
-		cb->giveHeroBonus(&gb);
-		cb->showInfoDialog(&iw);
-	}
-}
-
-void CGPyramid::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
-{
-	if (result.winner == 0)
-	{
-		InfoWindow iw;
-		iw.player = hero->getOwner();
-		iw.text.addTxt (MetaString::ADVOB_TXT, 106);
-		iw.text.addTxt (MetaString::SPELL_NAME, spell);
-		if (!hero->getArt(ArtifactPosition::SPELLBOOK))
-			iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook
-		else if (hero->getSecSkillLevel(SecondarySkill::WISDOM) < 3)
-			iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom
-		else
-		{
-			std::set<SpellID> spells;
-			spells.insert (SpellID(spell));
-			cb->changeSpells (hero, true, spells);
-			iw.components.push_back(Component (Component::SPELL, spell, 0, 0));
-		}
-		cb->showInfoDialog(&iw);
-		cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0);
-	}
-}
-
-void CGKeys::setPropertyDer (ui8 what, ui32 val) //101-108 - enable key for player 1-8
-{
-	if (what >= 101 && what <= (100 + PlayerColor::PLAYER_LIMIT_I))
-	{
-		PlayerColor player(what-101);
-		playerKeyMap[player].insert((ui8)val);
-	}
-	else
-		logGlobal->errorStream() << boost::format("Unexpected properties requested to set: what=%d, val=%d") % (int)what % val;
-}
-
-bool CGKeys::wasMyColorVisited (PlayerColor player) const
-{
-	if (vstd::contains(playerKeyMap[player], subID)) //creates set if it's not there
-		return true;
-	else
-		return false;
-}
-
-const std::string& CGKeys::getHoverText() const
-{
-	bool visited = wasMyColorVisited (cb->getLocalPlayer());
-	hoverName = getName() + "\n" + visitedTxt(visited);
-	return hoverName;
-}
-
-
-const std::string CGKeys::getName() const
-{
-	std::string name;
-	name = VLC->generaltexth->tentColors[subID] + " " + VLC->objtypeh->getObjectName(ID);
-	return name;
-}
-
-bool CGKeymasterTent::wasVisited (PlayerColor player) const
-{
-	return wasMyColorVisited (player);
-}
-
-void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const
-{
-	int txt_id;
-	if (!wasMyColorVisited (h->getOwner()) )
-	{
-		cb->setObjProperty(id, h->tempOwner.getNum()+101, subID);
-		txt_id=19;
-	}
-	else
-		txt_id=20;
-    showInfoDialog(h,txt_id,soundBase::CAVEHEAD);
-}
-
-void CGBorderGuard::initObj()
-{
-	//ui32 m13489val = subID; //store color as quest info
-	blockVisit = true;
-}
-
-void CGBorderGuard::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
-{
-	text << std::pair<ui8,ui32>(11,18);
-}
-
-void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const
-{
-	if (!onHover)
-		text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER);
-}
-
-bool CGBorderGuard::checkQuest (const CGHeroInstance * h) const
-{
-	return wasMyColorVisited (h->tempOwner);
-}
-
-void CGBorderGuard::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if (wasMyColorVisited (h->getOwner()) )
-	{
-		BlockingDialog bd (true, false);
-		bd.player = h->getOwner();
-		bd.soundID = soundBase::QUEST;
-		bd.text.addTxt (MetaString::ADVOB_TXT, 17);
-		cb->showBlockingDialog (&bd);
-	}
-	else
-	{
-		showInfoDialog(h,18,soundBase::CAVEHEAD);
-
-		AddQuest aq;
-		aq.quest = QuestInfo (quest, this, visitablePos());
-		aq.player = h->tempOwner;
-		cb->sendAndApply (&aq);
-		//TODO: add this quest only once OR check for multiple instances later
-	}
-}
-
-void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	if (answer)
-		cb->removeObject(this);
-}
-
-void CGBorderGate::onHeroVisit( const CGHeroInstance * h ) const //TODO: passability
-{
-	if (!wasMyColorVisited (h->getOwner()) )
-	{
-		showInfoDialog(h,18,0);
-
-		AddQuest aq;
-		aq.quest = QuestInfo (quest, this, visitablePos());
-		aq.player = h->tempOwner;
-		cb->sendAndApply (&aq);
-	}
-}
-
-ui8 CGBorderGate::getPassableness() const
-{
-	ui8 ret = 0;
-	for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
-		ret |= wasMyColorVisited(PlayerColor(i))<<i;
-	return ret;
-}
-
-void CGMagi::initObj()
-{
-	if (ID == Obj::EYE_OF_MAGI)
-	{
-		blockVisit = true;
-		eyelist[subID].push_back(id);
-	}
-}
-void CGMagi::onHeroVisit(const CGHeroInstance * h) const
-{
-	if (ID == Obj::HUT_OF_MAGI)
-	{
-		showInfoDialog(h, 61, soundBase::LIGHTHOUSE);
-
-		if (!eyelist[subID].empty())
-		{
-			CenterView cv;
-			cv.player = h->tempOwner;
-			cv.focusTime = 2000;
-
-			FoWChange fw;
-			fw.player = h->tempOwner;
-			fw.mode = 1;
-
-			for(auto it : eyelist[subID])
-			{
-				const CGObjectInstance *eye = cb->getObj(it);
-
-				cb->getTilesInRange (fw.tiles, eye->pos, 10, h->tempOwner, 1);
-				cb->sendAndApply(&fw);
-				cv.pos = eye->pos;
-
-				cb->sendAndApply(&cv);
-			}
-			cv.pos = h->getPosition(false);
-			cb->sendAndApply(&cv);
-		}
-	}
-	else if (ID == Obj::EYE_OF_MAGI)
-	{
-		showInfoDialog(h,48,soundBase::invalid);
-	}
-
-}
-void CGBoat::initObj()
-{
-	hero = nullptr;
-}
-
-void CGSirens::initObj()
-{
-	blockVisit = true;
-}
-
-const std::string & CGSirens::getHoverText() const
-{
-	getNameVis(hoverName);
-	return hoverName;
-}
-
-void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
-{
-	InfoWindow iw;
-	iw.soundID = soundBase::DANGER;
-	iw.player = h->tempOwner;
-	if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Sirens
-	{
-		iw.text.addTxt(MetaString::ADVOB_TXT,133);
-	}
-	else
-	{
-		giveDummyBonus(h->id, Bonus::ONE_BATTLE);
-		TExpType xp = 0;
-
-		for (auto i = h->Slots().begin(); i != h->Slots().end(); i++)
-		{
-			TQuantity drown = i->second->count * 0.3;
-			if(drown)
-			{
-				cb->changeStackCount(StackLocation(h, i->first), -drown);
-				xp += drown * i->second->type->valOfBonuses(Bonus::STACK_HEALTH);
-			}
-		}
-
-		if(xp)
-		{
-			xp = h->calculateXp(xp);
-			iw.text.addTxt(MetaString::ADVOB_TXT,132);
-			iw.text.addReplacement(xp);
-			cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, xp, false);
-		}
-		else
-		{
-			iw.text.addTxt(MetaString::ADVOB_TXT,134);
-		}
-	}
-	cb->showInfoDialog(&iw);
-
-}
-
-//bool IShipyard::validLocation() const
-//{
-//	std::vector<int3> offsets;
-//	getOutOffsets(offsets);
-//
-//	TerrainTile *tile;
-//	for(int i = 0; i < offsets.size(); i++)
-//		if((tile = IObjectInterface::cb->getTile(o->pos + offsets[i]))  &&  tile->terType == TerrainTile::water) //tile is in the map and is water
-//			return true;
-//	return false;
-//}
-
-int3 IBoatGenerator::bestLocation() const
-{
-	std::vector<int3> offsets;
-	getOutOffsets(offsets);
-
-	for (auto & offset : offsets)
-	{
-		if (const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map
-		{
-            if (tile->terType == ETerrainType::WATER  &&  (!tile->blocked || tile->blockingObjects.front()->ID == 8)) //and is water and is not blocked or is blocked by boat
-				return o->pos + offset;
-		}
-	}
-	return int3 (-1,-1,-1);
-}
-
-IBoatGenerator::EGeneratorState IBoatGenerator::shipyardStatus() const
-{
-	int3 tile = bestLocation();
-	const TerrainTile *t = IObjectInterface::cb->getTile(tile);
-	if(!t)
-		return TILE_BLOCKED; //no available water
-	else if(!t->blockingObjects.size())
-		return GOOD; //OK
-	else if(t->blockingObjects.front()->ID == Obj::BOAT)
-		return BOAT_ALREADY_BUILT; //blocked with boat
-	else
-		return TILE_BLOCKED; //blocked
-}
-
-int IBoatGenerator::getBoatType() const
-{
-	//We make good ships by default
-	return 1;
-}
-
-
-IBoatGenerator::IBoatGenerator(const CGObjectInstance *O)
-: o(O)
-{
-}
-
-void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visitor) const
-{
-	switch(shipyardStatus())
-	{
-	case BOAT_ALREADY_BUILT:
-		out.addTxt(MetaString::GENERAL_TXT, 51);
-		break;
-	case TILE_BLOCKED:
-		if(visitor)
-		{
-			out.addTxt(MetaString::GENERAL_TXT, 134);
-			out.addReplacement(visitor->name);
-		}
-		else
-			out.addTxt(MetaString::ADVOB_TXT, 189);
-		break;
-	case NO_WATER:
-        logGlobal->errorStream() << "Shipyard without water!!! " << o->pos << "\t" << o->id;
-		return;
-	}
-}
-
-void IShipyard::getBoatCost( std::vector<si32> &cost ) const
-{
-	cost.resize(GameConstants::RESOURCE_QUANTITY);
-	cost[Res::WOOD] = 10;
-	cost[Res::GOLD] = 1000;
-}
-
-IShipyard::IShipyard(const CGObjectInstance *O)
-	: IBoatGenerator(O)
-{
-}
-
-IShipyard * IShipyard::castFrom( CGObjectInstance *obj )
-{
-	if(!obj)
-		return nullptr;
-
-	if(obj->ID == Obj::TOWN)
-	{
-		return static_cast<CGTownInstance*>(obj);
-	}
-	else if(obj->ID == Obj::SHIPYARD)
-	{
-		return static_cast<CGShipyard*>(obj);
-	}
-	else
-	{
-		return nullptr;
-	}
-}
-
-const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj )
-{
-	return castFrom(const_cast<CGObjectInstance*>(obj));
-}
-
-CGShipyard::CGShipyard()
-	:IShipyard(this)
-{
-}
-
-void CGShipyard::getOutOffsets( std::vector<int3> &offsets ) const
-{
-	// H J L K I
-	// A x S x B
-	// C E G F D
-	offsets += int3(-3,0,0), int3(1,0,0), //AB
-		int3(-3,1,0), int3(1,1,0), int3(-2,1,0), int3(0,1,0), int3(-1,1,0), //CDEFG
-		int3(-3,-1,0), int3(1,-1,0), int3(-2,-1,0), int3(0,-1,0), int3(-1,-1,0); //HIJKL
-}
-
-void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if(!cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner))
-		cb->setOwner(this, h->tempOwner);
-
-	auto s = shipyardStatus();
-	if(s != IBoatGenerator::GOOD)
-	{
-		InfoWindow iw;
-		iw.player = tempOwner;
-		getProblemText(iw.text, h);
-		cb->showInfoDialog(&iw);
-	}
-	else
-	{
-		openWindow(OpenWindow::SHIPYARD_WINDOW,id.getNum(),h->id.getNum());
-	}
-}
-
-void CCartographer::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if (!wasVisited (h->getOwner()) ) //if hero has not visited yet this cartographer
-	{
-		if (cb->getResource(h->tempOwner, Res::GOLD) >= 1000) //if he can afford a map
-		{
-			//ask if he wants to buy one
-			int text=0;
-			switch (subID)
-			{
-				case 0:
-					text = 25;
-					break;
-				case 1:
-					text = 26;
-					break;
-				case 2:
-					text = 27;
-					break;
-				default:
-                    logGlobal->warnStream() << "Unrecognized subtype of cartographer";
-			}
-			assert(text);
-			BlockingDialog bd (true, false);
-			bd.player = h->getOwner();
-			bd.soundID = soundBase::LIGHTHOUSE;
-			bd.text.addTxt (MetaString::ADVOB_TXT, text);
-			cb->showBlockingDialog (&bd);
-		}
-		else //if he cannot afford
-		{
-			showInfoDialog(h,28,soundBase::CAVEHEAD);
-		}
-	}
-	else //if he already visited carographer
-	{
-		showInfoDialog(h,24,soundBase::CAVEHEAD);
-	}
-}
-
-void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
-{
-	if (answer) //if hero wants to buy map
-	{
-		cb->giveResource (hero->tempOwner, Res::GOLD, -1000);
-		FoWChange fw;
-		fw.mode = 1;
-		fw.player = hero->tempOwner;
-
-		//subIDs of different types of cartographers:
-		//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());
-	}
-}
-
-void CGDenOfthieves::onHeroVisit (const CGHeroInstance * h) const
-{
-	cb->showThievesGuildWindow(h->tempOwner, id);
-}
-
-void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const
-{
-	InfoWindow iw;
-	iw.player = h->tempOwner;
-	TeamState *ts = cb->gameState()->getPlayerTeam(h->tempOwner);
-	assert(ts);
-	TeamID team = ts->id;
-
-	if(!wasVisited(team))
-	{
-		iw.text.addTxt(MetaString::ADVOB_TXT, 96);
-		cb->sendAndApply(&iw);
-
-		cb->setObjProperty(id, 20, h->tempOwner.getNum()); //increment general visited obelisks counter
-
-		openWindow(OpenWindow::PUZZLE_MAP, h->tempOwner.getNum());
-
-		cb->setObjProperty(id, 10, h->tempOwner.getNum()); //mark that particular obelisk as visited
-	}
-	else
-	{
-		iw.text.addTxt(MetaString::ADVOB_TXT, 97);
-		cb->sendAndApply(&iw);
-	}
-
-}
-
-void CGObelisk::initObj()
-{
-	obeliskCount++;
-}
-
-const std::string & CGObelisk::getHoverText() const
-{
-	bool visited = wasVisited(cb->getLocalPlayer());
-	hoverName = VLC->objtypeh->getObjectName(ID) + " " + visitedTxt(visited);
-	return hoverName;
-}
-
-void CGObelisk::setPropertyDer( ui8 what, ui32 val )
-{
-	CPlayersVisited::setPropertyDer(what, val);
-	switch(what)
-	{
-	case 20:
-		assert(val < PlayerColor::PLAYER_LIMIT_I);
-		visited[TeamID(val)]++;
-
-		if(visited[TeamID(val)] > obeliskCount)
-		{
-            logGlobal->errorStream() << "Error: Visited " << visited[TeamID(val)] << "\t\t" << obeliskCount;
-			assert(0);
-		}
-
-		break;
-	}
-}
-
-void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const
-{
-	if(h->tempOwner != tempOwner)
-	{
-		PlayerColor oldOwner = tempOwner;
-		cb->setOwner(this,h->tempOwner); //not ours? flag it!
-		showInfoDialog(h,69,soundBase::LIGHTHOUSE);
-		giveBonusTo(h->tempOwner);
-
-		if(oldOwner < PlayerColor::PLAYER_LIMIT) //remove bonus from old owner
-		{
-			RemoveBonus rb(RemoveBonus::PLAYER);
-			rb.whoID = oldOwner.getNum();
-			rb.source = Bonus::OBJECT;
-			rb.id = id.getNum();
-			cb->sendAndApply(&rb);
-		}
-	}
-}
-
-void CGLighthouse::initObj()
-{
-	if(tempOwner < PlayerColor::PLAYER_LIMIT)
-	{
-		giveBonusTo(tempOwner);
-	}
-}
-
-const std::string & CGLighthouse::getHoverText() const
-{
-	hoverName = VLC->objtypeh->getObjectName(ID);
-	//TODO: owned by %s player
-	return hoverName;
-}
-
-void CGLighthouse::giveBonusTo( PlayerColor player ) const
-{
-	GiveBonus gb(GiveBonus::PLAYER);
-	gb.bonus.type = Bonus::SEA_MOVEMENT;
-	gb.bonus.val = 500;
-	gb.id = player.getNum();
-	gb.bonus.duration = Bonus::PERMANENT;
-	gb.bonus.source = Bonus::OBJECT;
-	gb.bonus.sid = id.getNum();
-	cb->sendAndApply(&gb);
-}
-
-void CArmedInstance::randomizeArmy(int type)
-{
-	for (auto & elem : stacks)
-	{
-		int & randID = elem.second->idRand;
-		if(randID >= 0)
-		{
-			int level = randID / 2;
-			bool upgrade = randID % 2;
-			elem.second->setType(VLC->townh->factions[type]->town->creatures[level][upgrade]);
-
-			randID = -1;
-		}
-		assert(elem.second->valid(false));
-		assert(elem.second->armyObj == this);
-	}
-	return;
-}
-
-CArmedInstance::CArmedInstance()
-{
-	battle = nullptr;
-}
-
-//int CArmedInstance::valOfGlobalBonuses(CSelector selector) const
-//{
-////	if (tempOwner != NEUTRAL_PLAYER)
-//	return cb->gameState()->players[tempOwner].valOfBonuses(selector);
-//}
-
-void CArmedInstance::updateMoraleBonusFromArmy()
-{
-	if(!validTypes(false)) //object not randomized, don't bother
-		return;
-
-	Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE)));
-	if(!b)
-	{
-		b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1);
-		addNewBonus(b);
-	}
-
-	//number of alignments and presence of undead
-	std::set<TFaction> factions;
-	bool hasUndead = false;
-
-	for(auto slot : Slots())
-	{
-		const CStackInstance * inst = slot.second;
-		const CCreature * creature  = VLC->creh->creatures[inst->getCreatureID()];
-
-		factions.insert(creature->faction);
-		// Check for undead flag instead of faction (undead mummies are neutral)
-		hasUndead |= inst->hasBonusOfType(Bonus::UNDEAD);
-	}
-
-	size_t factionsInArmy = factions.size(); //town garrison seems to take both sets into account
-
-	// Take Angelic Alliance troop-mixing freedom of non-evil units into account.
-	if (hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX))
-	{
-		size_t mixableFactions = 0;
-
-		for(TFaction f : factions)
-		{
-			if (VLC->townh->factions[f]->alignment != EAlignment::EVIL)
-				mixableFactions++;
-		}
-		if (mixableFactions > 0)
-			factionsInArmy -= mixableFactions - 1;
-	}
-
-	if(factionsInArmy == 1)
-	{
-		b->val = +1;
-		b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
-	}
-	else if (!factions.empty()) // no bonus from empty garrison
-	{
-	 	b->val = 2 - factionsInArmy;
-		b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d
-	}
-	boost::algorithm::trim(b->description);
-
-	//-1 modifier for any Undead unit in army
-	const ui8 UNDEAD_MODIFIER_ID = -2;
-	Bonus *undeadModifier = getBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID));
- 	if(hasUndead)
-	{
-		if(!undeadModifier)
-			addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]));
-	}
-	else if(undeadModifier)
-		removeBonus(undeadModifier);
-
-}
-
-void CArmedInstance::armyChanged()
-{
-	updateMoraleBonusFromArmy();
-}
-
-CBonusSystemNode * CArmedInstance::whereShouldBeAttached(CGameState *gs)
-{
-	if(tempOwner < PlayerColor::PLAYER_LIMIT)
-		return gs->getPlayer(tempOwner);
-	else
-		return &gs->globalEffects;
-}
-
-CBonusSystemNode * CArmedInstance::whatShouldBeAttached()
-{
-	return this;
-}
-
-bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const
-{
-	switch(mode)
-	{
-	case EMarketMode::RESOURCE_RESOURCE:
-		{
-			double effectiveness = std::min((getMarketEfficiency() + 1.0) / 20.0, 0.5);
-
-			double r = VLC->objh->resVals[id1], //value of given resource
-				g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource
-
-			if(r>g) //if given resource is more expensive than wanted
-			{
-				val2 = ceil(r / g);
-				val1 = 1;
-			}
-			else //if wanted resource is more expensive
-			{
-				val1 = (g / r) + 0.5;
-				val2 = 1;
-			}
-		}
-		break;
-	case EMarketMode::CREATURE_RESOURCE:
-		{
-			const double effectivenessArray[] = {0.0, 0.3, 0.45, 0.50, 0.65, 0.7, 0.85, 0.9, 1.0};
-			double effectiveness = effectivenessArray[std::min(getMarketEfficiency(), 8)];
-
-			double r = VLC->creh->creatures[id1]->cost[6], //value of given creature in gold
-				g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource
-
-			if(r>g) //if given resource is more expensive than wanted
-			{
-				val2 = ceil(r / g);
-				val1 = 1;
-			}
-			else //if wanted resource is more expensive
-			{
-				val1 = (g / r) + 0.5;
-				val2 = 1;
-			}
-		}
-		break;
-	case EMarketMode::RESOURCE_PLAYER:
-		val1 = 1;
-		val2 = 1;
-		break;
-	case EMarketMode::RESOURCE_ARTIFACT:
-		{
-			double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6);
-			double r = VLC->objh->resVals[id1], //value of offered resource
-				g = VLC->arth->artifacts[id2]->price / effectiveness; //value of bought artifact in gold
-
-			if(id1 != 6) //non-gold prices are doubled
-				r /= 2;
-
-			val1 = std::max(1, (int)((g / r) + 0.5)); //don't sell arts for less than 1 resource
-			val2 = 1;
-		}
-		break;
-	case EMarketMode::ARTIFACT_RESOURCE:
-		{
-			double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6);
-			double r = VLC->arth->artifacts[id1]->price * effectiveness,
-				g = VLC->objh->resVals[id2];
-
-// 			if(id2 != 6) //non-gold prices are doubled
-// 				r /= 2;
-
-			val1 = 1;
-			val2 = std::max(1, (int)((r / g) + 0.5)); //at least one resource is given in return
-		}
-		break;
-	case EMarketMode::CREATURE_EXP:
-		{
-			val1 = 1;
-			val2 = (VLC->creh->creatures[id1]->AIValue / 40) * 5;
-		}
-		break;
-	case EMarketMode::ARTIFACT_EXP:
-		{
-			val1 = 1;
-
-			int givenClass = VLC->arth->artifacts[id1]->getArtClassSerial();
-			if(givenClass < 0 || givenClass > 3)
-			{
-				val2 = 0;
-				return false;
-			}
-
-			static const int expPerClass[] = {1000, 1500, 3000, 6000};
-			val2 = expPerClass[givenClass];
-		}
-		break;
-	default:
-		assert(0);
-		return false;
-	}
-
-	return true;
-}
-
-bool IMarket::allowsTrade(EMarketMode::EMarketMode mode) const
-{
-	return false;
-}
-
-int IMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const
-{
-	switch(mode)
-	{
-	case EMarketMode::RESOURCE_RESOURCE:
-	case EMarketMode::ARTIFACT_RESOURCE:
-	case EMarketMode::CREATURE_RESOURCE:
-			return -1;
-	default:
-			return 1;
-	}
-}
-
-std::vector<int> IMarket::availableItemsIds(EMarketMode::EMarketMode mode) const
-{
-	std::vector<int> ret;
-	switch(mode)
-	{
-	case EMarketMode::RESOURCE_RESOURCE:
-	case EMarketMode::ARTIFACT_RESOURCE:
-	case EMarketMode::CREATURE_RESOURCE:
-		for (int i = 0; i < 7; i++)
-			ret.push_back(i);
-	}
-	return ret;
-}
-
-const IMarket * IMarket::castFrom(const CGObjectInstance *obj, bool verbose /*= true*/)
-{
-	switch(obj->ID)
-	{
-	case Obj::TOWN:
-		return static_cast<const CGTownInstance*>(obj);
-	case Obj::ALTAR_OF_SACRIFICE:
-	case Obj::BLACK_MARKET:
-	case Obj::TRADING_POST:
-	case Obj::TRADING_POST_SNOW:
-	case Obj::FREELANCERS_GUILD:
-		return static_cast<const CGMarket*>(obj);
-	case Obj::UNIVERSITY:
-		return static_cast<const CGUniversity*>(obj);
-	default:
-		if(verbose)
-            logGlobal->errorStream() << "Cannot cast to IMarket object with ID " << obj->ID;
-		return nullptr;
-	}
-}
-
-IMarket::IMarket(const CGObjectInstance *O)
-	:o(O)
-{
-
-}
-
-std::vector<EMarketMode::EMarketMode> IMarket::availableModes() const
-{
-	std::vector<EMarketMode::EMarketMode> ret;
-	for (int i = 0; i < EMarketMode::MARTKET_AFTER_LAST_PLACEHOLDER; i++)
-		if(allowsTrade((EMarketMode::EMarketMode)i))
-			ret.push_back((EMarketMode::EMarketMode)i);
-
-	return ret;
-}
-
-void CGMarket::onHeroVisit(const CGHeroInstance * h) const
-{
-	openWindow(OpenWindow::MARKET_WINDOW,id.getNum(),h->id.getNum());
-}
-
-int CGMarket::getMarketEfficiency() const
-{
-	return 5;
-}
-
-bool CGMarket::allowsTrade(EMarketMode::EMarketMode mode) const
-{
-	switch(mode)
-	{
-	case EMarketMode::RESOURCE_RESOURCE:
-	case EMarketMode::RESOURCE_PLAYER:
-		switch(ID)
-		{
-		case Obj::TRADING_POST:
-		case Obj::TRADING_POST_SNOW:
-			return true;
-		default:
-			return false;
-		}
-	case EMarketMode::CREATURE_RESOURCE:
-		return ID == Obj::FREELANCERS_GUILD;
-	//case ARTIFACT_RESOURCE:
-	case EMarketMode::RESOURCE_ARTIFACT:
-		return ID == Obj::BLACK_MARKET;
-	case EMarketMode::ARTIFACT_EXP:
-	case EMarketMode::CREATURE_EXP:
-		return ID == Obj::ALTAR_OF_SACRIFICE; //TODO? check here for alignment of visiting hero? - would not be coherent with other checks here
-	case EMarketMode::RESOURCE_SKILL:
-		return ID == Obj::UNIVERSITY;
-	default:
-		return false;
-	}
-}
-
-int CGMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const
-{
-	return -1;
-}
-
-std::vector<int> CGMarket::availableItemsIds(EMarketMode::EMarketMode mode) const
-{
-	switch(mode)
-	{
-	case EMarketMode::RESOURCE_RESOURCE:
-	case EMarketMode::RESOURCE_PLAYER:
-		return IMarket::availableItemsIds(mode);
-	default:
-		return std::vector<int>();
-	}
-}
-
-CGMarket::CGMarket()
-	:IMarket(this)
-{
-}
-
-std::vector<int> CGBlackMarket::availableItemsIds(EMarketMode::EMarketMode mode) const
-{
-	switch(mode)
-	{
-	case EMarketMode::ARTIFACT_RESOURCE:
-		return IMarket::availableItemsIds(mode);
-	case EMarketMode::RESOURCE_ARTIFACT:
-		{
-			std::vector<int> ret;
-			for(const CArtifact *a : artifacts)
-				if(a)
-					ret.push_back(a->id);
-				else
-					ret.push_back(-1);
-			return ret;
-		}
-	default:
-		return std::vector<int>();
-	}
-}
-
-void CGBlackMarket::newTurn() const
-{
-	if(cb->getDate(Date::DAY_OF_MONTH) != 1) //new month
-		return;
-
-	SetAvailableArtifacts saa;
-	saa.id = id.getNum();
-	cb->pickAllowedArtsSet(saa.arts);
-	cb->sendAndApply(&saa);
-}
-
-void CGUniversity::initObj()
-{
-	std::vector<int> toChoose;
-	for(int i = 0; i < GameConstants::SKILL_QUANTITY; ++i)
-	{
-		if(cb->isAllowed(2, i))
-		{
-			toChoose.push_back(i);
-		}
-	}
-	if(toChoose.size() < 4)
-	{
-        logGlobal->warnStream()<<"Warning: less then 4 available skills was found by University initializer!";
-		return;
-	}
-
-	// get 4 skills
-	for(int i = 0; i < 4; ++i)
-	{
-		// move randomly one skill to selected and remove from list
-		auto it = RandomGeneratorUtil::nextItem(toChoose, cb->gameState()->getRandomGenerator());
-		skills.push_back(*it);
-		toChoose.erase(it);
-	}
-}
-
-std::vector<int> CGUniversity::availableItemsIds(EMarketMode::EMarketMode mode) const
-{
-	switch (mode)
-	{
-		case EMarketMode::RESOURCE_SKILL:
-			return skills;
-
-		default:
-			return std::vector <int> ();
-	}
-}
-
-void CGUniversity::onHeroVisit(const CGHeroInstance * h) const
-{
-	openWindow(OpenWindow::UNIVERSITY_WINDOW,id.getNum(),h->id.getNum());
-}
-
-GrowthInfo::Entry::Entry(const std::string &format, int _count)
-	: count(_count)
-{
-	description = boost::str(boost::format(format) % count);
-}
-
-GrowthInfo::Entry::Entry(int subID, BuildingID building, int _count)
-	: count(_count)
-{
-	description = boost::str(boost::format("%s %+d") % VLC->townh->factions[subID]->town->buildings.at(building)->Name() % count);
-}
-
-CTownAndVisitingHero::CTownAndVisitingHero()
-{
-	setNodeType(TOWN_AND_VISITOR);
-}
-
-int GrowthInfo::totalGrowth() const
+const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj )
 {
-	int ret = 0;
-	for(const Entry &entry : entries)
-		ret += entry.count;
-
-	return ret;
+	return castFrom(const_cast<CGObjectInstance*>(obj));
 }

+ 6 - 1220
lib/mapObjects/CObjectHandler.h

@@ -1,14 +1,10 @@
 #pragma once
 
-#include "../lib/CCreatureSet.h"
-#include "../lib/CTownHandler.h"
-#include "../lib/mapObjects/CObjectClassesHandler.h"
-#include "CArtHandler.h"
-#include "../lib/ConstTransitivePtr.h"
-#include "int3.h"
-#include "GameConstants.h"
-#include "ResourceSet.h"
-#include "CRandomGenerator.h"
+#include "ObjectTemplate.h"
+
+#include "../IGameCallback.h"
+#include "../int3.h"
+#include "../HeroBonus.h"
 
 /*
  * CObjectHandler.h, part of VCMI engine
@@ -20,86 +16,8 @@
  *
  */
 
-class CGameState;
-class CArtifactInstance;
-struct MetaString;
-struct BattleInfo;
-struct QuestInfo;
-class IGameCallback;
-struct BattleResult;
-class CGObjectInstance;
-class CScript;
 class CGHeroInstance;
-class CTown;
-class CHero;
-class CBuilding;
-class CSpell;
-class CGTownInstance;
-class CGTownBuilding;
-class CArtifact;
-class CSpecObjInfo;
-class CCastleEvent;
-struct TerrainTile;
-struct InfoWindow;
-struct Component;
-struct BankConfig;
-struct UpdateHerospecialty;
-struct NewArtifact;
-class CGBoat;
-class CArtifactSet;
-class CCommanderInstance;
-
-class DLL_LINKAGE CQuest
-{
-public:
-	enum Emission {MISSION_NONE = 0, MISSION_LEVEL = 1, MISSION_PRIMARY_STAT = 2, MISSION_KILL_HERO = 3, MISSION_KILL_CREATURE = 4,
-		MISSION_ART = 5, MISSION_ARMY = 6, MISSION_RESOURCES = 7, MISSION_HERO = 8, MISSION_PLAYER = 9, MISSION_KEYMASTER = 10};
-	enum Eprogress {NOT_ACTIVE, IN_PROGRESS, COMPLETE};
-
-	si32 qid; //unique quest id for serialization / identification
-
-	Emission missionType;
-	Eprogress progress;
-	si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit
-
-	ui32 m13489val;
-	std::vector<ui32> m2stats;
-	std::vector<ui16> m5arts; //artifacts id
-	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
-	ui8 textOption;
-	CStackBasicDescriptor stackToKill;
-	ui8 stackDirection;
-	std::string heroName; //backup of hero name
-	si32 heroPortrait;
-
-	std::string firstVisitText, nextVisitText, completedText;
-	bool isCustomFirst, isCustomNext, isCustomComplete;
-
-	CQuest(){missionType = MISSION_NONE;}; //default constructor
-	virtual ~CQuest(){};
-
-	virtual bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not
-	virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
-	virtual void getCompletionText (MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h = nullptr) const;
-	virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry
-	virtual void completeQuest (const CGHeroInstance * h) const {};
-	virtual void addReplacements(MetaString &out, const std::string &base) const;
-
-	bool operator== (const CQuest & quest) const
-	{
-		return (quest.qid == qid);
-	}
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & qid & missionType & progress & lastDay & m13489val & m2stats & m5arts & m6creatures & m7resources
-			& textOption & stackToKill & stackDirection & heroName & heroPortrait
-			& firstVisitText & nextVisitText & completedText & isCustomFirst & isCustomNext & isCustomComplete;
-	}
-};
+struct BattleResult;
 
 class DLL_LINKAGE IObjectInterface
 {
@@ -174,30 +92,6 @@ public:
 	}
 };
 
-class DLL_LINKAGE IMarket
-{
-public:
-	const CGObjectInstance *o;
-
-	IMarket(const CGObjectInstance *O);
-	virtual ~IMarket() {}
-
-	virtual int getMarketEfficiency() const =0;
-	virtual bool allowsTrade(EMarketMode::EMarketMode mode) const;
-	virtual int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const; //-1 if unlimited
-	virtual std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
-
-	bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units
-	std::vector<EMarketMode::EMarketMode> availableModes() const;
-
-	static const IMarket *castFrom(const CGObjectInstance *obj, bool verbose = true);
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & o;
-	}
-};
-
 class DLL_LINKAGE CGObjectInstance : public IObjectInterface
 {
 public:
@@ -269,1114 +163,6 @@ private:
 	CGObjectInstance * obj;
 };
 
-class CGHeroPlaceholder : public CGObjectInstance
-{
-public:
-	//subID stores id of hero type. If it's 0xff then following field is used
-	ui8 power;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-		h & power;
-	}
-};
-
-class DLL_LINKAGE CPlayersVisited: public CGObjectInstance
-{
-public:
-	std::set<PlayerColor> players; //players that visited this object
-
-	bool wasVisited(PlayerColor player) const;
-	bool wasVisited(TeamID team) const;
-	void setPropertyDer(ui8 what, ui32 val) override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-		h & players;
-	}
-};
-
-class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet
-{
-public:
-	BattleInfo *battle; //set to the current battle, if engaged
-
-	void randomizeArmy(int type);
-	virtual void updateMoraleBonusFromArmy();
-
-	void armyChanged() override;
-
-	//////////////////////////////////////////////////////////////////////////
-//	int valOfGlobalBonuses(CSelector selector) const; //used only for castle interface								???
-	virtual CBonusSystemNode *whereShouldBeAttached(CGameState *gs);
-	virtual CBonusSystemNode *whatShouldBeAttached();
-	//////////////////////////////////////////////////////////////////////////
-
-	CArmedInstance();
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-		h & static_cast<CBonusSystemNode&>(*this);
-		h & static_cast<CCreatureSet&>(*this);
-	}
-};
-
-class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet
-{
-public:
-	enum ECanDig
-	{
-		CAN_DIG, LACK_OF_MOVEMENT, WRONG_TERRAIN, TILE_OCCUPIED
-	};
-	//////////////////////////////////////////////////////////////////////////
-
-	ui8 moveDir; //format:	123
-					//		8 4
-					//		765
-	mutable ui8 isStanding, tacticFormationEnabled;
-
-	//////////////////////////////////////////////////////////////////////////
-
-	ConstTransitivePtr<CHero> type;
-	TExpType exp; //experience points
-	ui32 level; //current level of hero
-	std::string name; //may be custom
-	std::string biography; //if custom
-	si32 portrait; //may be custom
-	si32 mana; // remaining spell points
-	std::vector<std::pair<SecondarySkill,ui8> > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities
-	ui32 movement; //remaining movement points
-	ui8 sex;
-	bool inTownGarrison; // if hero is in town garrison
-	ConstTransitivePtr<CGTownInstance> visitedTown; //set if hero is visiting town or in the town garrison
-	ConstTransitivePtr<CCommanderInstance> commander;
-	const CGBoat *boat; //set to CGBoat when sailing
-
-
-	//std::vector<const CArtifact*> artifacts; //hero's artifacts from bag
-	//std::map<ui16, const CArtifact*> artifWorn; //map<position,artifact_id>; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5
-	std::set<SpellID> spells; //known spells (spell IDs)
-	std::set<ObjectInstanceID> visitedObjects;
-
-	struct DLL_LINKAGE Patrol
-	{
-		Patrol(){patrolling=false;patrolRadious=-1;};
-		bool patrolling;
-		ui32 patrolRadious;
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & patrolling & patrolRadious;
-		}
-	} patrol;
-
-	struct DLL_LINKAGE HeroSpecial : CBonusSystemNode
-	{
-		bool growsWithLevel;
-
-		HeroSpecial(){growsWithLevel = false;};
-
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & static_cast<CBonusSystemNode&>(*this);
-			h & growsWithLevel;
-		}
-	};
-
-	std::vector<HeroSpecial*> specialty;
-
-	struct DLL_LINKAGE SecondarySkillsInfo
-	{
-		//skills are determined, initialized at map start
-		//FIXME remove mutable
-		mutable CRandomGenerator rand;
-		ui8 magicSchoolCounter;
-		ui8 wisdomCounter;
-
-		void resetMagicSchoolCounter();
-		void resetWisdomCounter();
-
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & magicSchoolCounter & wisdomCounter & rand;
-		}
-	} skillsInfo;
-
-	int3 getSightCenter() const; //"center" tile from which the sight distance is calculated
-	int getSightRadious() const; //sight distance (should be used if player-owned structure)
-	//////////////////////////////////////////////////////////////////////////
-
-	int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral
-	void getOutOffsets(std::vector<int3> &offsets) const; //offsets to obj pos when we boat can be placed
-
-	//////////////////////////////////////////////////////////////////////////
-
-	bool hasSpellbook() const;
-	EAlignment::EAlignment getAlignment() const;
-	const std::string &getBiography() const;
-	bool needsLastStack()const;
-	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
-	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
-	bool canWalkOnSea() const;
-	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
-
-	// ----- primary and secondary skill, experience, level handling -----
-
-	/// Returns true if hero has lower level than should upon his experience.
-	bool gainsLevel() const;
-
-	/// Returns the next primary skill on level up. Can only be called if hero can gain a level up.
-	PrimarySkill::PrimarySkill nextPrimarySkill() const;
-
-	/// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up.
-	boost::optional<SecondarySkill> nextSecondarySkill() const;
-
-	/// Gets 0, 1 or 2 secondary skills which are proposed on hero level up.
-	std::vector<SecondarySkill> getLevelUpProposedSecondarySkills() const;
-
-	ui8 getSecSkillLevel(SecondarySkill skill) const; //0 - no skill
-
-	/// Returns true if hero has free secondary skill slot.
-	bool canLearnSkill() const;
-
-	void setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs);
-	void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value
-	void levelUp(std::vector<SecondarySkill> skills);
-
-	int maxMovePoints(bool onLand) const;
-	int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const;
-
-	//int getSpellSecLevel(int spell) const; //returns level of secondary ability (fire, water, earth, air magic) known to this hero and applicable to given spell; -1 if error
-	static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
-	double getFightingStrength() const; // takes attack / defense skill into account
-	double getMagicStrength() const; // takes knowledge / spell power skill into account
-	double getHeroStrength() const; // includes fighting and magic strength
-	ui64 getTotalStrength() const; // includes fighting strength and army strength
-	TExpType calculateXp(TExpType exp) const; //apply learning skill
-	ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const; //returns level on which given spell would be cast by this hero (0 - none, 1 - basic etc); optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic,
-	bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses
-	CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const;
-	void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const;
-	ECanDig diggingStatus() const; //0 - can dig; 1 - lack of movement; 2 -
-
-	//////////////////////////////////////////////////////////////////////////
-
-	void setType(si32 ID, si32 subID);
-
-	void initHero();
-	void initHero(HeroTypeID SUBID);
-
-	void putArtifact(ArtifactPosition pos, CArtifactInstance *art);
-	void putInBackpack(CArtifactInstance *art);
-	void initExp();
-	void initArmy(IArmyDescriptor *dst = nullptr);
-	//void giveArtifact (ui32 aid);
-	void pushPrimSkill(PrimarySkill::PrimarySkill which, int val);
-	ui8 maxlevelsToMagicSchool() const;
-	ui8 maxlevelsToWisdom() const;
-	void Updatespecialty();
-	void recreateSecondarySkillsBonuses();
-	void updateSkill(SecondarySkill which, int val);
-
-	CGHeroInstance();
-	virtual ~CGHeroInstance();
-	//////////////////////////////////////////////////////////////////////////
-	//
-	ArtBearer::ArtBearer bearerType() const override;
-	//////////////////////////////////////////////////////////////////////////
-
-	CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override;
-	std::string nodeName() const override;
-	void deserializationFix();
-
-	void initObj() override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	const std::string & getHoverText() const override;
-protected:
-	void setPropertyDer(ui8 what, ui32 val) override;//synchr
-
-private:
-	void levelUpAutomatically();
-
-public:
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this);
-		h & static_cast<CArtifactSet&>(*this);
-		h & exp & level & name & biography & portrait & mana & secSkills & movement
-			& sex & inTownGarrison & spells & patrol & moveDir & skillsInfo;
-		h & visitedTown & boat;
-		h & type & specialty & commander;
-		BONUS_TREE_DESERIALIZATION_FIX
-		//visitied town pointer will be restored by map serialization method
-	}
-};
-
-class DLL_LINKAGE CSpecObjInfo
-{
-public:
-	virtual ~CSpecObjInfo(){};
-	PlayerColor player; //owner
-};
-
-class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo
-{
-public:
-	bool asCastle;
-	ui32 identifier;
-	ui8 castles[2]; //allowed castles
-};
-
-class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo
-{
-public:
-	ui8 minLevel, maxLevel; //minimal and maximal level of creature in dwelling: <0, 6>
-};
-
-class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo
-{
-};
-
-class DLL_LINKAGE CGDwelling : public CArmedInstance
-{
-public:
-	typedef std::vector<std::pair<ui32, std::vector<CreatureID> > > TCreaturesSet;
-
-	CSpecObjInfo * info; //h3m info about dewlling
-	TCreaturesSet creatures; //creatures[level] -> <vector of alternative ids (base creature and upgrades, creatures amount>
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this) & creatures;
-	}
-
-	void initObj() override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void newTurn() const override;
-	void setProperty(ui8 what, ui32 val) override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-
-private:
-	void heroAcceptsCreatures(const CGHeroInstance *h) const;
-};
-
-class DLL_LINKAGE CGTownBuilding : public IObjectInterface
-{
-///basic class for town structures handled as map objects
-public:
-	BuildingID ID; //from buildig list
-	si32 id; //identifies its index on towns vector
-	CGTownInstance *town;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & ID & id;
-	}
-};
-class DLL_LINKAGE COPWBonus : public CGTownBuilding
-{///used for OPW bonusing structures
-public:
-	std::set<si32> visitors;
-	void setProperty(ui8 what, ui32 val) override;
-	void onHeroVisit (const CGHeroInstance * h) const override;
-
-	COPWBonus (BuildingID index, CGTownInstance *TOWN);
-	COPWBonus (){ID = BuildingID::NONE; town = nullptr;};
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGTownBuilding&>(*this);
-		h & visitors;
-	}
-};
-
-class DLL_LINKAGE CTownBonus : public CGTownBuilding
-{
-///used for one-time bonusing structures
-///feel free to merge inheritance tree
-public:
-	std::set<ObjectInstanceID> visitors;
-	void setProperty(ui8 what, ui32 val) override;
-	void onHeroVisit (const CGHeroInstance * h) const override;
-
-	CTownBonus (BuildingID index, CGTownInstance *TOWN);
-	CTownBonus (){ID = BuildingID::NONE; town = nullptr;};
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGTownBuilding&>(*this);
-		h & visitors;
-	}
-};
-
-class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode
-{
-public:
-	CTownAndVisitingHero();
-};
-
-struct DLL_LINKAGE GrowthInfo
-{
-	struct Entry
-	{
-		int count;
-		std::string description;
-		Entry(const std::string &format, int _count);
-		Entry(int subID, BuildingID building, int _count);
-	};
-
-	std::vector<Entry> entries;
-	int totalGrowth() const;
-};
-
-class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket
-{
-public:
-	enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3};
-
-	CTownAndVisitingHero townAndVis;
-	const CTown * town;
-	std::string name; // name of town
-	si32 builded; //how many buildings has been built this turn
-	si32 destroyed; //how many buildings has been destroyed this turn
-	ConstTransitivePtr<CGHeroInstance> garrisonHero, visitingHero;
-	ui32 identifier; //special identifier from h3m (only > RoE maps)
-	si32 alignment;
-	std::set<BuildingID> forbiddenBuildings, builtBuildings;
-	std::vector<CGTownBuilding*> bonusingBuildings;
-	std::vector<SpellID> possibleSpells, obligatorySpells;
-	std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
-	std::list<CCastleEvent> events;
-	std::pair<si32, si32> bonusValue;//var to store town bonuses (rampart = resources from mystic pond);
-
-	//////////////////////////////////////////////////////////////////////////
-	static std::vector<const CArtifact *> merchantArtifacts; //vector of artifacts available at Artifact merchant, NULLs possible (for making empty space when artifact is bought)
-	static std::vector<int> universitySkills;//skills for university of magic
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGDwelling&>(*this);
-		h & static_cast<IShipyard&>(*this);
-		h & static_cast<IMarket&>(*this);
-		h & name & builded & destroyed & identifier;
-		h & garrisonHero & visitingHero;
-		h & alignment & forbiddenBuildings & builtBuildings & bonusValue
-			& possibleSpells & obligatorySpells & spells & /*strInfo & */events & bonusingBuildings;
-
-		for (std::vector<CGTownBuilding*>::iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++)
-			(*i)->town = this;
-
-		h & town & townAndVis;
-		BONUS_TREE_DESERIALIZATION_FIX
-
-		vstd::erase_if(builtBuildings, [this](BuildingID building) -> bool
-		{
-			if(!town->buildings.count(building) ||  !town->buildings.at(building))
-			{
-				logGlobal->errorStream() << boost::format("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s")
-					% name % pos % building;
-				return true;
-			}
-			return false;
-		});
-	}
-	//////////////////////////////////////////////////////////////////////////
-
-	CBonusSystemNode *whatShouldBeAttached() override;
-	std::string nodeName() const override;
-	void updateMoraleBonusFromArmy() override;
-	void deserializationFix();
-	void recreateBuildingsBonuses();
-	bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr &prop, int subtype = -1); //returns true if building is built and bonus has been added
-	bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype = -1); //convienence version of above
-	void setVisitingHero(CGHeroInstance *h);
-	void setGarrisonedHero(CGHeroInstance *h);
-	const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself
-
-	//////////////////////////////////////////////////////////////////////////
-
-	ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used
-	int3 getSightCenter() const override; //"center" tile from which the sight distance is calculated
-	int getSightRadious() const override; //returns sight distance
-	int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral
-	void getOutOffsets(std::vector<int3> &offsets) const; //offsets to obj pos when we boat can be placed
-	int getMarketEfficiency() const override; //=market count
-	bool allowsTrade(EMarketMode::EMarketMode mode) const;
-	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
-
-	void setType(si32 ID, si32 subID);
-	void updateAppearance();
-
-	//////////////////////////////////////////////////////////////////////////
-
-	bool needsLastStack() const;
-	CGTownInstance::EFortLevel fortLevel() const;
-	int hallLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
-	int mageGuildLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
-	int getHordeLevel(const int & HID) const; //HID - 0 or 1; returns creature level or -1 if that horde structure is not present
-	int creatureGrowth(const int & level) const;
-	GrowthInfo getGrowthInfo(int level) const;
-	bool hasFort() const;
-	bool hasCapitol() const;
-	//checks if building is constructed and town has same subID
-	bool hasBuilt(BuildingID buildingID) const;
-	bool hasBuilt(BuildingID buildingID, int townID) const;
-	TResources dailyIncome() const; //calculates daily income of this town
-	int spellsAtLevel(int level, bool checkGuild) const; //levels are counted from 1 (1 - 5)
-	bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero
-	int getTownLevel() const;
-
-	void removeCapitols (PlayerColor owner) const;
-	void addHeroToStructureVisitors(const CGHeroInstance *h, si32 structureInstanceID) const; //hero must be visiting or garrisoned in town
-
-	CGTownInstance();
-	virtual ~CGTownInstance();
-
-	///IObjectInterface overrides
-	void newTurn() const override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void onHeroLeave(const CGHeroInstance * h) const override;
-	void initObj() override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-protected:
-	void setPropertyDer(ui8 what, ui32 val) override;
-};
-class DLL_LINKAGE CGPandoraBox : public CArmedInstance
-{
-public:
-	std::string message;
-	bool hasGuardians; //helper - after battle even though we have no stacks, allows us to know that there was battle
-
-	//gained things:
-	ui32 gainedExp;
-	si32 manaDiff; //amount of gained / lost mana
-	si32 moraleDiff; //morale modifier
-	si32 luckDiff; //luck modifier
-	TResources resources;//gained / lost resources
-	std::vector<si32> primskills;//gained / lost prim skills
-	std::vector<SecondarySkill> abilities; //gained abilities
-	std::vector<si32> abilityLevels; //levels of gained abilities
-	std::vector<ArtifactID> artifacts; //gained artifacts
-	std::vector<SpellID> spells; //gained spells
-	CCreatureSet creatures; //gained creatures
-
-	void initObj() override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-	void heroLevelUpDone(const CGHeroInstance *hero) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this);
-		h & message & hasGuardians & gainedExp & manaDiff & moraleDiff & luckDiff & resources & primskills
-			& abilities & abilityLevels & artifacts & spells & creatures;
-	}
-protected:
-	void giveContentsUpToExp(const CGHeroInstance *h) const;
-	void giveContentsAfterExp(const CGHeroInstance *h) const;
-private:
-	void getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const;
-	void getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const;
-};
-
-class DLL_LINKAGE CGEvent : public CGPandoraBox  //event objects
-{
-public:
-	bool removeAfterVisit; //true if event is removed after occurring
-	ui8 availableFor; //players whom this event is available for
-	bool computerActivate; //true if computer player can activate this event
-	bool humanActivate; //true if human player can activate this event
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGPandoraBox &>(*this);
-		h & removeAfterVisit & availableFor & computerActivate & humanActivate;
-	}
-
-	void onHeroVisit(const CGHeroInstance * h) const override;
-private:
-	void activated(const CGHeroInstance * h) const;
-};
-
-class DLL_LINKAGE CGCreature : public CArmedInstance //creatures on map
-{
-	enum Action {
-		FIGHT = -2, FLEE = -1, JOIN_FOR_FREE = 0 //values > 0 mean gold price
-	};
-
-public:
-	ui32 identifier; //unique code for this monster (used in missions)
-	si8 character; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage)
-	std::string message; //message printed for attacking hero
-	TResources resources; // resources given to hero that has won with monsters
-	ArtifactID gainedArtifact; //ID of artifact gained to hero, -1 if none
-	bool neverFlees; //if true, the troops will never flee
-	bool notGrowingTeam; //if true, number of units won't grow
-	ui64 temppower; //used to handle fractional stack growth for tiny stacks
-
-	bool refusedJoining;
-
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	const std::string & getHoverText() const override;
-	void initObj() override;
-	void newTurn() const override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-
-
-	struct DLL_LINKAGE formationInfo // info about merging stacks after battle back into one
-	{
-		si32 basicType;
-		ui32 randomFormation; //random seed used to determine number of stacks and is there's upgraded stack
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & basicType & randomFormation;
-		}
-	} formation;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this);
-		h & identifier & character & message & resources & gainedArtifact & neverFlees & notGrowingTeam & temppower;
-		h & refusedJoining & formation;
-	}
-protected:
-	void setPropertyDer(ui8 what, ui32 val) override;
-private:
-
-	void fight(const CGHeroInstance *h) const;
-	void flee( const CGHeroInstance * h ) const;
-	void fleeDecision(const CGHeroInstance *h, ui32 pursue) const;
-	void joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const;
-
-	int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0)
-
-};
-
-
-class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles
-{
-public:
-	std::string message;
-
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-		h & message;
-	}
-};
-
-class DLL_LINKAGE IQuestObject
-{
-public:
-	CQuest * quest;
-
-	IQuestObject(): quest(new CQuest()){};
-	virtual ~IQuestObject() {};
-	virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
-	virtual bool checkQuest (const CGHeroInstance * h) const;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & quest;
-	}
-};
-
-class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward
-{
-public:
-	enum ERewardType {NOTHING, EXPERIENCE, MANA_POINTS, MORALE_BONUS, LUCK_BONUS, RESOURCES, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE};
-	ERewardType rewardType;
-	si32 rID; //reward ID
-	si32 rVal; //reward value
-	std::string seerName;
-
-	CGSeerHut() : IQuestObject(){};
-	void initObj() override;
-	const std::string & getHoverText() const override;
-	void newTurn() const override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-
-	virtual void init();
-	int checkDirection() const; //calculates the region of map where monster is placed
-	void setObjToKill(); //remember creatures / heroes to kill after they are initialized
-	const CGHeroInstance *getHeroToKill(bool allowNull = false) const;
-	const CGCreature *getCreatureToKill(bool allowNull = false) const;
-	void getRolloverText (MetaString &text, bool onHover) const;
-	void getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h = nullptr) const;
-	void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects
-	virtual void completeQuest (const CGHeroInstance * h) const;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this) & static_cast<IQuestObject&>(*this);
-		h & rewardType & rID & rVal & seerName;
-	}
-protected:
-	void setPropertyDer(ui8 what, ui32 val) override;
-};
-
-class DLL_LINKAGE CGQuestGuard : public CGSeerHut
-{
-public:
-	CGQuestGuard() : CGSeerHut(){};
-	void init() override;
-	void completeQuest (const CGHeroInstance * h) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGSeerHut&>(*this);
-	}
-};
-
-class DLL_LINKAGE CGWitchHut : public CPlayersVisited
-{
-public:
-	std::vector<si32> allowedAbilities;
-	ui32 ability;
-
-	const std::string & getHoverText() const override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CPlayersVisited&>(*this);
-		h & allowedAbilities & ability;
-	}
-};
-
-
-class DLL_LINKAGE CGScholar : public CGObjectInstance
-{
-public:
-	enum EBonusType {PRIM_SKILL, SECONDARY_SKILL, SPELL, RANDOM = 255};
-	EBonusType bonusType;
-	ui16 bonusID; //ID of skill/spell
-
-//	void giveAnyBonus(const CGHeroInstance * h) const; //TODO: remove
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-		h & bonusType & bonusID;
-	}
-};
-
-class DLL_LINKAGE CGGarrison : public CArmedInstance
-{
-public:
-	bool removableUnits;
-
-	ui8 getPassableness() const;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this);
-		h & removableUnits;
-	}
-};
-
-class DLL_LINKAGE CGArtifact : public CArmedInstance
-{
-public:
-	CArtifactInstance *storedArtifact;
-	std::string message;
-
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-
-	void pick( const CGHeroInstance * h ) const;
-	void initObj() override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this);
-		h & message & storedArtifact;
-	}
-};
-
-class DLL_LINKAGE CGResource : public CArmedInstance
-{
-public:
-	ui32 amount; //0 if random
-	std::string message;
-
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-
-	void collectRes(PlayerColor player) const;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this);
-		h & amount & message;
-	}
-};
-
-class DLL_LINKAGE CGShrine : public CPlayersVisited
-{
-public:
-	SpellID spell; //id of spell or NONE if random
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
-	const std::string & getHoverText() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CPlayersVisited&>(*this);;
-		h & spell;
-	}
-};
-
-class DLL_LINKAGE CGMine : public CArmedInstance
-{
-public:
-	Res::ERes producedResource;
-	ui32 producedQuantity;
-	
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-
-	void flagMine(PlayerColor player) const;
-	void newTurn() const override;
-	void initObj() override;
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this);
-		h & producedResource & producedQuantity;
-	}
-	ui32 defaultResProduction();
-};
-
-class DLL_LINKAGE CGTeleport : public CGObjectInstance //teleports and subterranean gates
-{
-public:
-	static std::map<Obj, std::map<int, std::vector<ObjectInstanceID> > > objs; //teleports: map[ID][subID] => vector of ids
-	static std::vector<std::pair<ObjectInstanceID, ObjectInstanceID> > gates; //subterranean gates: pairs of ids
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
-	static void postInit();
-	static ObjectInstanceID getMatchingGate(ObjectInstanceID id); //receives id of one subterranean gate and returns id of the paired one, -1 if none
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-	}
-};
-
-class DLL_LINKAGE CGMagicWell : public CGObjectInstance //objects giving bonuses to luck/morale/movement
-{
-public:
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	const std::string & getHoverText() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-	}
-};
-
-class DLL_LINKAGE CGSirens : public CGObjectInstance
-{
-public:
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	const std::string & getHoverText() const override;
-	void initObj() override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-	}
-};
-
-class DLL_LINKAGE CGObservatory : public CGObjectInstance //Redwood observatory
-{
-public:
-	void onHeroVisit(const CGHeroInstance * h) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-	}
-};
-
-
-class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards
-{
-public:
-	static std::map <PlayerColor, std::set <ui8> > playerKeyMap; //[players][keysowned]
-	//SubID 0 - lightblue, 1 - green, 2 - red, 3 - darkblue, 4 - brown, 5 - purple, 6 - white, 7 - black
-
-	const std::string getName() const; //depending on color
-	bool wasMyColorVisited (PlayerColor player) const;
-
-	const std::string & getHoverText() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-	}
-protected:
-	void setPropertyDer(ui8 what, ui32 val) override;
-};
-
-class DLL_LINKAGE CGKeymasterTent : public CGKeys
-{
-public:
-	bool wasVisited (PlayerColor player) const;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-	}
-};
-
-class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject
-{
-public:
-	CGBorderGuard() : IQuestObject(){};
-	void initObj() override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-
-	void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
-	void getRolloverText (MetaString &text, bool onHover) const;
-	bool checkQuest (const CGHeroInstance * h) const;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<IQuestObject&>(*this);
-		h & static_cast<CGObjectInstance&>(*this);
-		h & blockVisit;
-	}
-};
-
-class DLL_LINKAGE CGBorderGate : public CGBorderGuard
-{
-public:
-	CGBorderGate() : CGBorderGuard(){};
-	void onHeroVisit(const CGHeroInstance * h) const override;
-
-	ui8 getPassableness() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGBorderGuard&>(*this); //need to serialize or object will be empty
-	}
-};
-
-class DLL_LINKAGE CGBoat : public CGObjectInstance
-{
-public:
-	ui8 direction;
-	const CGHeroInstance *hero;  //hero on board
-
-	void initObj() override;
-
-	CGBoat()
-	{
-		hero = nullptr;
-		direction = 4;
-	}
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this) & direction & hero;
-	}
-};
-
-class DLL_LINKAGE CBank : public CArmedInstance
-{
-	public:
-	int index; //banks have unusal numbering - see ZCRBANK.txt and initObj()
-	BankConfig *bc;
-	double multiplier; //for improved banks script
-	std::vector<ui32> artifacts; //fixed and deterministic
-	ui32 daycounter;
-
-	void initObj() override;
-	const std::string & getHoverText() const override;
-	void initialize() const;
-	void reset(ui16 var1);
-	void newTurn() const override;
-	bool wasVisited (PlayerColor player) const override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CArmedInstance&>(*this);
-		h & index & multiplier & artifacts & daycounter & bc;
-	}
-protected:
-	void setPropertyDer(ui8 what, ui32 val) override;
-};
-class DLL_LINKAGE CGPyramid : public CBank
-{
-public:
-	ui16 spell;
-
-	void initObj() override;
-	const std::string & getHoverText() const override;
-	void newTurn() const override {}; //empty, no reset
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CBank&>(*this);
-		h & spell;
-	}
-};
-
-class CGShipyard : public CGObjectInstance, public IShipyard
-{
-public:
-	void getOutOffsets(std::vector<int3> &offsets) const; //offsets to obj pos when we boat can be placed
-	CGShipyard();
-	void onHeroVisit(const CGHeroInstance * h) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-		h & static_cast<IShipyard&>(*this);
-	}
-};
-
-class DLL_LINKAGE CGMagi : public CGObjectInstance
-{
-public:
-	static std::map <si32, std::vector<ObjectInstanceID> > eyelist; //[subID][id], supports multiple sets as in H5
-
-	void initObj() override;
-	void onHeroVisit(const CGHeroInstance * h) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-	}
-};
-
-
-
-class DLL_LINKAGE CCartographer : public CPlayersVisited
-{
-///behaviour varies depending on surface and  floor
-public:
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CPlayersVisited&>(*this);
-	}
-};
-
-class DLL_LINKAGE CGDenOfthieves : public CGObjectInstance
-{
-	void onHeroVisit(const CGHeroInstance * h) const override;
-};
-
-class DLL_LINKAGE CGObelisk : public CPlayersVisited
-{
-public:
-	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;
-	const std::string & getHoverText() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CPlayersVisited&>(*this);
-	}
-protected:
-	void setPropertyDer(ui8 what, ui32 val) override;
-};
-
-class DLL_LINKAGE CGLighthouse : public CGObjectInstance
-{
-public:
-	void onHeroVisit(const CGHeroInstance * h) const override;
-	void initObj() override;
-	const std::string & getHoverText() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-	}
-	void giveBonusTo( PlayerColor player ) const;
-};
-
-class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket
-{
-public:
-	CGMarket();
-	///IObjectIntercae
-	void onHeroVisit(const CGHeroInstance * h) const override; //open trading window
-
-	///IMarket
-	int getMarketEfficiency() const override;
-	bool allowsTrade(EMarketMode::EMarketMode mode) const override;
-	int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited
-	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGObjectInstance&>(*this);
-		h & static_cast<IMarket&>(*this);
-	}
-};
-
-class DLL_LINKAGE CGBlackMarket : public CGMarket
-{
-public:
-	std::vector<const CArtifact *> artifacts; //available artifacts
-
-	void newTurn() const override; //reset artifacts for black market every month
-	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGMarket&>(*this);
-		h & artifacts;
-	}
-};
-
-class DLL_LINKAGE CGUniversity : public CGMarket
-{
-public:
-	std::vector<int> skills; //available skills
-
-	std::vector<int> availableItemsIds(EMarketMode::EMarketMode mode) const;
-	void initObj() override;//set skills for trade
-	void onHeroVisit(const CGHeroInstance * h) const override; //open window
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & static_cast<CGMarket&>(*this);
-		h & skills;
-	}
-};
-
 struct BankConfig
 {
 	BankConfig() {level = chance = upgradeChance = combatValue = value = rewardDifficulty = easiest = 0; };

+ 861 - 0
lib/mapObjects/CQuest.cpp

@@ -0,0 +1,861 @@
+/*
+ * CObjectHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CQuest.h"
+
+#include "NetPacks.h"
+#include "../client/CSoundBase.h"
+#include "../CGeneralTextHandler.h"
+#include "../CHeroHandler.h"
+#include "CObjectClassesHandler.h"
+
+using namespace boost::assign;
+
+std::map <PlayerColor, std::set <ui8> > CGKeys::playerKeyMap;
+
+///helpers
+static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
+{
+	InfoWindow iw;
+	iw.soundID = soundID;
+	iw.player = playerID;
+	iw.text.addTxt(MetaString::ADVOB_TXT,txtID);
+	IObjectInterface::cb->sendAndApply(&iw);
+}
+
+static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID)
+{
+	const PlayerColor playerID = h->getOwner();
+	showInfoDialog(playerID,txtID,soundID);
+}
+
+static std::string & visitedTxt(const bool visited)
+{
+	int id = visited ? 352 : 353;
+	return VLC->generaltexth->allTexts[id];
+}
+
+bool CQuest::checkQuest (const CGHeroInstance * h) const
+{
+	switch (missionType)
+	{
+		case MISSION_NONE:
+			return true;
+		case MISSION_LEVEL:
+			if (m13489val <= h->level)
+				return true;
+			return false;
+		case MISSION_PRIMARY_STAT:
+			for (int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
+			{
+				if (h->getPrimSkillLevel(static_cast<PrimarySkill::PrimarySkill>(i)) < m2stats[i])
+					return false;
+			}
+			return true;
+		case MISSION_KILL_HERO:
+		case MISSION_KILL_CREATURE:
+			if (!h->cb->getObjByQuestIdentifier(m13489val))
+				return true;
+			return false;
+		case MISSION_ART:
+			for (auto & elem : m5arts)
+			{
+				if (h->hasArt(elem))
+					continue;
+				return false; //if the artifact was not found
+			}
+			return true;
+		case MISSION_ARMY:
+			{
+				std::vector<CStackBasicDescriptor>::const_iterator cre;
+				TSlots::const_iterator it;
+				ui32 count;
+				for (cre = m6creatures.begin(); cre != m6creatures.end(); ++cre)
+				{
+					for (count = 0, it = h->Slots().begin(); it !=  h->Slots().end(); ++it)
+					{
+						if (it->second->type == cre->type)
+							count += it->second->count;
+					}
+					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 ?
+			{	//Quest has no direct access to callback
+				if (h->cb->getResource (h->tempOwner, i) < m7resources[i])
+					return false;
+			}
+			return true;
+		case MISSION_HERO:
+			if (m13489val == h->type->ID.getNum())
+				return true;
+			return false;
+		case MISSION_PLAYER:
+			if (m13489val == h->getOwner().getNum())
+				return true;
+			return false;
+		default:
+			return false;
+	}
+}
+
+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)
+	{
+		isCustom = isCustomFirst;
+		iwText << (text = firstVisitText);
+	}
+	else if (failRequirements)
+	{
+		isCustom = isCustomNext;
+		iwText << (text = nextVisitText);
+	}
+	switch (missionType)
+	{
+		case MISSION_LEVEL:
+			components.push_back(Component (Component::EXPERIENCE, 0, m13489val, 0));
+			if (!isCustom)
+				iwText.addReplacement(m13489val);
+			break;
+		case MISSION_PRIMARY_STAT:
+		{
+			MetaString loot;
+			for (int i = 0; i < 4; ++i)
+			{
+				if (m2stats[i])
+				{
+					components.push_back(Component (Component::PRIM_SKILL, i, m2stats[i], 0));
+					loot << "%d %s";
+					loot.addReplacement(m2stats[i]);
+					loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
+				}
+			}
+			if (!isCustom)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_KILL_HERO:
+			components.push_back(Component(Component::HERO_PORTRAIT, heroPortrait, 0, 0));
+			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)
+				iwText.addReplacement(VLC->heroh->heroes[m13489val]->name);
+			break;
+		case MISSION_KILL_CREATURE:
+			{
+				components.push_back(Component(stackToKill));
+				if (!isCustom)
+				{
+					addReplacements(iwText, text);
+				}
+			}
+			break;
+		case MISSION_ART:
+		{
+			MetaString loot;
+			for (auto & elem : m5arts)
+			{
+				components.push_back(Component (Component::ARTIFACT, elem, 0, 0));
+				loot << "%s";
+				loot.addReplacement(MetaString::ART_NAMES, elem);
+			}
+			if (!isCustom)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_ARMY:
+		{
+			MetaString loot;
+			for (auto & elem : m6creatures)
+			{
+				components.push_back(Component(elem));
+				loot << "%s";
+				loot.addReplacement(elem);
+			}
+			if (!isCustom)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_RESOURCES:
+		{
+			MetaString loot;
+			for (int i = 0; i < 7; ++i)
+			{
+				if (m7resources[i])
+				{
+					components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0));
+					loot << "%d %s";
+					loot.addReplacement(m7resources[i]);
+					loot.addReplacement(MetaString::RES_NAMES, i);
+				}
+			}
+			if (!isCustom)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_PLAYER:
+			components.push_back(Component (Component::FLAG, m13489val, 0, 0));
+			if (!isCustom)
+				iwText.addReplacement(VLC->generaltexth->colors[m13489val]);
+			break;
+	}
+}
+
+void CQuest::getRolloverText (MetaString &ms, bool onHover) const
+{
+	if (onHover)
+		ms << "\n\n";
+
+	ms << VLC->generaltexth->quests[missionType-1][onHover ? 3 : 4][textOption];
+
+	switch (missionType)
+	{
+		case MISSION_LEVEL:
+			ms.addReplacement(m13489val);
+			break;
+		case MISSION_PRIMARY_STAT:
+			{
+				MetaString loot;
+				for (int i = 0; i < 4; ++i)
+				{
+					if (m2stats[i])
+					{
+						loot << "%d %s";
+						loot.addReplacement(m2stats[i]);
+						loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
+					}
+				}
+				ms.addReplacement(loot.buildList());
+			}
+			break;
+		case MISSION_KILL_HERO:
+			ms.addReplacement(heroName);
+			break;
+		case MISSION_KILL_CREATURE:
+			ms.addReplacement(stackToKill);
+			break;
+		case MISSION_ART:
+			{
+				MetaString loot;
+				for (auto & elem : m5arts)
+				{
+					loot << "%s";
+					loot.addReplacement(MetaString::ART_NAMES, elem);
+				}
+				ms.addReplacement(loot.buildList());
+			}
+			break;
+		case MISSION_ARMY:
+			{
+				MetaString loot;
+				for (auto & elem : m6creatures)
+				{
+					loot << "%s";
+					loot.addReplacement(elem);
+				}
+				ms.addReplacement(loot.buildList());
+			}
+			break;
+		case MISSION_RESOURCES:
+			{
+				MetaString loot;
+				for (int i = 0; i < 7; ++i)
+				{
+					if (m7resources[i])
+					{
+						loot << "%d %s";
+						loot.addReplacement(m7resources[i]);
+						loot.addReplacement(MetaString::RES_NAMES, i);
+					}
+				}
+				ms.addReplacement(loot.buildList());
+			}
+			break;
+		case MISSION_HERO:
+			ms.addReplacement(VLC->heroh->heroes[m13489val]->name);
+			break;
+		case MISSION_PLAYER:
+			ms.addReplacement(VLC->generaltexth->colors[m13489val]);
+			break;
+		default:
+			break;
+	}
+}
+
+void CQuest::getCompletionText (MetaString &iwText, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
+{
+	iwText << completedText;
+	switch (missionType)
+	{
+		case CQuest::MISSION_LEVEL:
+			if (!isCustomComplete)
+				iwText.addReplacement(m13489val);
+			break;
+		case CQuest::MISSION_PRIMARY_STAT:
+			if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace
+			{
+				MetaString loot;
+				for (int i = 0; i < 4; ++i)
+				{
+					if (m2stats[i])
+					{
+						loot << "%d %s";
+						loot.addReplacement(m2stats[i]);
+						loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
+					}
+				}
+				if (!isCustomComplete)
+					iwText.addReplacement(loot.buildList());
+			}
+			break;
+		case CQuest::MISSION_ART:
+		{
+			MetaString loot;
+			for (auto & elem : m5arts)
+			{
+				loot << "%s";
+				loot.addReplacement(MetaString::ART_NAMES, elem);
+			}
+			if (!isCustomComplete)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case CQuest::MISSION_ARMY:
+		{
+			MetaString loot;
+			for (auto & elem : m6creatures)
+			{
+				loot << "%s";
+				loot.addReplacement(elem);
+			}
+			if (!isCustomComplete)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case CQuest::MISSION_RESOURCES:
+		{
+			MetaString loot;
+			for (int i = 0; i < 7; ++i)
+			{
+				if (m7resources[i])
+				{
+					loot << "%d %s";
+					loot.addReplacement(m7resources[i]);
+					loot.addReplacement(MetaString::RES_NAMES, i);
+				}
+			}
+			if (!isCustomComplete)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_KILL_HERO:
+		case MISSION_KILL_CREATURE:
+			if (!isCustomComplete)
+				addReplacements(iwText, completedText);
+			break;
+		case MISSION_HERO:
+			if (!isCustomComplete)
+				iwText.addReplacement(VLC->heroh->heroes[m13489val]->name);
+			break;
+		case MISSION_PLAYER:
+			if (!isCustomComplete)
+				iwText.addReplacement(VLC->generaltexth->colors[m13489val]);
+			break;
+	}
+}
+
+void CGSeerHut::setObjToKill()
+{
+	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)
+	{
+		quest->heroName = getHeroToKill(false)->name;
+		quest->heroPortrait = getHeroToKill(false)->portrait;
+	}
+}
+
+void CGSeerHut::init()
+{
+	seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, cb->gameState()->getRandomGenerator());
+	quest->textOption = cb->gameState()->getRandomGenerator().nextInt(2);
+}
+
+void CGSeerHut::initObj()
+{
+	init();
+
+	quest->progress = CQuest::NOT_ACTIVE;
+	if (quest->missionType)
+	{
+		if (!quest->isCustomFirst)
+			quest->firstVisitText = VLC->generaltexth->quests[quest->missionType-1][0][quest->textOption];
+		if (!quest->isCustomNext)
+			quest->nextVisitText = VLC->generaltexth->quests[quest->missionType-1][1][quest->textOption];
+		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];
+	}
+}
+
+void CGSeerHut::getRolloverText (MetaString &text, bool onHover) const
+{
+	quest->getRolloverText (text, onHover);//TODO: simplify?
+	if (!onHover)
+		text.addReplacement(seerName);
+}
+
+const std::string & CGSeerHut::getHoverText() const
+{
+	switch (ID)
+	{
+	case Obj::SEER_HUT:
+		if (quest->progress != CQuest::NOT_ACTIVE)
+		{
+			hoverName = VLC->generaltexth->allTexts[347];
+			boost::algorithm::replace_first(hoverName,"%s", seerName);
+		}
+		else //just seer hut
+			hoverName = VLC->objtypeh->getObjectName(ID);
+		break;
+	case Obj::QUEST_GUARD:
+		hoverName = VLC->objtypeh->getObjectName(ID);
+		break;
+	default:
+		logGlobal->debugStream() << "unrecognized quest object";
+	}
+	if (quest->progress & quest->missionType) //rollover when the quest is active
+	{
+		MetaString ms;
+		getRolloverText (ms, true);
+		hoverName += ms.toString();
+	}
+	return hoverName;
+}
+
+void CQuest::addReplacements(MetaString &out, const std::string &base) const
+{
+	switch(missionType)
+	{
+	case MISSION_KILL_CREATURE:
+		out.addReplacement(stackToKill);
+		if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster
+		{
+			out.addReplacement(VLC->generaltexth->arraytxt[147+stackDirection]);
+		}
+		break;
+	case MISSION_KILL_HERO:
+		out.addReplacement(heroName);
+		break;
+	}
+}
+
+bool IQuestObject::checkQuest(const CGHeroInstance* h) const
+{
+	return quest->checkQuest(h);
+}
+
+void IQuestObject::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
+{
+	quest->getVisitText (text,components, isCustom, FirstVisit, h);
+}
+
+void CGSeerHut::getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
+{
+	quest->getCompletionText (text, components, isCustom, h);
+	switch (rewardType)
+	{
+		case EXPERIENCE: components.push_back(Component (Component::EXPERIENCE, 0, h->calculateXp(rVal), 0));
+			break;
+		case MANA_POINTS: components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0));
+			break;
+		case MORALE_BONUS: components.push_back(Component (Component::MORALE, 0, rVal, 0));
+			break;
+		case LUCK_BONUS: components.push_back(Component (Component::LUCK, 0, rVal, 0));
+			break;
+		case RESOURCES: components.push_back(Component (Component::RESOURCE, rID, rVal, 0));
+			break;
+		case PRIMARY_SKILL: components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0));
+			break;
+		case SECONDARY_SKILL: components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0));
+			break;
+		case ARTIFACT: components.push_back(Component (Component::ARTIFACT, rID, 0, 0));
+			break;
+		case SPELL: components.push_back(Component (Component::SPELL, rID, 0, 0));
+			break;
+		case CREATURE: components.push_back(Component (Component::CREATURE, rID, rVal, 0));
+			break;
+	}
+}
+
+void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
+{
+	switch (what)
+	{
+		case 10:
+			quest->progress = static_cast<CQuest::Eprogress>(val);
+			break;
+	}
+}
+
+void CGSeerHut::newTurn() const
+{
+	if (quest->lastDay >= 0 && quest->lastDay < cb->getDate()-1) //time is up
+	{
+		cb->setObjProperty (id, 10, CQuest::COMPLETE);
+	}
+}
+
+void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
+{
+	InfoWindow iw;
+	iw.player = h->getOwner();
+	if (quest->progress < CQuest::COMPLETE)
+	{
+		bool firstVisit = !quest->progress;
+		bool failRequirements = !checkQuest(h);
+		bool isCustom=false;
+
+		if (firstVisit)
+		{
+			isCustom = quest->isCustomFirst;
+			cb->setObjProperty (id, 10, CQuest::IN_PROGRESS);
+
+			AddQuest aq;
+			aq.quest = QuestInfo (quest, this, visitablePos());
+			aq.player = h->tempOwner;
+			cb->sendAndApply (&aq); //TODO: merge with setObjProperty?
+		}
+		else if (failRequirements)
+		{
+			isCustom = quest->isCustomNext;
+		}
+
+		if (firstVisit || failRequirements)
+		{
+			getVisitText (iw.text, iw.components, isCustom, firstVisit, h);
+
+			cb->showInfoDialog(&iw);
+		}
+		if (!failRequirements) // propose completion, also on first visit
+		{
+			BlockingDialog bd (true, false);
+			bd.player = h->getOwner();
+			bd.soundID = soundBase::QUEST;
+
+			getCompletionText (bd.text, bd.components, isCustom, h);
+
+			cb->showBlockingDialog (&bd);
+			return;
+		}
+	}
+	else
+	{
+		iw.text << VLC->generaltexth->seerEmpty[quest->textOption];
+		if (ID == Obj::SEER_HUT)
+			iw.text.addReplacement(seerName);
+		cb->showInfoDialog(&iw);
+	}
+}
+
+int CGSeerHut::checkDirection() const
+{
+	int3 cord = getCreatureToKill()->pos;
+	if ((double)cord.x/(double)cb->getMapSize().x < 0.34) //north
+	{
+		if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //northwest
+			return 8;
+		else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //north
+			return 1;
+		else //northeast
+			return 2;
+	}
+	else if ((double)cord.x/(double)cb->getMapSize().x < 0.67) //horizontal
+	{
+		if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //west
+			return 7;
+		else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //central
+			return 9;
+		else //east
+			return 3;
+	}
+	else //south
+	{
+		if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //southwest
+			return 6;
+		else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //south
+			return 5;
+		else //southeast
+			return 4;
+	}
+}
+
+void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const
+{
+	if (accept)
+	{
+		switch (quest->missionType)
+		{
+			case CQuest::MISSION_ART:
+				for (auto & elem : quest->m5arts)
+				{
+					cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false)));
+				}
+				break;
+			case CQuest::MISSION_ARMY:
+					cb->takeCreatures(h->id, quest->m6creatures);
+				break;
+			case CQuest::MISSION_RESOURCES:
+				for (int i = 0; i < 7; ++i)
+				{
+					cb->giveResource(h->getOwner(), static_cast<Res::ERes>(i), -quest->m7resources[i]);
+				}
+				break;
+			default:
+				break;
+		}
+		cb->setObjProperty (id, 10, CQuest::COMPLETE); //mission complete
+		completeQuest(h); //make sure to remove QuestGuard at the very end
+	}
+}
+
+void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward
+{
+	switch (rewardType)
+	{
+		case EXPERIENCE:
+		{
+			TExpType expVal = h->calculateXp(rVal);
+			cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false);
+			break;
+		}
+		case MANA_POINTS:
+		{
+			cb->setManaPoints(h->id, h->mana+rVal);
+			break;
+		}
+		case MORALE_BONUS: case LUCK_BONUS:
+		{
+			Bonus hb(Bonus::ONE_WEEK, (rewardType == 3 ? Bonus::MORALE : Bonus::LUCK),
+				Bonus::OBJECT, rVal, h->id.getNum(), "", -1);
+			GiveBonus gb;
+			gb.id = h->id.getNum();
+			gb.bonus = hb;
+			cb->giveHeroBonus(&gb);
+		}
+			break;
+		case RESOURCES:
+			cb->giveResource(h->getOwner(), static_cast<Res::ERes>(rID), rVal);
+			break;
+		case PRIMARY_SKILL:
+			cb->changePrimSkill(h, static_cast<PrimarySkill::PrimarySkill>(rID), rVal, false);
+			break;
+		case SECONDARY_SKILL:
+			cb->changeSecSkill(h, SecondarySkill(rID), rVal, false);
+			break;
+		case ARTIFACT:
+			cb->giveHeroNewArtifact(h, VLC->arth->artifacts[rID],ArtifactPosition::FIRST_AVAILABLE);
+			break;
+		case SPELL:
+		{
+			std::set<SpellID> spell;
+			spell.insert (SpellID(rID));
+			cb->changeSpells(h, true, spell);
+		}
+			break;
+		case CREATURE:
+			{
+				CCreatureSet creatures;
+				creatures.setCreature(SlotID(0), CreatureID(rID), rVal);
+				cb->giveCreatures(this, h, creatures, false);
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
+{
+	const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
+	if(allowNull && !o)
+		return nullptr;
+	assert(o && (o->ID == Obj::HERO  ||  o->ID == Obj::PRISON));
+	return static_cast<const CGHeroInstance*>(o);
+}
+
+const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
+{
+	const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
+	if(allowNull && !o)
+		return nullptr;
+	assert(o && o->ID == Obj::MONSTER);
+	return static_cast<const CGCreature*>(o);
+}
+
+void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	finishQuest(hero, answer);
+}
+
+void CGQuestGuard::init()
+{
+	blockVisit = true;
+	quest->textOption = cb->gameState()->getRandomGenerator().nextInt(3, 5);
+}
+
+void CGQuestGuard::completeQuest(const CGHeroInstance *h) const
+{
+	cb->removeObject(this);
+}
+
+void CGKeys::setPropertyDer (ui8 what, ui32 val) //101-108 - enable key for player 1-8
+{
+	if (what >= 101 && what <= (100 + PlayerColor::PLAYER_LIMIT_I))
+	{
+		PlayerColor player(what-101);
+		playerKeyMap[player].insert((ui8)val);
+	}
+	else
+		logGlobal->errorStream() << boost::format("Unexpected properties requested to set: what=%d, val=%d") % (int)what % val;
+}
+
+bool CGKeys::wasMyColorVisited (PlayerColor player) const
+{
+	if (vstd::contains(playerKeyMap[player], subID)) //creates set if it's not there
+		return true;
+	else
+		return false;
+}
+
+const std::string& CGKeys::getHoverText() const
+{
+	bool visited = wasMyColorVisited (cb->getLocalPlayer());
+	hoverName = getName() + "\n" + visitedTxt(visited);
+	return hoverName;
+}
+
+
+const std::string CGKeys::getName() const
+{
+	std::string name;
+	name = VLC->generaltexth->tentColors[subID] + " " + VLC->objtypeh->getObjectName(ID);
+	return name;
+}
+
+bool CGKeymasterTent::wasVisited (PlayerColor player) const
+{
+	return wasMyColorVisited (player);
+}
+
+void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const
+{
+	int txt_id;
+	if (!wasMyColorVisited (h->getOwner()) )
+	{
+		cb->setObjProperty(id, h->tempOwner.getNum()+101, subID);
+		txt_id=19;
+	}
+	else
+		txt_id=20;
+	showInfoDialog(h,txt_id,soundBase::CAVEHEAD);
+}
+
+void CGBorderGuard::initObj()
+{
+	//ui32 m13489val = subID; //store color as quest info
+	blockVisit = true;
+}
+
+void CGBorderGuard::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
+{
+	text << std::pair<ui8,ui32>(11,18);
+}
+
+void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const
+{
+	if (!onHover)
+		text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER);
+}
+
+bool CGBorderGuard::checkQuest (const CGHeroInstance * h) const
+{
+	return wasMyColorVisited (h->tempOwner);
+}
+
+void CGBorderGuard::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if (wasMyColorVisited (h->getOwner()) )
+	{
+		BlockingDialog bd (true, false);
+		bd.player = h->getOwner();
+		bd.soundID = soundBase::QUEST;
+		bd.text.addTxt (MetaString::ADVOB_TXT, 17);
+		cb->showBlockingDialog (&bd);
+	}
+	else
+	{
+		showInfoDialog(h,18,soundBase::CAVEHEAD);
+
+		AddQuest aq;
+		aq.quest = QuestInfo (quest, this, visitablePos());
+		aq.player = h->tempOwner;
+		cb->sendAndApply (&aq);
+		//TODO: add this quest only once OR check for multiple instances later
+	}
+}
+
+void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	if (answer)
+		cb->removeObject(this);
+}
+
+void CGBorderGate::onHeroVisit( const CGHeroInstance * h ) const //TODO: passability
+{
+	if (!wasMyColorVisited (h->getOwner()) )
+	{
+		showInfoDialog(h,18,0);
+
+		AddQuest aq;
+		aq.quest = QuestInfo (quest, this, visitablePos());
+		aq.player = h->tempOwner;
+		cb->sendAndApply (&aq);
+	}
+}
+
+ui8 CGBorderGate::getPassableness() const
+{
+	ui8 ret = 0;
+	for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
+		ret |= wasMyColorVisited(PlayerColor(i))<<i;
+	return ret;
+}

+ 198 - 0
lib/mapObjects/CQuest.h

@@ -0,0 +1,198 @@
+#pragma once
+
+#include "CObjectHandler.h"
+#include "CGArmedInstance.h"
+
+#include "../CCreatureSet.h"
+#include "../NetPacksBase.h"
+
+/*
+ * CObjectHandler.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
+ *
+ */
+
+class DLL_LINKAGE CQuest
+{
+public:
+	enum Emission {MISSION_NONE = 0, MISSION_LEVEL = 1, MISSION_PRIMARY_STAT = 2, MISSION_KILL_HERO = 3, MISSION_KILL_CREATURE = 4,
+		MISSION_ART = 5, MISSION_ARMY = 6, MISSION_RESOURCES = 7, MISSION_HERO = 8, MISSION_PLAYER = 9, MISSION_KEYMASTER = 10};
+	enum Eprogress {NOT_ACTIVE, IN_PROGRESS, COMPLETE};
+
+	si32 qid; //unique quest id for serialization / identification
+
+	Emission missionType;
+	Eprogress progress;
+	si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit
+
+	ui32 m13489val;
+	std::vector<ui32> m2stats;
+	std::vector<ui16> m5arts; //artifacts id
+	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
+	ui8 textOption;
+	CStackBasicDescriptor stackToKill;
+	ui8 stackDirection;
+	std::string heroName; //backup of hero name
+	si32 heroPortrait;
+
+	std::string firstVisitText, nextVisitText, completedText;
+	bool isCustomFirst, isCustomNext, isCustomComplete;
+
+	CQuest(){missionType = MISSION_NONE;}; //default constructor
+	virtual ~CQuest(){};
+
+	virtual bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not
+	virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
+	virtual void getCompletionText (MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h = nullptr) const;
+	virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry
+	virtual void completeQuest (const CGHeroInstance * h) const {};
+	virtual void addReplacements(MetaString &out, const std::string &base) const;
+
+	bool operator== (const CQuest & quest) const
+	{
+		return (quest.qid == qid);
+	}
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & qid & missionType & progress & lastDay & m13489val & m2stats & m5arts & m6creatures & m7resources
+			& textOption & stackToKill & stackDirection & heroName & heroPortrait
+			& firstVisitText & nextVisitText & completedText & isCustomFirst & isCustomNext & isCustomComplete;
+	}
+};
+
+class DLL_LINKAGE IQuestObject
+{
+public:
+	CQuest * quest;
+
+	IQuestObject(): quest(new CQuest()){};
+	virtual ~IQuestObject() {};
+	virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
+	virtual bool checkQuest (const CGHeroInstance * h) const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & quest;
+	}
+};
+
+class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward
+{
+public:
+	enum ERewardType {NOTHING, EXPERIENCE, MANA_POINTS, MORALE_BONUS, LUCK_BONUS, RESOURCES, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE};
+	ERewardType rewardType;
+	si32 rID; //reward ID
+	si32 rVal; //reward value
+	std::string seerName;
+
+	CGSeerHut() : IQuestObject(){};
+	void initObj() override;
+	const std::string & getHoverText() const override;
+	void newTurn() const override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+
+	virtual void init();
+	int checkDirection() const; //calculates the region of map where monster is placed
+	void setObjToKill(); //remember creatures / heroes to kill after they are initialized
+	const CGHeroInstance *getHeroToKill(bool allowNull = false) const;
+	const CGCreature *getCreatureToKill(bool allowNull = false) const;
+	void getRolloverText (MetaString &text, bool onHover) const;
+	void getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h = nullptr) const;
+	void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects
+	virtual void completeQuest (const CGHeroInstance * h) const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this) & static_cast<IQuestObject&>(*this);
+		h & rewardType & rID & rVal & seerName;
+	}
+protected:
+	void setPropertyDer(ui8 what, ui32 val) override;
+};
+
+class DLL_LINKAGE CGQuestGuard : public CGSeerHut
+{
+public:
+	CGQuestGuard() : CGSeerHut(){};
+	void init() override;
+	void completeQuest (const CGHeroInstance * h) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGSeerHut&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards
+{
+public:
+	static std::map <PlayerColor, std::set <ui8> > playerKeyMap; //[players][keysowned]
+	//SubID 0 - lightblue, 1 - green, 2 - red, 3 - darkblue, 4 - brown, 5 - purple, 6 - white, 7 - black
+
+	const std::string getName() const; //depending on color
+	bool wasMyColorVisited (PlayerColor player) const;
+
+	const std::string & getHoverText() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+	}
+protected:
+	void setPropertyDer(ui8 what, ui32 val) override;
+};
+
+class DLL_LINKAGE CGKeymasterTent : public CGKeys
+{
+public:
+	bool wasVisited (PlayerColor player) const;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject
+{
+public:
+	CGBorderGuard() : IQuestObject(){};
+	void initObj() override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+
+	void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const;
+	void getRolloverText (MetaString &text, bool onHover) const;
+	bool checkQuest (const CGHeroInstance * h) const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<IQuestObject&>(*this);
+		h & static_cast<CGObjectInstance&>(*this);
+		h & blockVisit;
+	}
+};
+
+class DLL_LINKAGE CGBorderGate : public CGBorderGuard
+{
+public:
+	CGBorderGate() : CGBorderGuard(){};
+	void onHeroVisit(const CGHeroInstance * h) const override;
+
+	ui8 getPassableness() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGBorderGuard&>(*this); //need to serialize or object will be empty
+	}
+};

+ 3 - 2
lib/mapObjects/CRewardableConstructor.h

@@ -1,8 +1,9 @@
 #pragma once
 
-#include "mapObjects/CRewardableObject.h"
+#include "CRewardableObject.h"
 #include "CObjectClassesHandler.h"
-#include "JsonNode.h"
+
+#include "../JsonNode.h"
 
 /*
  * CObjectConstructor.h, part of VCMI engine

+ 2 - 0
lib/mapObjects/CRewardableObject.cpp

@@ -15,6 +15,8 @@
 #include "../client/CSoundBase.h"
 #include "NetPacks.h"
 
+#include "CObjectClassesHandler.h"
+
 bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const
 {
 	if (dayOfWeek != 0)

+ 3 - 1
lib/mapObjects/CRewardableObject.h

@@ -1,7 +1,9 @@
 #pragma once
 
 #include "CObjectHandler.h"
-#include "NetPacksBase.h"
+#include "CGArmedInstance.h"
+
+#include "../NetPacksBase.h"
 
 /*
  * CRewardableObject.h, part of VCMI engine

+ 25 - 0
lib/mapObjects/MapObjects.h

@@ -0,0 +1,25 @@
+#pragma once
+
+/*
+ * MapObjects.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
+ *
+ */
+
+// Helper header that includes all map objects, similar to old CObjectHandler.h
+// Possible TODO - remove this header after CObjectHandler.cpp will be fully split into smaller files
+#include "CObjectHandler.h"
+
+#include "CGArmedInstance.h"
+#include "CGBank.h"
+#include "CGHeroInstance.h"
+#include "CGMarket.h"
+#include "CGTownInstance.h"
+#include "CGPandoraBox.h"
+#include "CRewardableObject.h"
+#include "MiscObjects.h"
+#include "CQuest.h"

+ 1574 - 0
lib/mapObjects/MiscObjects.cpp

@@ -0,0 +1,1574 @@
+/*
+ * CObjectHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "MiscObjects.h"
+
+#include "NetPacks.h"
+#include "CGeneralTextHandler.h"
+//#include "CHeroHandler.h"
+#include "../client/CSoundBase.h"
+
+#include "CObjectClassesHandler.h"
+#include "../CSpellHandler.h"
+
+using namespace boost::assign;
+
+std::map<Obj, std::map<int, std::vector<ObjectInstanceID> > > CGTeleport::objs;
+std::vector<std::pair<ObjectInstanceID, ObjectInstanceID> > CGTeleport::gates;
+std::map <si32, std::vector<ObjectInstanceID> > CGMagi::eyelist;
+ui8 CGObelisk::obeliskCount; //how many obelisks are on map
+std::map<TeamID, ui8> CGObelisk::visited; //map: team_id => how many obelisks has been visited
+
+///helpers
+static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1)
+{
+	OpenWindow ow;
+	ow.window = type;
+	ow.id1 = id1;
+	ow.id2 = id2;
+	IObjectInterface::cb->sendAndApply(&ow);
+}
+
+static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
+{
+	InfoWindow iw;
+	iw.soundID = soundID;
+	iw.player = playerID;
+	iw.text.addTxt(MetaString::ADVOB_TXT,txtID);
+	IObjectInterface::cb->sendAndApply(&iw);
+}
+
+static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID)
+{
+	const PlayerColor playerID = h->getOwner();
+	showInfoDialog(playerID,txtID,soundID);
+}
+
+static std::string & visitedTxt(const bool visited)
+{
+	int id = visited ? 352 : 353;
+	return VLC->generaltexth->allTexts[id];
+}
+
+void CPlayersVisited::setPropertyDer( ui8 what, ui32 val )
+{
+	if(what == 10)
+		players.insert(PlayerColor(val));
+}
+
+bool CPlayersVisited::wasVisited( PlayerColor player ) const
+{
+	return vstd::contains(players,player);
+}
+
+bool CPlayersVisited::wasVisited( TeamID team ) const
+{
+	for(auto i : players)
+	{
+		if(cb->getPlayer(i)->team == team)
+			return true;
+	}
+	return false;
+}
+
+const std::string & CGCreature::getHoverText() const
+{
+	if(stacks.empty())
+	{
+		static const std::string errorValue("!!!INVALID_STACK!!!");
+
+		//should not happen...
+		logGlobal->errorStream() << "Invalid stack at tile " << pos << ": subID=" << subID << "; id=" << id;
+		return errorValue; // references to temporary are illegal - use pre-constructed string
+	}
+
+	MetaString ms;
+	int pom = stacks.begin()->second->getQuantityID();
+	pom = 172 + 3*pom;
+	ms.addTxt(MetaString::ARRAY_TXT,pom);
+	ms << " " ;
+	ms.addTxt(MetaString::CRE_PL_NAMES,subID);
+	ms.toString(hoverName);
+
+	if(const CGHeroInstance *selHero = cb->getSelectedHero(cb->getCurrentPlayer()))
+	{
+		const JsonNode & texts = VLC->generaltexth->localizedTexts["adventureMap"]["monsterThreat"];
+
+		hoverName += texts["title"].String();
+		int choice;
+		double ratio = ((double)getArmyStrength() / selHero->getTotalStrength());
+		     if (ratio < 0.1)  choice = 0;
+		else if (ratio < 0.25) choice = 1;
+		else if (ratio < 0.6)  choice = 2;
+		else if (ratio < 0.9)  choice = 3;
+		else if (ratio < 1.1)  choice = 4;
+		else if (ratio < 1.3)  choice = 5;
+		else if (ratio < 1.8)  choice = 6;
+		else if (ratio < 2.5)  choice = 7;
+		else if (ratio < 4)    choice = 8;
+		else if (ratio < 8)    choice = 9;
+		else if (ratio < 20)   choice = 10;
+		else                   choice = 11;
+		hoverName += texts["levels"].Vector()[choice].String();
+	}
+	return hoverName;
+}
+void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
+{
+	int action = takenAction(h);
+	switch( action ) //decide what we do...
+	{
+	case FIGHT:
+		fight(h);
+		break;
+	case FLEE: //flee
+		{
+			flee(h);
+			break;
+		}
+	case JOIN_FOR_FREE: //join for free
+		{
+			BlockingDialog ynd(true,false);
+			ynd.player = h->tempOwner;
+			ynd.text.addTxt(MetaString::ADVOB_TXT, 86);
+			ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID);
+			cb->showBlockingDialog(&ynd);
+			break;
+		}
+	default: //join for gold
+		{
+			assert(action > 0);
+
+			//ask if player agrees to pay gold
+			BlockingDialog ynd(true,false);
+			ynd.player = h->tempOwner;
+			std::string tmp = VLC->generaltexth->advobtxt[90];
+			boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast<std::string>(getStackCount(SlotID(0))));
+			boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast<std::string>(action));
+			boost::algorithm::replace_first(tmp,"%s",VLC->creh->creatures[subID]->namePl);
+			ynd.text << tmp;
+			cb->showBlockingDialog(&ynd);
+			break;
+		}
+	}
+
+}
+
+void CGCreature::initObj()
+{
+	blockVisit = true;
+	switch(character)
+	{
+	case 0:
+		character = -4;
+		break;
+	case 1:
+		character = cb->gameState()->getRandomGenerator().nextInt(1, 7);
+		break;
+	case 2:
+		character = cb->gameState()->getRandomGenerator().nextInt(1, 10);
+		break;
+	case 3:
+		character = cb->gameState()->getRandomGenerator().nextInt(4, 10);
+		break;
+	case 4:
+		character = 10;
+		break;
+	}
+
+	stacks[SlotID(0)]->setType(CreatureID(subID));
+	TQuantity &amount = stacks[SlotID(0)]->count;
+	CCreature &c = *VLC->creh->creatures[subID];
+	if(amount == 0)
+	{
+		amount = cb->gameState()->getRandomGenerator().nextInt(c.ammMin, c.ammMax);
+
+		if(amount == 0) //armies with 0 creatures are illegal
+		{
+            logGlobal->warnStream() << "Problem: stack " << nodeName() << " cannot have 0 creatures. Check properties of " << c.nodeName();
+			amount = 1;
+		}
+	}
+	formation.randomFormation = cb->gameState()->getRandomGenerator().nextInt();
+
+	temppower = stacks[SlotID(0)]->count * 1000;
+	refusedJoining = false;
+}
+
+void CGCreature::newTurn() const
+{//Works only for stacks of single type of size up to 2 millions
+	if (stacks.begin()->second->count < VLC->modh->settings.CREEP_SIZE && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1)
+	{
+		ui32 power = temppower * (100 +  VLC->modh->settings.WEEKLY_GROWTH)/100;
+		cb->setObjProperty(id, ObjProperty::MONSTER_COUNT, std::min (power/1000 , (ui32)VLC->modh->settings.CREEP_SIZE)); //set new amount
+		cb->setObjProperty(id, ObjProperty::MONSTER_POWER, power); //increase temppower
+	}
+	if (VLC->modh->modules.STACK_EXP)
+		cb->setObjProperty(id, ObjProperty::MONSTER_EXP, VLC->modh->settings.NEUTRAL_STACK_EXP); //for testing purpose
+}
+void CGCreature::setPropertyDer(ui8 what, ui32 val)
+{
+	switch (what)
+	{
+		case ObjProperty::MONSTER_COUNT:
+			stacks[SlotID(0)]->count = val;
+			break;
+		case ObjProperty::MONSTER_POWER:
+			temppower = val;
+			break;
+		case ObjProperty::MONSTER_EXP:
+			giveStackExp(val);
+			break;
+		case ObjProperty::MONSTER_RESTORE_TYPE:
+			formation.basicType = val;
+			break;
+		case ObjProperty::MONSTER_REFUSED_JOIN:
+			refusedJoining = val;
+			break;
+	}
+}
+
+int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
+{
+	//calculate relative strength of hero and creatures armies
+	double relStrength = double(h->getTotalStrength()) / getArmyStrength();
+
+	int powerFactor;
+	if(relStrength >= 7)
+		powerFactor = 11;
+
+	else if(relStrength >= 1)
+		powerFactor = (int)(2*(relStrength-1));
+
+	else if(relStrength >= 0.5)
+		powerFactor = -1;
+
+	else if(relStrength >= 0.333)
+		powerFactor = -2;
+	else
+		powerFactor = -3;
+
+	std::set<CreatureID> myKindCres; //what creatures are the same kind as we
+	const CCreature * myCreature = VLC->creh->creatures[subID];
+	myKindCres.insert(myCreature->idNumber); //we
+	myKindCres.insert(myCreature->upgrades.begin(), myCreature->upgrades.end()); //our upgrades
+
+	for(ConstTransitivePtr<CCreature> &crea : VLC->creh->creatures)
+	{
+		if(vstd::contains(crea->upgrades, myCreature->idNumber)) //it's our base creatures
+			myKindCres.insert(crea->idNumber);
+	}
+
+	int count = 0, //how many creatures of similar kind has hero
+		totalCount = 0;
+
+	for (auto & elem : h->Slots())
+	{
+		if(vstd::contains(myKindCres,elem.second->type->idNumber))
+			count += elem.second->count;
+		totalCount += elem.second->count;
+	}
+
+	int sympathy = 0; // 0 if hero have no similar creatures
+	if(count)
+		sympathy++; // 1 - if hero have at least 1 similar creature
+	if(count*2 > totalCount)
+		sympathy++; // 2 - hero have similar creatures more that 50%
+
+	int charisma = powerFactor + h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy;
+
+	if(charisma < character) //creatures will fight
+		return -2;
+
+	if (allowJoin)
+	{
+		if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy + 1 >= character)
+			return 0; //join for free
+
+		else if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) * 2  +  sympathy  +  1 >= character)
+			return VLC->creh->creatures[subID]->cost[6] * getStackCount(SlotID(0)); //join for gold
+	}
+
+	//we are still here - creatures have not joined hero, flee or fight
+
+	if (charisma > character)
+		return -1; //flee
+	else
+		return -2; //fight
+}
+
+void CGCreature::fleeDecision(const CGHeroInstance *h, ui32 pursue) const
+{
+	if(refusedJoining)
+		cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, false);
+
+	if(pursue)
+	{
+		fight(h);
+	}
+	else
+	{
+		cb->removeObject(this);
+	}
+}
+
+void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const
+{
+	if(!accept)
+	{
+		if(takenAction(h,false) == -1) //they flee
+		{
+			cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, true);
+			flee(h);
+		}
+		else //they fight
+		{
+			showInfoDialog(h,87,0);//Insulted by your refusal of their offer, the monsters attack!
+			fight(h);
+		}
+	}
+	else //accepted
+	{
+		if (cb->getResource(h->tempOwner, Res::GOLD) < cost) //player don't have enough gold!
+		{
+			InfoWindow iw;
+			iw.player = h->tempOwner;
+			iw.text << std::pair<ui8,ui32>(1,29);  //You don't have enough gold
+			cb->showInfoDialog(&iw);
+
+			//act as if player refused
+			joinDecision(h,cost,false);
+			return;
+		}
+
+		//take gold
+		if(cost)
+			cb->giveResource(h->tempOwner,Res::GOLD,-cost);
+
+		cb->tryJoiningArmy(this, h, true, true);
+	}
+}
+
+void CGCreature::fight( const CGHeroInstance *h ) const
+{
+	//split stacks
+	//TODO: multiple creature types in a stack?
+	int basicType = stacks.begin()->second->type->idNumber;
+	cb->setObjProperty(id, ObjProperty::MONSTER_RESTORE_TYPE, basicType); //store info about creature stack
+
+	double relativePower = static_cast<double>(h->getTotalStrength()) / getArmyStrength();
+	int stacksCount;
+	//TODO: number depends on tile type
+	if (relativePower < 0.5)
+	{
+		stacksCount = 7;
+	}
+	else if (relativePower < 0.67)
+	{
+		stacksCount = 7;
+	}
+	else if (relativePower < 1)
+	{
+		stacksCount = 6;
+	}
+	else if (relativePower < 1.5)
+	{
+		stacksCount = 5;
+	}
+	else if (relativePower < 2)
+	{
+		stacksCount = 4;
+	}
+	else
+	{
+		stacksCount = 3;
+	}
+	SlotID sourceSlot = stacks.begin()->first;
+	SlotID destSlot;
+	for (int stacksLeft = stacksCount; stacksLeft > 1; --stacksLeft)
+	{
+		int stackSize = stacks.begin()->second->count / stacksLeft;
+		if (stackSize)
+		{
+			if ((destSlot = getFreeSlot()).validSlot())
+				cb->moveStack(StackLocation(this, sourceSlot), StackLocation(this, destSlot), stackSize);
+			else
+			{
+                logGlobal->warnStream() <<"Warning! Not enough empty slots to split stack!";
+				break;
+			}
+		}
+		else break;
+	}
+	if (stacksCount > 1)
+	{
+		if (formation.randomFormation % 100 < 50) //upgrade
+		{
+			SlotID slotId = SlotID(stacks.size() / 2);
+			const auto & upgrades = getStack(slotId).type->upgrades;
+			if(!upgrades.empty())
+			{
+				auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator());
+				cb->changeStackType(StackLocation(this, slotId), VLC->creh->creatures[*it]);
+			}
+		}
+	}
+
+	cb->startBattleI(h, this);
+
+}
+
+void CGCreature::flee( const CGHeroInstance * h ) const
+{
+	BlockingDialog ynd(true,false);
+	ynd.player = h->tempOwner;
+	ynd.text.addTxt(MetaString::ADVOB_TXT,91);
+	ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID);
+	cb->showBlockingDialog(&ynd);
+}
+
+void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+
+	if(result.winner==0)
+	{
+		cb->removeObject(this);
+	}
+	else
+	{
+		//int killedAmount=0;
+		//for(std::set<std::pair<ui32,si32> >::iterator i=result->casualties[1].begin(); i!=result->casualties[1].end(); i++)
+		//	if(i->first == subID)
+		//		killedAmount += i->second;
+		//cb->setAmount(id, slots.find(0)->second.second - killedAmount);
+
+		/*
+		MetaString ms;
+		int pom = slots.find(0)->second.getQuantityID();
+		pom = 174 + 3*pom + 1;
+		ms << std::pair<ui8,ui32>(6,pom) << " " << std::pair<ui8,ui32>(7,subID);
+		cb->setHoverName(id,&ms);
+		cb->setObjProperty(id, 11, slots.begin()->second.count * 1000);
+		*/
+
+		//merge stacks into one
+		TSlots::const_iterator i;
+		CCreature * cre = VLC->creh->creatures[formation.basicType];
+		for (i = stacks.begin(); i != stacks.end(); i++)
+		{
+			if (cre->isMyUpgrade(i->second->type))
+			{
+				cb->changeStackType (StackLocation(this, i->first), cre); //un-upgrade creatures
+			}
+		}
+
+		//first stack has to be at slot 0 -> if original one got killed, move there first remaining stack
+		if(!hasStackAtSlot(SlotID(0)))
+			cb->moveStack(StackLocation(this, stacks.begin()->first), StackLocation(this, SlotID(0)), stacks.begin()->second->count);
+
+		while (stacks.size() > 1) //hopefully that's enough
+		{
+			// TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere)
+			i = stacks.end();
+			i--;
+			SlotID slot = getSlotFor(i->second->type);
+			if (slot == i->first) //no reason to move stack to its own slot
+				break;
+			else
+				cb->moveStack (StackLocation(this, i->first), StackLocation(this, slot), i->second->count);
+		}
+
+		cb->setObjProperty(id, ObjProperty::MONSTER_POWER, stacks.begin()->second->count * 1000); //remember casualties
+	}
+}
+
+void CGCreature::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	auto action = takenAction(hero);
+	if(!refusedJoining && action >= JOIN_FOR_FREE) //higher means price
+		joinDecision(hero, action, answer);
+	else if(action != FIGHT)
+		fleeDecision(hero, answer);
+	else
+		assert(0);
+}
+
+void CGMine::onHeroVisit( const CGHeroInstance * h ) const
+{
+	int relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);
+
+	if(relations == 2) //we're visiting our mine
+	{
+		cb->showGarrisonDialog(id,h->id,true);
+		return;
+	}
+	else if (relations == 1)//ally
+		return;
+
+	if(stacksCount()) //Mine is guarded
+	{
+		BlockingDialog ynd(true,false);
+		ynd.player = h->tempOwner;
+		ynd.text.addTxt(MetaString::ADVOB_TXT, subID == 7 ? 84 : 187);
+		cb->showBlockingDialog(&ynd);
+		return;
+	}
+
+	flagMine(h->tempOwner);
+
+}
+
+void CGMine::newTurn() const
+{
+	if(cb->getDate() == 1)
+		return;
+
+	if (tempOwner == PlayerColor::NEUTRAL)
+		return;
+
+	cb->giveResource(tempOwner, producedResource, producedQuantity);
+}
+
+void CGMine::initObj()
+{
+	if(subID >= 7) //Abandoned Mine
+	{
+		//set guardians
+		int howManyTroglodytes = cb->gameState()->getRandomGenerator().nextInt(100, 199);
+		auto troglodytes = new CStackInstance(CreatureID::TROGLODYTES, howManyTroglodytes);
+		putStack(SlotID(0), troglodytes);
+
+		//after map reading tempOwner placeholds bitmask for allowed resources
+		std::vector<Res::ERes> possibleResources;
+		for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
+			if(tempOwner.getNum() & 1<<i) //NOTE: reuse of tempOwner
+				possibleResources.push_back(static_cast<Res::ERes>(i));
+
+		assert(!possibleResources.empty());
+		producedResource = *RandomGeneratorUtil::nextItem(possibleResources, cb->gameState()->getRandomGenerator());
+		tempOwner = PlayerColor::NEUTRAL;
+		hoverName = VLC->generaltexth->mines[7].first + "\n" + VLC->generaltexth->allTexts[202] + " " + troglodytes->getQuantityTXT(false) + " " + troglodytes->type->namePl;
+	}
+	else
+	{
+		producedResource = static_cast<Res::ERes>(subID);
+
+		MetaString ms;
+		ms << std::pair<ui8,ui32>(9,producedResource);
+		if(tempOwner >= PlayerColor::PLAYER_LIMIT)
+			tempOwner = PlayerColor::NEUTRAL;
+		else
+			ms << " (" << std::pair<ui8,ui32>(6,23+tempOwner.getNum()) << ")";
+		ms.toString(hoverName);
+	}
+
+	producedQuantity = defaultResProduction();
+}
+
+void CGMine::flagMine(PlayerColor player) const
+{
+	assert(tempOwner != player);
+	cb->setOwner(this, player); //not ours? flag it!
+
+	MetaString ms;
+	ms << std::pair<ui8,ui32>(9,subID) << "\n(" << std::pair<ui8,ui32>(6,23+player.getNum()) << ")";
+	if(subID == 7)
+	{
+		ms << "(%s)";
+		ms.addReplacement(MetaString::RES_NAMES, producedResource);
+	}
+	cb->setHoverName(this,&ms);
+
+	InfoWindow iw;
+	iw.soundID = soundBase::FLAGMINE;
+	iw.text.addTxt(MetaString::MINE_EVNTS,producedResource); //not use subID, abandoned mines uses default mine texts
+	iw.player = player;
+	iw.components.push_back(Component(Component::RESOURCE,producedResource,producedQuantity,-1));
+	cb->showInfoDialog(&iw);
+}
+
+ui32 CGMine::defaultResProduction()
+{
+	switch(producedResource)
+	{
+	case Res::WOOD:
+	case Res::ORE:
+		return 2;
+	case Res::GOLD:
+		return 1000;
+	default:
+		return 1;
+	}
+}
+
+void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+	if(result.winner == 0) //attacker won
+	{
+		if(subID == 7)
+		{
+			showInfoDialog(hero->tempOwner, 85, 0);
+		}
+		flagMine(hero->tempOwner);
+	}
+}
+
+void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	if(answer)
+		cb->startBattleI(hero, this);
+}
+
+void CGResource::initObj()
+{
+	blockVisit = true;
+	hoverName = VLC->generaltexth->restypes[subID];
+
+	if(!amount)
+	{
+		switch(subID)
+		{
+		case 6:
+			amount = cb->gameState()->getRandomGenerator().nextInt(500, 1000);
+			break;
+		case 0: case 2:
+			amount = cb->gameState()->getRandomGenerator().nextInt(6, 10);
+			break;
+		default:
+			amount = cb->gameState()->getRandomGenerator().nextInt(3, 5);
+			break;
+		}
+	}
+}
+
+void CGResource::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if(stacksCount())
+	{
+		if(message.size())
+		{
+			BlockingDialog ynd(true,false);
+			ynd.player = h->getOwner();
+			ynd.text << message;
+			cb->showBlockingDialog(&ynd);
+		}
+		else
+		{
+			blockingDialogAnswered(h, true); //behave as if player accepted battle
+		}
+	}
+	else
+	{
+		if(message.length())
+		{
+			InfoWindow iw;
+			iw.player = h->tempOwner;
+			iw.text << message;
+			cb->showInfoDialog(&iw);
+		}
+		collectRes(h->getOwner());
+	}
+}
+
+void CGResource::collectRes( PlayerColor player ) const
+{
+	cb->giveResource(player, static_cast<Res::ERes>(subID), amount);
+	ShowInInfobox sii;
+	sii.player = player;
+	sii.c = Component(Component::RESOURCE,subID,amount,0);
+	sii.text.addTxt(MetaString::ADVOB_TXT,113);
+	sii.text.addReplacement(MetaString::RES_NAMES, subID);
+	cb->showCompInfo(&sii);
+	cb->removeObject(this);
+}
+
+void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+	if(result.winner == 0) //attacker won
+		collectRes(hero->getOwner());
+}
+
+void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	if(answer)
+		cb->startBattleI(hero, this);
+}
+
+void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const
+{
+	ObjectInstanceID destinationid;
+	switch(ID)
+	{
+	case Obj::MONOLITH_ONE_WAY_ENTRANCE: //one way - find corresponding exit monolith
+	{
+		if(vstd::contains(objs,Obj::MONOLITH_ONE_WAY_EXIT) && vstd::contains(objs[Obj::MONOLITH_ONE_WAY_EXIT],subID) && objs[Obj::MONOLITH_ONE_WAY_EXIT][subID].size())
+		{
+			destinationid = *RandomGeneratorUtil::nextItem(objs[Obj::MONOLITH_ONE_WAY_EXIT][subID], cb->gameState()->getRandomGenerator());
+		}
+		else
+		{
+			logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id;
+		}
+		break;
+	}
+	case Obj::MONOLITH_TWO_WAY://two way monolith - pick any other one
+	case Obj::WHIRLPOOL: //Whirlpool
+		if(vstd::contains(objs,ID) && vstd::contains(objs[ID],subID) && objs[ID][subID].size()>1)
+		{
+			//choose another exit
+			do
+			{
+				destinationid = *RandomGeneratorUtil::nextItem(objs[ID][subID], cb->gameState()->getRandomGenerator());
+			} while(destinationid == id);
+
+			if (ID == Obj::WHIRLPOOL)
+			{
+				if (!h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION))
+				{
+					if (h->Slots().size() > 1 || h->Slots().begin()->second->count > 1)
+					{ //we can't remove last unit
+						SlotID targetstack = h->Slots().begin()->first; //slot numbers may vary
+						for(auto i = h->Slots().rbegin(); i != h->Slots().rend(); i++)
+						{
+							if (h->getPower(targetstack) > h->getPower(i->first))
+							{
+								targetstack = (i->first);
+							}
+						}
+
+						TQuantity countToTake = h->getStackCount(targetstack) * 0.5;
+						vstd::amax(countToTake, 1);
+
+
+						InfoWindow iw;
+						iw.player = h->tempOwner;
+						iw.text.addTxt (MetaString::ADVOB_TXT, 168);
+						iw.components.push_back (Component(CStackBasicDescriptor(h->getCreature(targetstack), countToTake)));
+						cb->showInfoDialog(&iw);
+						cb->changeStackCount(StackLocation(h, targetstack), -countToTake);
+					}
+				}
+			}
+		}
+		else
+			logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id;
+		break;
+	case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level
+		{
+			destinationid = getMatchingGate(id);
+			if(destinationid == ObjectInstanceID()) //no exit
+			{
+				showInfoDialog(h,153,0);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged.
+			}
+			break;
+		}
+	}
+	if(destinationid == ObjectInstanceID())
+	{
+		logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( ";
+		return;
+	}
+	if (ID == Obj::WHIRLPOOL)
+	{
+		std::set<int3> tiles = cb->getObj(destinationid)->getBlockedPos();
+		auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator());
+		cb->moveHero(h->id, tile + int3(1,0,0), true);
+	}
+	else
+		cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true);
+}
+
+void CGTeleport::initObj()
+{
+	int si = subID;
+	switch (ID)
+	{
+	case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid
+	case Obj::WHIRLPOOL:
+		{
+			si = 0;
+			break;
+		}
+	default:
+		break;
+	}
+	objs[ID][si].push_back(id);
+}
+
+void CGTeleport::postInit() //matches subterranean gates into pairs
+{
+	//split on underground and surface gates
+	std::vector<const CGObjectInstance *> gatesSplit[2]; //surface and underground gates
+	for(auto & elem : objs[Obj::SUBTERRANEAN_GATE][0])
+	{
+		const CGObjectInstance *hlp = cb->getObj(elem);
+		gatesSplit[hlp->pos.z].push_back(hlp);
+	}
+
+	//sort by position
+	std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](const CGObjectInstance * a, const CGObjectInstance * b)
+	{
+		return a->pos < b->pos;
+	});
+
+	for(size_t i = 0; i < gatesSplit[0].size(); i++)
+	{
+		const CGObjectInstance *cur = gatesSplit[0][i];
+
+		//find nearest underground exit
+		std::pair<int, si32> best(-1, std::numeric_limits<si32>::max()); //pair<pos_in_vector, distance^2>
+		for(int j = 0; j < gatesSplit[1].size(); j++)
+		{
+			const CGObjectInstance *checked = gatesSplit[1][j];
+			if(!checked)
+				continue;
+			si32 hlp = checked->pos.dist2dSQ(cur->pos);
+			if(hlp < best.second)
+			{
+				best.first = j;
+				best.second = hlp;
+			}
+		}
+
+		if(best.first >= 0) //found pair
+		{
+			gates.push_back(std::make_pair(cur->id, gatesSplit[1][best.first]->id));
+			gatesSplit[1][best.first] = nullptr;
+		}
+		else
+			gates.push_back(std::make_pair(cur->id, ObjectInstanceID()));
+	}
+	objs.erase(Obj::SUBTERRANEAN_GATE);
+}
+
+ObjectInstanceID CGTeleport::getMatchingGate(ObjectInstanceID id)
+{
+	for(auto & gate : gates)
+	{
+		if(gate.first == id)
+			return gate.second;
+		if(gate.second == id)
+			return gate.first;
+	}
+
+	return ObjectInstanceID();
+}
+
+void CGArtifact::initObj()
+{
+	blockVisit = true;
+	if(ID == Obj::ARTIFACT)
+	{
+		hoverName = VLC->arth->artifacts[subID]->Name();
+		if(!storedArtifact->artType)
+			storedArtifact->setType(VLC->arth->artifacts[subID]);
+	}
+	if(ID == Obj::SPELL_SCROLL)
+		subID = 1;
+
+	assert(storedArtifact->artType);
+	assert(storedArtifact->getParentNodes().size());
+
+	//assert(storedArtifact->artType->id == subID); //this does not stop desync
+}
+
+void CGArtifact::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if(!stacksCount())
+	{
+		InfoWindow iw;
+		iw.player = h->tempOwner;
+		switch(ID)
+		{
+		case Obj::ARTIFACT:
+			{
+				iw.soundID = soundBase::treasure; //play sound only for non-scroll arts
+				iw.components.push_back(Component(Component::ARTIFACT,subID,0,0));
+				if(message.length())
+					iw.text <<  message;
+				else
+				{
+					if (VLC->arth->artifacts[subID]->EventText().size())
+						iw.text << std::pair<ui8, ui32> (MetaString::ART_EVNTS, subID);
+					else //fix for mod artifacts with no event text
+					{
+						iw.text.addTxt (MetaString::ADVOB_TXT, 183); //% has found treasure
+						iw.text.addReplacement (h->name);
+					}
+
+				}
+			}
+			break;
+		case Obj::SPELL_SCROLL:
+			{
+				int spellID = storedArtifact->getGivenSpellID();
+				iw.components.push_back (Component(Component::SPELL, spellID,0,0));
+				iw.text.addTxt (MetaString::ADVOB_TXT,135);
+				iw.text.addReplacement(MetaString::SPELL_NAME, spellID);
+			}
+			break;
+		}
+		cb->showInfoDialog(&iw);
+		pick(h);
+	}
+	else
+	{
+		if(message.size())
+		{
+			BlockingDialog ynd(true,false);
+			ynd.player = h->getOwner();
+			ynd.text << message;
+			cb->showBlockingDialog(&ynd);
+		}
+		else
+		{
+			blockingDialogAnswered(h, true);
+		}
+	}
+}
+
+void CGArtifact::pick(const CGHeroInstance * h) const
+{
+	cb->giveHeroArtifact(h, storedArtifact, ArtifactPosition::FIRST_AVAILABLE);
+	cb->removeObject(this);
+}
+
+void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+	if(result.winner == 0) //attacker won
+		pick(hero);
+}
+
+void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	if(answer)
+		cb->startBattleI(hero, this);
+}
+
+void CGWitchHut::initObj()
+{
+	ability = *RandomGeneratorUtil::nextItem(allowedAbilities, cb->gameState()->getRandomGenerator());
+}
+
+void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const
+{
+	InfoWindow iw;
+	iw.soundID = soundBase::gazebo;
+	iw.player = h->getOwner();
+	if(!wasVisited(h->tempOwner))
+		cb->setObjProperty(id, 10, h->tempOwner.getNum());
+	ui32 txt_id;
+	if(h->getSecSkillLevel(SecondarySkill(ability))) //you already know this skill
+	{
+		txt_id =172;
+	}
+	else if(!h->canLearnSkill()) //already all skills slots used
+	{
+		txt_id = 173;
+	}
+	else //give sec skill
+	{
+		iw.components.push_back(Component(Component::SEC_SKILL, ability, 1, 0));
+		txt_id = 171;
+		cb->changeSecSkill(h, SecondarySkill(ability), 1, true);
+	}
+
+	iw.text.addTxt(MetaString::ADVOB_TXT,txt_id);
+	iw.text.addReplacement(MetaString::SEC_SKILL_NAME, ability);
+	cb->showInfoDialog(&iw);
+}
+
+const std::string & CGWitchHut::getHoverText() const
+{
+	hoverName = VLC->objtypeh->getObjectName(ID);
+	if(wasVisited(cb->getLocalPlayer()))
+	{
+		hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s)
+		boost::algorithm::replace_first(hoverName,"%s",VLC->generaltexth->skillName[ability]);
+		const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
+		if(h && h->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability
+			hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned)
+	}
+	return hoverName;
+}
+
+void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const
+{
+	int message;
+
+	if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Well today
+	{
+		message = 78;//"A second drink at the well in one day will not help you."
+	}
+	else if(h->mana < h->manaLimit())
+	{
+		giveDummyBonus(h->id);
+		cb->setManaPoints(h->id,h->manaLimit());
+		message = 77;
+	}
+	else
+	{
+		message = 79;
+	}
+	showInfoDialog(h,message,soundBase::faerie);
+}
+
+const std::string & CGMagicWell::getHoverText() const
+{
+	getNameVis(hoverName);
+	return hoverName;
+}
+
+void CGObservatory::onHeroVisit( const CGHeroInstance * h ) const
+{
+	InfoWindow iw;
+	iw.player = h->tempOwner;
+	switch (ID)
+	{
+	case Obj::REDWOOD_OBSERVATORY:
+	case Obj::PILLAR_OF_FIRE:
+	{
+		iw.soundID = soundBase::LIGHTHOUSE;
+		iw.text.addTxt(MetaString::ADVOB_TXT,98 + (ID==Obj::PILLAR_OF_FIRE));
+
+		FoWChange fw;
+		fw.player = h->tempOwner;
+		fw.mode = 1;
+		cb->getTilesInRange (fw.tiles, pos, 20, h->tempOwner, 1);
+		cb->sendAndApply (&fw);
+		break;
+	}
+	case Obj::COVER_OF_DARKNESS:
+	{
+		iw.text.addTxt (MetaString::ADVOB_TXT, 31);
+		hideTiles(h->tempOwner, 20);
+		break;
+	}
+	}
+	cb->showInfoDialog(&iw);
+}
+
+void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if(spell == SpellID::NONE)
+	{
+        logGlobal->errorStream() << "Not initialized shrine visited!";
+		return;
+	}
+
+	if(!wasVisited(h->tempOwner))
+		cb->setObjProperty(id, 10, h->tempOwner.getNum());
+
+	InfoWindow iw;
+	iw.soundID = soundBase::temple;
+	iw.player = h->getOwner();
+	iw.text.addTxt(MetaString::ADVOB_TXT,127 + ID - 88);
+	iw.text.addTxt(MetaString::SPELL_NAME,spell);
+	iw.text << ".";
+
+	if(!h->getArt(ArtifactPosition::SPELLBOOK))
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT,131);
+	}
+	else if(ID == Obj::SHRINE_OF_MAGIC_THOUGHT  && !h->getSecSkillLevel(SecondarySkill::WISDOM)) //it's third level spell and hero doesn't have wisdom
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT,130);
+	}
+	else if(vstd::contains(h->spells,spell))//hero already knows the spell
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT,174);
+	}
+	else //give spell
+	{
+		std::set<SpellID> spells;
+		spells.insert(spell);
+		cb->changeSpells(h, true, spells);
+
+		iw.components.push_back(Component(Component::SPELL,spell,0,0));
+	}
+
+	cb->showInfoDialog(&iw);
+}
+
+void CGShrine::initObj()
+{
+	if(spell == SpellID::NONE) //spell not set
+	{
+		int level = ID-87;
+		std::vector<SpellID> possibilities;
+		cb->getAllowedSpells (possibilities, level);
+
+		if(possibilities.empty())
+		{
+            logGlobal->errorStream() << "Error: cannot init shrine, no allowed spells!";
+			return;
+		}
+
+		spell = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator());
+	}
+}
+
+const std::string & CGShrine::getHoverText() const
+{
+	hoverName = VLC->objtypeh->getObjectName(ID);
+	if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current
+	{
+		hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s)
+		boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->name);
+		const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
+		if(h && vstd::contains(h->spells,spell)) //hero knows that ability
+			hoverName += "\n\n" + VLC->generaltexth->allTexts[354]; // (Already learned)
+	}
+	return hoverName;
+}
+
+void CGSignBottle::initObj()
+{
+	//if no text is set than we pick random from the predefined ones
+	if(message.empty())
+	{
+		message = *RandomGeneratorUtil::nextItem(VLC->generaltexth->randsign, cb->gameState()->getRandomGenerator());
+	}
+
+	if(ID == Obj::OCEAN_BOTTLE)
+	{
+		blockVisit = true;
+	}
+}
+
+void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const
+{
+	InfoWindow iw;
+	iw.soundID = soundBase::STORE;
+	iw.player = h->getOwner();
+	iw.text << message;
+	cb->showInfoDialog(&iw);
+
+	if(ID == Obj::OCEAN_BOTTLE)
+		cb->removeObject(this);
+}
+
+//TODO: remove
+//void CGScholar::giveAnyBonus( const CGHeroInstance * h ) const
+//{
+//
+//}
+
+void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
+{
+
+	EBonusType type = bonusType;
+	int bid = bonusID;
+	//check if the bonus if applicable, if not - give primary skill (always possible)
+	int ssl = h->getSecSkillLevel(SecondarySkill(bid)); //current sec skill level, used if bonusType == 1
+	if((type == SECONDARY_SKILL
+			&& ((ssl == 3)  ||  (!ssl  &&  !h->canLearnSkill()))) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot)
+		|| (type == SPELL  &&  (!h->getArt(ArtifactPosition::SPELLBOOK) || vstd::contains(h->spells, (ui32) bid)
+		|| ( SpellID(bid).toSpell()->level > h->getSecSkillLevel(SecondarySkill::WISDOM) + 2)
+		))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom
+	{
+		type = PRIM_SKILL;
+		bid = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS - 1);
+	}
+
+	InfoWindow iw;
+	iw.soundID = soundBase::gazebo;
+	iw.player = h->getOwner();
+	iw.text.addTxt(MetaString::ADVOB_TXT,115);
+
+	switch (type)
+	{
+	case PRIM_SKILL:
+		cb->changePrimSkill(h,static_cast<PrimarySkill::PrimarySkill>(bid),+1);
+		iw.components.push_back(Component(Component::PRIM_SKILL,bid,+1,0));
+		break;
+	case SECONDARY_SKILL:
+		cb->changeSecSkill(h,SecondarySkill(bid),+1);
+		iw.components.push_back(Component(Component::SEC_SKILL,bid,ssl+1,0));
+		break;
+	case SPELL:
+		{
+			std::set<SpellID> hlp;
+			hlp.insert(SpellID(bid));
+			cb->changeSpells(h,true,hlp);
+			iw.components.push_back(Component(Component::SPELL,bid,0,0));
+		}
+		break;
+	default:
+		logGlobal->errorStream() << "Error: wrong bonus type (" << (int)type << ") for Scholar!\n";
+		return;
+	}
+
+	cb->showInfoDialog(&iw);
+	cb->removeObject(this);
+}
+
+void CGScholar::initObj()
+{
+	blockVisit = true;
+	if(bonusType == RANDOM)
+	{
+		bonusType = static_cast<EBonusType>(cb->gameState()->getRandomGenerator().nextInt(2));
+		switch(bonusType)
+		{
+		case PRIM_SKILL:
+			bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS -1);
+			break;
+		case SECONDARY_SKILL:
+			bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::SKILL_QUANTITY -1);
+			break;
+		case SPELL:
+			std::vector<SpellID> possibilities;
+			for (int i = 1; i < 6; ++i)
+				cb->getAllowedSpells (possibilities, i);
+			bonusID = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator());
+			break;
+		}
+	}
+}
+
+void CGGarrison::onHeroVisit (const CGHeroInstance *h) const
+{
+	int ally = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);
+	if (!ally && stacksCount() > 0) {
+		//TODO: Find a way to apply magic garrison effects in battle.
+		cb->startBattleI(h, this);
+		return;
+	}
+
+	//New owner.
+	if (!ally)
+		cb->setOwner(this, h->tempOwner);
+
+	cb->showGarrisonDialog(id, h->id, removableUnits);
+}
+
+ui8 CGGarrison::getPassableness() const
+{
+	if ( !stacksCount() )//empty - anyone can visit
+		return GameConstants::ALL_PLAYERS;
+	if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit
+		return 0;
+
+	ui8 mask = 0;
+	TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner);
+	for(PlayerColor it : ts->players)
+		mask |= 1<<it.getNum(); //allies - add to possible visitors
+
+	return mask;
+}
+
+void CGGarrison::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
+{
+	if (result.winner == 0)
+		onHeroVisit(hero);
+}
+
+void CGMagi::initObj()
+{
+	if (ID == Obj::EYE_OF_MAGI)
+	{
+		blockVisit = true;
+		eyelist[subID].push_back(id);
+	}
+}
+void CGMagi::onHeroVisit(const CGHeroInstance * h) const
+{
+	if (ID == Obj::HUT_OF_MAGI)
+	{
+		showInfoDialog(h, 61, soundBase::LIGHTHOUSE);
+
+		if (!eyelist[subID].empty())
+		{
+			CenterView cv;
+			cv.player = h->tempOwner;
+			cv.focusTime = 2000;
+
+			FoWChange fw;
+			fw.player = h->tempOwner;
+			fw.mode = 1;
+
+			for(auto it : eyelist[subID])
+			{
+				const CGObjectInstance *eye = cb->getObj(it);
+
+				cb->getTilesInRange (fw.tiles, eye->pos, 10, h->tempOwner, 1);
+				cb->sendAndApply(&fw);
+				cv.pos = eye->pos;
+
+				cb->sendAndApply(&cv);
+			}
+			cv.pos = h->getPosition(false);
+			cb->sendAndApply(&cv);
+		}
+	}
+	else if (ID == Obj::EYE_OF_MAGI)
+	{
+		showInfoDialog(h,48,soundBase::invalid);
+	}
+
+}
+void CGBoat::initObj()
+{
+	hero = nullptr;
+}
+
+void CGSirens::initObj()
+{
+	blockVisit = true;
+}
+
+const std::string & CGSirens::getHoverText() const
+{
+	getNameVis(hoverName);
+	return hoverName;
+}
+
+void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
+{
+	InfoWindow iw;
+	iw.soundID = soundBase::DANGER;
+	iw.player = h->tempOwner;
+	if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Sirens
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT,133);
+	}
+	else
+	{
+		giveDummyBonus(h->id, Bonus::ONE_BATTLE);
+		TExpType xp = 0;
+
+		for (auto i = h->Slots().begin(); i != h->Slots().end(); i++)
+		{
+			TQuantity drown = i->second->count * 0.3;
+			if(drown)
+			{
+				cb->changeStackCount(StackLocation(h, i->first), -drown);
+				xp += drown * i->second->type->valOfBonuses(Bonus::STACK_HEALTH);
+			}
+		}
+
+		if(xp)
+		{
+			xp = h->calculateXp(xp);
+			iw.text.addTxt(MetaString::ADVOB_TXT,132);
+			iw.text.addReplacement(xp);
+			cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, xp, false);
+		}
+		else
+		{
+			iw.text.addTxt(MetaString::ADVOB_TXT,134);
+		}
+	}
+	cb->showInfoDialog(&iw);
+
+}
+
+CGShipyard::CGShipyard()
+	:IShipyard(this)
+{
+}
+
+void CGShipyard::getOutOffsets( std::vector<int3> &offsets ) const
+{
+	// H J L K I
+	// A x S x B
+	// C E G F D
+	offsets += int3(-3,0,0), int3(1,0,0), //AB
+		int3(-3,1,0), int3(1,1,0), int3(-2,1,0), int3(0,1,0), int3(-1,1,0), //CDEFG
+		int3(-3,-1,0), int3(1,-1,0), int3(-2,-1,0), int3(0,-1,0), int3(-1,-1,0); //HIJKL
+}
+
+void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if(!cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner))
+		cb->setOwner(this, h->tempOwner);
+
+	auto s = shipyardStatus();
+	if(s != IBoatGenerator::GOOD)
+	{
+		InfoWindow iw;
+		iw.player = tempOwner;
+		getProblemText(iw.text, h);
+		cb->showInfoDialog(&iw);
+	}
+	else
+	{
+		openWindow(OpenWindow::SHIPYARD_WINDOW,id.getNum(),h->id.getNum());
+	}
+}
+
+void CCartographer::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if (!wasVisited (h->getOwner()) ) //if hero has not visited yet this cartographer
+	{
+		if (cb->getResource(h->tempOwner, Res::GOLD) >= 1000) //if he can afford a map
+		{
+			//ask if he wants to buy one
+			int text=0;
+			switch (subID)
+			{
+				case 0:
+					text = 25;
+					break;
+				case 1:
+					text = 26;
+					break;
+				case 2:
+					text = 27;
+					break;
+				default:
+                    logGlobal->warnStream() << "Unrecognized subtype of cartographer";
+			}
+			assert(text);
+			BlockingDialog bd (true, false);
+			bd.player = h->getOwner();
+			bd.soundID = soundBase::LIGHTHOUSE;
+			bd.text.addTxt (MetaString::ADVOB_TXT, text);
+			cb->showBlockingDialog (&bd);
+		}
+		else //if he cannot afford
+		{
+			showInfoDialog(h,28,soundBase::CAVEHEAD);
+		}
+	}
+	else //if he already visited carographer
+	{
+		showInfoDialog(h,24,soundBase::CAVEHEAD);
+	}
+}
+
+void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
+{
+	if (answer) //if hero wants to buy map
+	{
+		cb->giveResource (hero->tempOwner, Res::GOLD, -1000);
+		FoWChange fw;
+		fw.mode = 1;
+		fw.player = hero->tempOwner;
+
+		//subIDs of different types of cartographers:
+		//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());
+	}
+}
+
+void CGDenOfthieves::onHeroVisit (const CGHeroInstance * h) const
+{
+	cb->showThievesGuildWindow(h->tempOwner, id);
+}
+
+void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const
+{
+	InfoWindow iw;
+	iw.player = h->tempOwner;
+	TeamState *ts = cb->gameState()->getPlayerTeam(h->tempOwner);
+	assert(ts);
+	TeamID team = ts->id;
+
+	if(!wasVisited(team))
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT, 96);
+		cb->sendAndApply(&iw);
+
+		cb->setObjProperty(id, 20, h->tempOwner.getNum()); //increment general visited obelisks counter
+
+		openWindow(OpenWindow::PUZZLE_MAP, h->tempOwner.getNum());
+
+		cb->setObjProperty(id, 10, h->tempOwner.getNum()); //mark that particular obelisk as visited
+	}
+	else
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT, 97);
+		cb->sendAndApply(&iw);
+	}
+
+}
+
+void CGObelisk::initObj()
+{
+	obeliskCount++;
+}
+
+const std::string & CGObelisk::getHoverText() const
+{
+	bool visited = wasVisited(cb->getLocalPlayer());
+	hoverName = VLC->objtypeh->getObjectName(ID) + " " + visitedTxt(visited);
+	return hoverName;
+}
+
+void CGObelisk::setPropertyDer( ui8 what, ui32 val )
+{
+	CPlayersVisited::setPropertyDer(what, val);
+	switch(what)
+	{
+	case 20:
+		assert(val < PlayerColor::PLAYER_LIMIT_I);
+		visited[TeamID(val)]++;
+
+		if(visited[TeamID(val)] > obeliskCount)
+		{
+            logGlobal->errorStream() << "Error: Visited " << visited[TeamID(val)] << "\t\t" << obeliskCount;
+			assert(0);
+		}
+
+		break;
+	}
+}
+
+void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if(h->tempOwner != tempOwner)
+	{
+		PlayerColor oldOwner = tempOwner;
+		cb->setOwner(this,h->tempOwner); //not ours? flag it!
+		showInfoDialog(h,69,soundBase::LIGHTHOUSE);
+		giveBonusTo(h->tempOwner);
+
+		if(oldOwner < PlayerColor::PLAYER_LIMIT) //remove bonus from old owner
+		{
+			RemoveBonus rb(RemoveBonus::PLAYER);
+			rb.whoID = oldOwner.getNum();
+			rb.source = Bonus::OBJECT;
+			rb.id = id.getNum();
+			cb->sendAndApply(&rb);
+		}
+	}
+}
+
+void CGLighthouse::initObj()
+{
+	if(tempOwner < PlayerColor::PLAYER_LIMIT)
+	{
+		giveBonusTo(tempOwner);
+	}
+}
+
+const std::string & CGLighthouse::getHoverText() const
+{
+	hoverName = VLC->objtypeh->getObjectName(ID);
+	//TODO: owned by %s player
+	return hoverName;
+}
+
+void CGLighthouse::giveBonusTo( PlayerColor player ) const
+{
+	GiveBonus gb(GiveBonus::PLAYER);
+	gb.bonus.type = Bonus::SEA_MOVEMENT;
+	gb.bonus.val = 500;
+	gb.id = player.getNum();
+	gb.bonus.duration = Bonus::PERMANENT;
+	gb.bonus.source = Bonus::OBJECT;
+	gb.bonus.sid = id.getNum();
+	cb->sendAndApply(&gb);
+}

+ 377 - 0
lib/mapObjects/MiscObjects.h

@@ -0,0 +1,377 @@
+#pragma once
+
+#include "CObjectHandler.h"
+#include "CGArmedInstance.h"
+
+/*
+ * CObjectHandler.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
+ *
+ */
+
+class DLL_LINKAGE CPlayersVisited: public CGObjectInstance
+{
+public:
+	std::set<PlayerColor> players; //players that visited this object
+
+	bool wasVisited(PlayerColor player) const;
+	bool wasVisited(TeamID team) const;
+	void setPropertyDer(ui8 what, ui32 val) override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+		h & players;
+	}
+};
+
+class DLL_LINKAGE CGCreature : public CArmedInstance //creatures on map
+{
+	enum Action {
+		FIGHT = -2, FLEE = -1, JOIN_FOR_FREE = 0 //values > 0 mean gold price
+	};
+
+public:
+	ui32 identifier; //unique code for this monster (used in missions)
+	si8 character; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage)
+	std::string message; //message printed for attacking hero
+	TResources resources; // resources given to hero that has won with monsters
+	ArtifactID gainedArtifact; //ID of artifact gained to hero, -1 if none
+	bool neverFlees; //if true, the troops will never flee
+	bool notGrowingTeam; //if true, number of units won't grow
+	ui64 temppower; //used to handle fractional stack growth for tiny stacks
+
+	bool refusedJoining;
+
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	const std::string & getHoverText() const override;
+	void initObj() override;
+	void newTurn() const override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+
+
+	struct DLL_LINKAGE formationInfo // info about merging stacks after battle back into one
+	{
+		si32 basicType;
+		ui32 randomFormation; //random seed used to determine number of stacks and is there's upgraded stack
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & basicType & randomFormation;
+		}
+	} formation;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this);
+		h & identifier & character & message & resources & gainedArtifact & neverFlees & notGrowingTeam & temppower;
+		h & refusedJoining & formation;
+	}
+protected:
+	void setPropertyDer(ui8 what, ui32 val) override;
+private:
+
+	void fight(const CGHeroInstance *h) const;
+	void flee( const CGHeroInstance * h ) const;
+	void fleeDecision(const CGHeroInstance *h, ui32 pursue) const;
+	void joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const;
+
+	int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0)
+
+};
+
+
+class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles
+{
+public:
+	std::string message;
+
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void initObj() override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+		h & message;
+	}
+};
+
+class DLL_LINKAGE CGWitchHut : public CPlayersVisited
+{
+public:
+	std::vector<si32> allowedAbilities;
+	ui32 ability;
+
+	const std::string & getHoverText() const override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void initObj() override;
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CPlayersVisited&>(*this);
+		h & allowedAbilities & ability;
+	}
+};
+
+class DLL_LINKAGE CGScholar : public CGObjectInstance
+{
+public:
+	enum EBonusType {PRIM_SKILL, SECONDARY_SKILL, SPELL, RANDOM = 255};
+	EBonusType bonusType;
+	ui16 bonusID; //ID of skill/spell
+
+//	void giveAnyBonus(const CGHeroInstance * h) const; //TODO: remove
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void initObj() override;
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+		h & bonusType & bonusID;
+	}
+};
+
+class DLL_LINKAGE CGGarrison : public CArmedInstance
+{
+public:
+	bool removableUnits;
+
+	ui8 getPassableness() const;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this);
+		h & removableUnits;
+	}
+};
+
+class DLL_LINKAGE CGArtifact : public CArmedInstance
+{
+public:
+	CArtifactInstance *storedArtifact;
+	std::string message;
+
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+
+	void pick( const CGHeroInstance * h ) const;
+	void initObj() override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this);
+		h & message & storedArtifact;
+	}
+};
+
+class DLL_LINKAGE CGResource : public CArmedInstance
+{
+public:
+	ui32 amount; //0 if random
+	std::string message;
+
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void initObj() override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+
+	void collectRes(PlayerColor player) const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this);
+		h & amount & message;
+	}
+};
+
+class DLL_LINKAGE CGShrine : public CPlayersVisited
+{
+public:
+	SpellID spell; //id of spell or NONE if random
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void initObj() override;
+	const std::string & getHoverText() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CPlayersVisited&>(*this);;
+		h & spell;
+	}
+};
+
+class DLL_LINKAGE CGMine : public CArmedInstance
+{
+public:
+	Res::ERes producedResource;
+	ui32 producedQuantity;
+	
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+
+	void flagMine(PlayerColor player) const;
+	void newTurn() const override;
+	void initObj() override;
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CArmedInstance&>(*this);
+		h & producedResource & producedQuantity;
+	}
+	ui32 defaultResProduction();
+};
+
+class DLL_LINKAGE CGTeleport : public CGObjectInstance //teleports and subterranean gates
+{
+public:
+	static std::map<Obj, std::map<int, std::vector<ObjectInstanceID> > > objs; //teleports: map[ID][subID] => vector of ids
+	static std::vector<std::pair<ObjectInstanceID, ObjectInstanceID> > gates; //subterranean gates: pairs of ids
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void initObj() override;
+	static void postInit();
+	static ObjectInstanceID getMatchingGate(ObjectInstanceID id); //receives id of one subterranean gate and returns id of the paired one, -1 if none
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGMagicWell : public CGObjectInstance //objects giving bonuses to luck/morale/movement
+{
+public:
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	const std::string & getHoverText() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGSirens : public CGObjectInstance
+{
+public:
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	const std::string & getHoverText() const override;
+	void initObj() override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGObservatory : public CGObjectInstance //Redwood observatory
+{
+public:
+	void onHeroVisit(const CGHeroInstance * h) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGBoat : public CGObjectInstance
+{
+public:
+	ui8 direction;
+	const CGHeroInstance *hero;  //hero on board
+
+	void initObj() override;
+
+	CGBoat()
+	{
+		hero = nullptr;
+		direction = 4;
+	}
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this) & direction & hero;
+	}
+};
+
+class CGShipyard : public CGObjectInstance, public IShipyard
+{
+public:
+	void getOutOffsets(std::vector<int3> &offsets) const; //offsets to obj pos when we boat can be placed
+	CGShipyard();
+	void onHeroVisit(const CGHeroInstance * h) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+		h & static_cast<IShipyard&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGMagi : public CGObjectInstance
+{
+public:
+	static std::map <si32, std::vector<ObjectInstanceID> > eyelist; //[subID][id], supports multiple sets as in H5
+
+	void initObj() override;
+	void onHeroVisit(const CGHeroInstance * h) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+	}
+};
+
+
+
+class DLL_LINKAGE CCartographer : public CPlayersVisited
+{
+///behaviour varies depending on surface and  floor
+public:
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CPlayersVisited&>(*this);
+	}
+};
+
+class DLL_LINKAGE CGDenOfthieves : public CGObjectInstance
+{
+	void onHeroVisit(const CGHeroInstance * h) const override;
+};
+
+class DLL_LINKAGE CGObelisk : public CPlayersVisited
+{
+public:
+	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;
+	const std::string & getHoverText() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CPlayersVisited&>(*this);
+	}
+protected:
+	void setPropertyDer(ui8 what, ui32 val) override;
+};
+
+class DLL_LINKAGE CGLighthouse : public CGObjectInstance
+{
+public:
+	void onHeroVisit(const CGHeroInstance * h) const override;
+	void initObj() override;
+	const std::string & getHoverText() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<CGObjectInstance&>(*this);
+	}
+	void giveBonusTo( PlayerColor player ) const;
+};

+ 1 - 1
lib/mapping/CCampaignHandler.cpp

@@ -10,7 +10,7 @@
 #include "../CGeneralTextHandler.h"
 #include "../StartInfo.h"
 #include "../CArtHandler.h" //for hero crossover
-#include "../mapObjects/CObjectHandler.h" //for hero crossover
+#include "../mapObjects/CGHeroInstance.h"//for hero crossover
 #include "../CHeroHandler.h"
 #include "CMapService.h"
 #include "CMap.h"

+ 1 - 0
lib/mapping/CMap.cpp

@@ -7,6 +7,7 @@
 #include "../CTownHandler.h"
 #include "../CHeroHandler.h"
 #include "../mapObjects/CObjectClassesHandler.h"
+#include "../mapObjects/CGHeroInstance.h"
 #include "../CGeneralTextHandler.h"
 #include "../CSpellHandler.h"
 #include "CMapEditManager.h"

+ 3 - 1
lib/mapping/CMap.h

@@ -12,7 +12,9 @@
 #pragma once
 
 #include "../ConstTransitivePtr.h"
-#include "../mapObjects/CObjectHandler.h"
+#include "../mapObjects/MiscObjects.h" // To serialize static props
+#include "../mapObjects/CQuest.h" // To serialize static props
+#include "../mapObjects/CGTownInstance.h" // To serialize static props
 #include "../ResourceSet.h"
 #include "../int3.h"
 #include "../GameConstants.h"

+ 1 - 0
lib/mapping/CMapEditManager.cpp

@@ -4,6 +4,7 @@
 #include "../JsonNode.h"
 #include "../filesystem/Filesystem.h"
 #include "../mapObjects/CObjectClassesHandler.h"
+#include "../mapObjects/CGHeroInstance.h"
 #include "../VCMI_Lib.h"
 
 MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0)

+ 1 - 2
lib/mapping/MapFormatH3M.cpp

@@ -20,9 +20,8 @@
 #include "../CCreatureHandler.h"
 #include "../CGeneralTextHandler.h"
 #include "../CHeroHandler.h"
-#include "../mapObjects/CObjectHandler.h"
-#include "../mapObjects/CRewardableObject.h"
 #include "../mapObjects/CObjectClassesHandler.h"
+#include "../mapObjects/MapObjects.h"
 #include "../VCMI_Lib.h"
 #include "../NetPacksBase.h"
 

+ 2 - 1
lib/mapping/MapFormatH3M.h

@@ -14,7 +14,8 @@
 #include "CMapService.h"
 #include "../GameConstants.h"
 #include "../ResourceSet.h"
-#include "../mapObjects/CObjectClassesHandler.h"
+//#include "../mapObjects/CObjectClassesHandler.h"
+#include "../mapObjects/ObjectTemplate.h"
 
 #include "../int3.h"
 

+ 1 - 2
lib/registerTypes/RegisterTypes.h

@@ -4,14 +4,13 @@
 #include "../NetPacks.h"
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
-#include "../mapObjects/CObjectHandler.h"
-#include "../mapObjects/CRewardableObject.h"
 #include "../CGameState.h"
 #include "../CHeroHandler.h"
 #include "../CTownHandler.h"
 #include "../CModHandler.h" //needed?
 #include "../mapObjects/CObjectClassesHandler.h"
 #include "../mapObjects/CRewardableConstructor.h"
+#include "../mapObjects/MapObjects.h"
 
 /*
  * RegisterTypes.h, part of VCMI engine

+ 2 - 2
lib/rmg/CMapGenerator.cpp

@@ -5,8 +5,8 @@
 #include "../VCMI_Lib.h"
 #include "../CGeneralTextHandler.h"
 #include "../mapping/CMapEditManager.h"
-#include "../mapObjects/CObjectHandler.h"
-#include "../mapObjects/CObjectClassesHandler.h"
+//#include "../mapObjects/CObjectHandler.h"
+//#include "../mapObjects/CObjectClassesHandler.h"
 #include "../CTownHandler.h"
 #include "../StringConstants.h"
 #include "../filesystem/Filesystem.h"

+ 0 - 1
lib/rmg/CMapGenerator.h

@@ -15,7 +15,6 @@
 #include "../CRandomGenerator.h"
 #include "CMapGenOptions.h"
 #include "CRmgTemplateZone.h"
-#include "../mapObjects/CObjectHandler.h"
 #include "../int3.h"
 
 class CMap;

+ 2 - 0
lib/rmg/CRmgTemplateZone.cpp

@@ -18,6 +18,8 @@
 #include "../CTownHandler.h"
 #include "../CCreatureHandler.h"
 
+#include "../mapObjects/CObjectClassesHandler.h"
+
 class CMap;
 class CMapEditManager;
 

+ 2 - 2
server/CGameHandler.cpp

@@ -8,9 +8,9 @@
 #include "../lib/CModHandler.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/CBuildingHandler.h"
-#include "../lib/mapObjects/CObjectClassesHandler.h"
+//#include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/CSpellHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CTownHandler.h"

+ 2 - 2
server/CVCMIServer.cpp

@@ -8,7 +8,7 @@
 #include "../lib/Connection.h"
 #include "../lib/CModHandler.h"
 #include "../lib/CArtHandler.h"
-#include "../lib/mapObjects/CObjectClassesHandler.h"
+//#include "../lib/mapObjects/CObjectClassesHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CTownHandler.h"
@@ -26,7 +26,7 @@
 #include "../lib/VCMIDirs.h"
 #include "CGameHandler.h"
 #include "../lib/mapping/CMapInfo.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/GameConstants.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
 #include "../lib/CConfigHandler.h"

+ 1 - 1
server/NetPacksServer.cpp

@@ -2,7 +2,7 @@
 #include "../lib/NetPacks.h"
 
 #include "CGameHandler.h"
-#include "../lib/mapObjects/CObjectHandler.h"
+//#include "../lib/mapObjects/CObjectHandler.h"
 #include "../lib/IGameCallback.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/CGameState.h"