Bläddra i källkod

Random map generator refactoring (#762)

random map generator refactoring and improvements
Nordsoft91 3 år sedan
förälder
incheckning
4bd0ff680a
60 ändrade filer med 6713 tillägg och 4544 borttagningar
  1. 1 1
      client/CMusicHandler.h
  2. 1 1
      client/widgets/AdventureMapClasses.h
  3. 0 10
      config/randomMap.json
  4. 12 2
      config/terrains.json
  5. 1 1
      lib/CCreatureHandler.h
  6. 1 1
      lib/CHeroHandler.h
  7. 38 2
      lib/CMakeLists.txt
  8. 33 6
      lib/Terrain.cpp
  9. 4 1
      lib/Terrain.h
  10. 87 46
      lib/mapping/CDrawRoadsOperation.cpp
  11. 49 14
      lib/mapping/CDrawRoadsOperation.h
  12. 9 2
      lib/mapping/CMapEditManager.cpp
  13. 4 1
      lib/mapping/CMapEditManager.h
  14. 3 0
      lib/rmg/CMapGenOptions.cpp
  15. 68 719
      lib/rmg/CMapGenerator.cpp
  16. 8 73
      lib/rmg/CMapGenerator.h
  17. 30 0
      lib/rmg/CRmgTemplate.cpp
  18. 10 3
      lib/rmg/CRmgTemplate.h
  19. 0 3387
      lib/rmg/CRmgTemplateZone.cpp
  20. 0 237
      lib/rmg/CRmgTemplateZone.h
  21. 37 27
      lib/rmg/CZonePlacer.cpp
  22. 10 10
      lib/rmg/CZonePlacer.h
  23. 280 0
      lib/rmg/ConnectionsPlacer.cpp
  24. 35 0
      lib/rmg/ConnectionsPlacer.h
  25. 220 0
      lib/rmg/Functions.cpp
  26. 48 0
      lib/rmg/Functions.h
  27. 417 0
      lib/rmg/ObjectManager.cpp
  28. 56 0
      lib/rmg/ObjectManager.h
  29. 188 0
      lib/rmg/ObstaclePlacer.cpp
  30. 21 0
      lib/rmg/ObstaclePlacer.h
  31. 404 0
      lib/rmg/RiverPlacer.cpp
  32. 48 0
      lib/rmg/RiverPlacer.h
  33. 399 0
      lib/rmg/RmgArea.cpp
  34. 83 0
      lib/rmg/RmgArea.h
  35. 343 0
      lib/rmg/RmgMap.cpp
  36. 81 0
      lib/rmg/RmgMap.h
  37. 327 0
      lib/rmg/RmgObject.cpp
  38. 87 0
      lib/rmg/RmgObject.h
  39. 193 0
      lib/rmg/RmgPath.cpp
  40. 49 0
      lib/rmg/RmgPath.h
  41. 105 0
      lib/rmg/RoadPlacer.cpp
  42. 37 0
      lib/rmg/RoadPlacer.h
  43. 103 0
      lib/rmg/RockPlacer.cpp
  44. 30 0
      lib/rmg/RockPlacer.h
  45. 35 0
      lib/rmg/TerrainPainter.cpp
  46. 21 0
      lib/rmg/TerrainPainter.h
  47. 78 0
      lib/rmg/TileInfo.cpp
  48. 41 0
      lib/rmg/TileInfo.h
  49. 279 0
      lib/rmg/TownPlacer.cpp
  50. 37 0
      lib/rmg/TownPlacer.h
  51. 788 0
      lib/rmg/TreasurePlacer.cpp
  52. 67 0
      lib/rmg/TreasurePlacer.h
  53. 261 0
      lib/rmg/WaterAdopter.cpp
  54. 36 0
      lib/rmg/WaterAdopter.h
  55. 359 0
      lib/rmg/WaterProxy.cpp
  56. 55 0
      lib/rmg/WaterProxy.h
  57. 116 0
      lib/rmg/WaterRoutes.cpp
  58. 27 0
      lib/rmg/WaterRoutes.h
  59. 410 0
      lib/rmg/Zone.cpp
  60. 143 0
      lib/rmg/Zone.h

+ 1 - 1
client/CMusicHandler.h

@@ -11,12 +11,12 @@
 
 #include "../lib/CConfigHandler.h"
 #include "../lib/CSoundBase.h"
+#include "../lib/Terrain.h"
 
 struct _Mix_Music;
 struct SDL_RWops;
 typedef struct _Mix_Music Mix_Music;
 struct Mix_Chunk;
-class Terrain;
 
 class CAudioBase {
 protected:

+ 1 - 1
client/widgets/AdventureMapClasses.h

@@ -11,6 +11,7 @@
 
 #include "ObjectLists.h"
 #include "../../lib/FunctionList.h"
+#include "Terrain.h"
 
 class CArmedInstance;
 class CAnimation;
@@ -30,7 +31,6 @@ struct InfoAboutTown;
 class CHeroTooltip;
 class CTownTooltip;
 class CTextBox;
-class Terrain;
 
 /// Base UI Element for hero\town lists
 class CList : public CIntObject

+ 0 - 10
config/randomMap.json

@@ -18,16 +18,6 @@
   },
   "mines" :
   {
-    "value" :
-    {
-      "wood" : 1500,
-      "ore" : 1500,
-      "gems" : 3500,
-      "crystal" : 3500,
-      "mercury" : 3500,
-      "sulfur" : 3500,
-      "gold" : 7000
-    },
     "extraResourcesLimit" : 3
   },
   "minGuardStrength" : 2000,

+ 12 - 2
config/terrains.json

@@ -7,6 +7,7 @@
 		"music" : "Dirt.mp3",
 		"tiles" : "DIRTTL",
 		"code" : "dt",
+		"river" : "rm",
 		"battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"],
 		"terrainViewPatterns" : "dirt"
 	},
@@ -18,6 +19,7 @@
 		"music" : "Sand.mp3",
 		"tiles" : "SANDTL",
 		"code" : "sa",
+		"river" : "rm",
 		"battleFields" : ["sand_mesas"],
 		"transitionRequired" : true,
 		"terrainViewPatterns" : "sand"
@@ -30,6 +32,7 @@
 		"music" : "Grass.mp3",
 		"tiles" : "GRASTL",
 		"code" : "gr",
+		"river" : "rw",
 		"battleFields" : ["grass_hills", "grass_pines"]
 	},
 	"snow" :
@@ -40,6 +43,7 @@
 		"music" : "Snow.mp3",
 		"tiles" : "SNOWTL",
 		"code" : "sn",
+		"river" : "ri",
 		"battleFields" : ["snow_mountains", "snow_trees"]
 	},
 	"swamp" :
@@ -50,6 +54,7 @@
 		"music" : "Swamp.mp3",
 		"tiles" : "SWMPTL",
 		"code" : "sw",
+		"river" : "rw",
 		"battleFields" : ["swamp_trees"]
 	},
 	"rough" :
@@ -60,6 +65,7 @@
 		"music" : "Rough.mp3",
 		"tiles" : "ROUGTL",
 		"code" : "rg",
+		"river" : "rm",
 		"battleFields" : ["rough"]
 	},
 	"subterra" :
@@ -71,7 +77,9 @@
 		"tiles" : "SUBBTL",
 		"type" : "SUB",
 		"code" : "sb",
-		"battleFields" : ["subterranean"]
+		"river" : "rw",
+		"battleFields" : ["subterranean"],
+		"rockTerrain" : "rock"
 	},
 	"lava" :
 	{
@@ -81,7 +89,9 @@
 		"music" : "Lava.mp3",
 		"tiles" : "LAVATL",
 		"code" : "lv",
-		"battleFields" : ["lava"]
+		"river" : "rl",
+		"battleFields" : ["lava"],
+		"rockTerrain" : "rock"
 	},
 	"water" :
 	{

+ 1 - 1
lib/CCreatureHandler.h

@@ -19,12 +19,12 @@
 #include "JsonNode.h"
 #include "IHandlerBase.h"
 #include "CRandomGenerator.h"
+#include "Terrain.h"
 
 class CLegacyConfigParser;
 class CCreatureHandler;
 class CCreature;
 class JsonSerializeFormat;
-class Terrain;
 
 class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode
 {

+ 1 - 1
lib/CHeroHandler.h

@@ -18,6 +18,7 @@
 #include "GameConstants.h"
 #include "HeroBonus.h"
 #include "IHandlerBase.h"
+#include "Terrain.h"
 
 class CHeroClass;
 class CGameInfo;
@@ -26,7 +27,6 @@ struct BattleHex;
 class JsonNode;
 class CRandomGenerator;
 class JsonSerializeFormat;
-class Terrain;
 class BattleField;
 
 struct SSpecialtyInfo

+ 38 - 2
lib/CMakeLists.txt

@@ -81,12 +81,30 @@ set(lib_SRCS
 		registerTypes/TypesLobbyPacks.cpp
 		registerTypes/TypesServerPacks.cpp
 
+		rmg/RmgArea.cpp
+		rmg/RmgObject.cpp
+		rmg/RmgPath.cpp
 		rmg/CMapGenerator.cpp
 		rmg/CMapGenOptions.cpp
 		rmg/CRmgTemplate.cpp
 		rmg/CRmgTemplateStorage.cpp
-		rmg/CRmgTemplateZone.cpp
 		rmg/CZonePlacer.cpp
+		rmg/TileInfo.cpp
+		rmg/Zone.cpp
+		rmg/Functions.cpp
+		rmg/ObjectManager.cpp
+		rmg/RoadPlacer.cpp
+		rmg/TreasurePlacer.cpp
+		rmg/RmgMap.cpp
+		rmg/ConnectionsPlacer.cpp
+		rmg/WaterAdopter.cpp
+		rmg/TownPlacer.cpp
+		rmg/WaterProxy.cpp
+		rmg/WaterRoutes.cpp
+		rmg/RockPlacer.cpp
+		rmg/ObstaclePlacer.cpp
+		rmg/RiverPlacer.cpp
+		rmg/TerrainPainter.cpp
 
 		serializer/BinaryDeserializer.cpp
 		serializer/BinarySerializer.cpp
@@ -288,12 +306,30 @@ set(lib_HEADERS
 
 		registerTypes/RegisterTypes.h
 
+		rmg/RmgArea.h
+		rmg/RmgObject.h
+		rmg/RmgPath.h
 		rmg/CMapGenerator.h
 		rmg/CMapGenOptions.h
 		rmg/CRmgTemplate.h
 		rmg/CRmgTemplateStorage.h
-		rmg/CRmgTemplateZone.h
 		rmg/CZonePlacer.h
+		rmg/TileInfo.h
+		rmg/Zone.h
+		rmg/Functions.h
+		rmg/ObjectManager.h
+		rmg/RoadPlacer.h
+		rmg/TreasurePlacer.h
+		rmg/RmgMap.h
+		rmg/ConnectionsPlacer.h
+		rmg/WaterAdopter.h
+		rmg/TownPlacer.h
+		rmg/WaterProxy.h
+		rmg/WaterRoutes.h
+		rmg/RockPlacer.h
+		rmg/ObstaclePlacer.h
+		rmg/RiverPlacer.h
+		rmg/TerrainPainter.h
 		rmg/float3.h
 
 		serializer/BinaryDeserializer.h

+ 33 - 6
lib/Terrain.cpp

@@ -8,6 +8,7 @@
  *
  */
 
+#include "StdInc.h"
 #include "Terrain.h"
 #include "VCMI_Lib.h"
 #include "CModHandler.h"
@@ -78,8 +79,26 @@ Terrain::Manager::Manager()
 				auto s = terr.second["type"].String();
 				if(s == "LAND") info.type = Terrain::Info::Type::Land;
 				if(s == "WATER") info.type = Terrain::Info::Type::Water;
-				if(s == "SUB") info.type = Terrain::Info::Type::Subterranean;
 				if(s == "ROCK") info.type = Terrain::Info::Type::Rock;
+				if(s == "SUB") info.type = Terrain::Info::Type::Subterranean;
+			}
+			
+			if(terr.second["rockTerrain"].isNull())
+			{
+				info.rockTerrain = "rock";
+			}
+			else
+			{
+				info.rockTerrain = terr.second["rockTerrain"].String();
+			}
+			
+			if(terr.second["river"].isNull())
+			{
+				info.river = RIVER_NAMES[0];
+			}
+			else
+			{
+				info.river = terr.second["river"].String();
 			}
 			
 			if(terr.second["horseSoundId"].isNull())
@@ -114,6 +133,14 @@ Terrain::Manager::Manager()
 				}
 			}
 			
+			if(!terr.second["prohibitTransitions"].isNull())
+			{
+				for(auto & t : terr.second["prohibitTransitions"].Vector())
+				{
+					info.prohibitTransitions.emplace_back(t.String());
+				}
+			}
+			
 			info.transitionRequired = false;
 			if(!terr.second["transitionRequired"].isNull())
 			{
@@ -126,7 +153,7 @@ Terrain::Manager::Manager()
 				info.terrainViewPatterns = terr.second["terrainViewPatterns"].String();
 			}
 			
-			terrainInfo[Terrain(terr.first)] = info;
+			terrainInfo[terr.first] = info;
 		}
 	}
 }
@@ -139,15 +166,15 @@ Terrain::Manager & Terrain::Manager::get()
 
 std::vector<Terrain> Terrain::Manager::terrains()
 {
-	std::vector<Terrain> _terrains;
+	std::set<Terrain> _terrains; //have to use std::set to have ordered container. Othervise de-sync is possible
 	for(const auto & info : Terrain::Manager::get().terrainInfo)
-		_terrains.push_back(info.first);
-	return _terrains;
+		_terrains.insert(info.first);
+	return std::vector<Terrain>(_terrains.begin(), _terrains.end());
 }
 
 const Terrain::Info & Terrain::Manager::getInfo(const Terrain & terrain)
 {
-	return Terrain::Manager::get().terrainInfo.at(terrain);
+	return Terrain::Manager::get().terrainInfo.at(static_cast<std::string>(terrain));
 }
 
 std::ostream & operator<<(std::ostream & os, const Terrain terrainType)

+ 4 - 1
lib/Terrain.h

@@ -37,9 +37,12 @@ public:
 		std::string terrainText;
 		std::string typeCode;
 		std::string terrainViewPatterns;
+		std::string rockTerrain;
+		std::string river;
 		int horseSoundId;
 		Type type;
 		std::vector<std::string> battleFields;
+		std::vector<Terrain> prohibitTransitions;
 	};
 	
 	class DLL_LINKAGE Manager
@@ -52,7 +55,7 @@ public:
 		static Manager & get();
 		Manager();
 		
-		std::map<Terrain, Info> terrainInfo;
+		std::unordered_map<std::string, Info> terrainInfo;
 	};
 	
 	/*enum EETerrainType

+ 87 - 46
lib/mapping/CDrawRoadsOperation.cpp

@@ -12,7 +12,7 @@
 #include "CDrawRoadsOperation.h"
 #include "CMap.h"
 
-const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::patterns =
+const std::vector<CDrawLinesOperation::LinePattern> CDrawLinesOperation::patterns =
 {
 	//single tile. fall-back pattern
 	{
@@ -147,21 +147,31 @@ static bool ruleIsAny(const std::string & rule)
 }
 #endif
 
+///CDrawLinesOperation
+CDrawLinesOperation::CDrawLinesOperation(CMap * map, const CTerrainSelection & terrainSel, CRandomGenerator * gen):
+	CMapOperation(map), terrainSel(terrainSel), gen(gen)
+{
+}
+
 ///CDrawRoadsOperation
 CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen):
-	CMapOperation(map),terrainSel(terrainSel), roadType(roadType), gen(gen)
+	CDrawLinesOperation(map, terrainSel, gen), roadType(roadType)
 {
+}
 
+///CDrawRiversOperation
+CDrawRiversOperation::CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & riverType, CRandomGenerator * gen):
+	CDrawLinesOperation(map, terrainSel, gen), riverType(riverType)
+{
 }
 
-void CDrawRoadsOperation::execute()
+void CDrawLinesOperation::execute()
 {
 	std::set<int3> invalidated;
 
 	for(const auto & pos : terrainSel.getSelectedItems())
 	{
-		auto & tile = map->getTile(pos);
-		tile.roadType = roadType;
+		executeTile(map->getTile(pos));
 
 		auto rect = extendTileAroundSafely(pos);
 		rect.forEach([&invalidated](const int3 & pos)
@@ -173,28 +183,17 @@ void CDrawRoadsOperation::execute()
 	updateTiles(invalidated);
 }
 
-void CDrawRoadsOperation::undo()
+void CDrawLinesOperation::undo()
 {
   //TODO
 }
 
-void CDrawRoadsOperation::redo()
+void CDrawLinesOperation::redo()
 {
   //TODO
 }
 
-std::string CDrawRoadsOperation::getLabel() const
-{
-	return "Draw Roads";
-}
-
-bool CDrawRoadsOperation::canApplyPattern(const RoadPattern & pattern) const
-{
-	//TODO: this method should be virtual for river support
-	return pattern.roadMapping.first >= 0;
-}
-
-void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const
+void CDrawLinesOperation::flipPattern(LinePattern& pattern, int flip) const
 {
 	//todo: use cashing here and also in terrain patterns
 
@@ -222,13 +221,7 @@ void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const
 	}
 }
 
-
-bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
-{
-	return tile.roadType != ROAD_NAMES[0]; //TODO: this method should be virtual for river support
-}
-
-void CDrawRoadsOperation::updateTiles(std::set<int3> & invalidated)
+void CDrawLinesOperation::updateTiles(std::set<int3> & invalidated)
 {
 	for(int3 coord : invalidated)
 	{
@@ -259,25 +252,7 @@ void CDrawRoadsOperation::updateTiles(std::set<int3> & invalidated)
 	}
 }
 
-bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
-{
-//TODO: this method should be virtual for river support
-
-   return map->getTile(pos).roadType != ROAD_NAMES[0];
-}
-
-
-void CDrawRoadsOperation::updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip)
-{
-  //TODO: this method should be virtual for river support
-
-	const std::pair<int, int> & mapping  = pattern.roadMapping;
-
-	tile.roadDir = gen->nextInt(mapping.first, mapping.second);
-	tile.extTileFlags = (tile.extTileFlags & 0xCF) | (flip << 4);
-}
-
-CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const RoadPattern & pattern, const int3 & pos)
+CDrawLinesOperation::ValidationResult CDrawLinesOperation::validateTile(const LinePattern & pattern, const int3 & pos)
 {
 	ValidationResult result(false);
 
@@ -294,7 +269,7 @@ CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const Ro
 		if((flip == FLIP_PATTERN_VERTICAL) && !(pattern.hasVFlip))
 			continue;
 
-		RoadPattern flipped = pattern;
+		LinePattern flipped = pattern;
 
 		flipPattern(flipped, flip);
 
@@ -344,3 +319,69 @@ CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const Ro
 
 	return result;
 }
+
+std::string CDrawRoadsOperation::getLabel() const
+{
+	return "Draw Roads";
+}
+
+std::string CDrawRiversOperation::getLabel() const
+{
+	return "Draw Rivers";
+}
+
+void CDrawRoadsOperation::executeTile(TerrainTile & tile)
+{
+	tile.roadType = roadType;
+}
+
+void CDrawRiversOperation::executeTile(TerrainTile & tile)
+{
+	tile.riverType = riverType;
+}
+
+bool CDrawRoadsOperation::canApplyPattern(const LinePattern & pattern) const
+{
+	return pattern.roadMapping.first >= 0;
+}
+
+bool CDrawRiversOperation::canApplyPattern(const LinePattern & pattern) const
+{
+	return pattern.riverMapping.first >= 0;
+}
+
+bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
+{
+	return tile.roadType != ROAD_NAMES[0];
+}
+
+bool CDrawRiversOperation::needUpdateTile(const TerrainTile & tile) const
+{
+	return tile.riverType != RIVER_NAMES[0];
+}
+
+bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
+{
+	return map->getTile(pos).roadType != ROAD_NAMES[0];
+}
+
+bool CDrawRiversOperation::tileHasSomething(const int3& pos) const
+{
+	return map->getTile(pos).riverType != RIVER_NAMES[0];
+}
+
+void CDrawRoadsOperation::updateTile(TerrainTile & tile, const LinePattern & pattern, const int flip)
+{
+	const std::pair<int, int> & mapping  = pattern.roadMapping;
+
+	tile.roadDir = gen->nextInt(mapping.first, mapping.second);
+	tile.extTileFlags = (tile.extTileFlags & 0b11001111) | (flip << 4);
+}
+
+void CDrawRiversOperation::updateTile(TerrainTile & tile, const LinePattern & pattern, const int flip)
+{
+	const std::pair<int, int> & mapping  = pattern.riverMapping;
+	
+	tile.riverDir = gen->nextInt(mapping.first, mapping.second);
+	tile.extTileFlags = (tile.extTileFlags & 0b00111111) | (flip << 2);
+}

+ 49 - 14
lib/mapping/CDrawRoadsOperation.h

@@ -15,17 +15,16 @@
 
 struct TerrainTile;
 
-class CDrawRoadsOperation : public CMapOperation
+class CDrawLinesOperation : public CMapOperation
 {
 public:
-	CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen);
 	void execute() override;
 	void undo() override;
 	void redo() override;
-	std::string getLabel() const override;	
-private:
 	
-	struct RoadPattern
+protected:
+	
+	struct LinePattern
 	{
 		std::string data[9];
 		std::pair<int, int> roadMapping, riverMapping;
@@ -39,20 +38,56 @@ private:
 		int flip;
 	};
 	
-	static const std::vector<RoadPattern> patterns;
+	CDrawLinesOperation(CMap * map, const CTerrainSelection & terrainSel, CRandomGenerator * gen);
 	
-	void flipPattern(RoadPattern & pattern, int flip) const;
+	virtual void executeTile(TerrainTile & tile) = 0;
+	virtual bool canApplyPattern(const CDrawLinesOperation::LinePattern & pattern) const = 0;
+	virtual bool needUpdateTile(const TerrainTile & tile) const = 0;
+	virtual bool tileHasSomething(const int3 & pos) const = 0;
+	virtual void updateTile(TerrainTile & tile, const CDrawLinesOperation::LinePattern & pattern, const int flip) = 0;
 	
-	void updateTiles(std::set<int3> & invalidated);
+	static const std::vector<LinePattern> patterns;
+	
+	void flipPattern(LinePattern & pattern, int flip) const;
 	
-	ValidationResult validateTile(const RoadPattern & pattern, const int3 & pos);
-	void updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip);
+	void updateTiles(std::set<int3> & invalidated);
 	
-	bool canApplyPattern(const RoadPattern & pattern) const;
-	bool needUpdateTile(const TerrainTile & tile) const;
-	bool tileHasSomething(const int3 & pos) const;
+	ValidationResult validateTile(const LinePattern & pattern, const int3 & pos);
 	
 	CTerrainSelection terrainSel;
+	CRandomGenerator * gen;
+};
+
+class CDrawRoadsOperation : public CDrawLinesOperation
+{
+public:
+	CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen);
+	std::string getLabel() const override;
+	
+protected:
+	void executeTile(TerrainTile & tile) override;
+	bool canApplyPattern(const CDrawLinesOperation::LinePattern & pattern) const override;
+	bool needUpdateTile(const TerrainTile & tile) const override;
+	bool tileHasSomething(const int3 & pos) const override;
+	void updateTile(TerrainTile & tile, const CDrawLinesOperation::LinePattern & pattern, const int flip) override;
+	
+private:
 	std::string roadType;
-	CRandomGenerator * gen;	
+};
+
+class CDrawRiversOperation : public CDrawLinesOperation
+{
+public:
+	CDrawRiversOperation(CMap * map, const CTerrainSelection & terrainSel, const std::string & roadType, CRandomGenerator * gen);
+	std::string getLabel() const override;
+	
+protected:
+	void executeTile(TerrainTile & tile) override;
+	bool canApplyPattern(const CDrawLinesOperation::LinePattern & pattern) const override;
+	bool needUpdateTile(const TerrainTile & tile) const override;
+	bool tileHasSomething(const int3 & pos) const override;
+	void updateTile(TerrainTile & tile, const CDrawLinesOperation::LinePattern & pattern, const int flip) override;
+	
+private:
+	std::string riverType;
 };

+ 9 - 2
lib/mapping/CMapEditManager.cpp

@@ -110,7 +110,7 @@ void CTerrainSelection::deselectRange(const MapRect & rect)
 	});
 }
 
-void CTerrainSelection::setSelection(std::vector<int3> & vec)
+void CTerrainSelection::setSelection(const std::vector<int3> & vec)
 {
 	for (auto pos : vec)
 		this->select(pos);
@@ -255,6 +255,13 @@ void CMapEditManager::drawRoad(const std::string & roadType, CRandomGenerator* g
 	terrainSel.clearSelection();
 }
 
+void CMapEditManager::drawRiver(const std::string & riverType, CRandomGenerator* gen)
+{
+	execute(make_unique<CDrawRiversOperation>(map, terrainSel, riverType, gen ? gen : &(this->gen)));
+	terrainSel.clearSelection();
+}
+
+
 
 void CMapEditManager::insertObject(CGObjectInstance * obj)
 {
@@ -818,7 +825,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 		else
 		{
 			terType = map->getTile(currentPos).terType;
-			if(terType != centerTerType)
+			if(terType != centerTerType && (terType.isPassable() || centerTerType.isPassable()))
 			{
 				isAlien = true;
 			}

+ 4 - 1
lib/mapping/CMapEditManager.h

@@ -93,7 +93,7 @@ public:
 	void deselectRange(const MapRect & rect) override;
 	void selectAll() override;
 	void clearSelection() override;
-	void setSelection(std::vector<int3> & vec);
+	void setSelection(const std::vector<int3> & vec);
 };
 
 /// Selection class to select objects.
@@ -173,6 +173,9 @@ public:
 
 	/// Draws roads at the current terrain selection. The selection will be cleared automatically.
 	void drawRoad(const std::string & roadType, CRandomGenerator * gen = nullptr);
+	
+	/// Draws rivers at the current terrain selection. The selection will be cleared automatically.
+	void drawRiver(const std::string & riverType, CRandomGenerator * gen = nullptr);
 
 	void insertObject(CGObjectInstance * obj);
 

+ 3 - 0
lib/rmg/CMapGenOptions.cpp

@@ -208,6 +208,7 @@ void CMapGenOptions::setMapTemplate(const CRmgTemplate * value)
 
 void CMapGenOptions::finalize(CRandomGenerator & rand)
 {
+	logGlobal->info("RMG map: %dx%d, %s underground", getWidth(), getHeight(), getHasTwoLevels() ? "WITH" : "NO");
 	logGlobal->info("RMG settings: players %d, teams %d, computer players %d, computer teams %d, water %d, monsters %d",
 		static_cast<int>(getPlayerCount()), static_cast<int>(getTeamCount()), static_cast<int>(getCompOnlyPlayerCount()),
 		static_cast<int>(getCompOnlyTeamCount()), static_cast<int>(getWaterContent()), static_cast<int>(getMonsterStrength()));
@@ -217,6 +218,8 @@ void CMapGenOptions::finalize(CRandomGenerator & rand)
 		mapTemplate = getPossibleTemplate(rand);
 	}
 	assert(mapTemplate);
+	
+	logGlobal->info("RMG template name: %s", mapTemplate->getName());
 
 	if (getPlayerCount() == RANDOM_SIZE)
 	{

+ 68 - 719
lib/rmg/CMapGenerator.cpp

@@ -18,67 +18,32 @@
 #include "../StringConstants.h"
 #include "../filesystem/Filesystem.h"
 #include "CZonePlacer.h"
-#include "CRmgTemplateZone.h"
 #include "../mapObjects/CObjectClassesHandler.h"
-
-static const int3 dirs4[] = {int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0)};
-static const int3 dirsDiagonal[] = { int3(1,1,0),int3(1,-1,0),int3(-1,1,0),int3(-1,-1,0) };
-
-void CMapGenerator::foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo)
-{
-	for(const int3 &dir : int3::getDirs())
-	{
-		int3 n = pos + dir;
-		/*important notice: perform any translation before this function is called,
-		so the actual map position is checked*/
-		if(map->isInTheMap(n))
-			foo(n);
-	}
-}
-
-void CMapGenerator::foreachDirectNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
-{
-	for(const int3 &dir : dirs4)
-	{
-		int3 n = pos + dir;
-		if(map->isInTheMap(n))
-			foo(n);
-	}
-}
-
-void CMapGenerator::foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
-{
-	for (const int3 &dir : dirsDiagonal)
-	{
-		int3 n = pos + dir;
-		if (map->isInTheMap(n))
-			foo(n);
-	}
-}
-
+#include "TileInfo.h"
+#include "Zone.h"
+#include "Functions.h"
+#include "RmgMap.h"
+#include "ObjectManager.h"
+#include "TreasurePlacer.h"
+#include "RoadPlacer.h"
 
 CMapGenerator::CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed) :
 	mapGenOptions(mapGenOptions), randomSeed(RandomSeed),
-	zonesTotal(0), tiles(nullptr), prisonsRemaining(0),
-    monolithIndex(0)
+	prisonsRemaining(0), monolithIndex(0)
 {
 	loadConfig();
 	rand.setSeed(this->randomSeed);
 	mapGenOptions.finalize(rand);
+	map = std::make_unique<RmgMap>(mapGenOptions);
+}
+
+int CMapGenerator::getRandomSeed() const
+{
+	return randomSeed;
 }
 
 void CMapGenerator::loadConfig()
 {
-	static const std::map<std::string, Res::ERes> resMap
-	{
-		{"wood", Res::ERes::WOOD},
-		{"ore", Res::ERes::ORE},
-		{"gems", Res::ERes::GEMS},
-		{"crystal", Res::ERes::CRYSTAL},
-		{"mercury", Res::ERes::MERCURY},
-		{"sulfur", Res::ERes::SULFUR},
-		{"gold", Res::ERes::GOLD},
-	};
 	static const ResourceID path("config/randomMap.json");
 	JsonNode randomMapJson(path);
 	for(auto& s : randomMapJson["terrain"]["undergroundAllow"].Vector())
@@ -96,10 +61,6 @@ void CMapGenerator::loadConfig()
 	{
 		config.waterTreasure.emplace_back(treasure["min"].Integer(), treasure["max"].Integer(), treasure["density"].Integer());
 	}
-	for(auto& s : resMap)
-	{
-		config.mineValues[s.second] = randomMapJson["mines"]["value"][s.first].Integer();
-	}
 	config.mineExtraResources = randomMapJson["mines"]["extraResourcesLimit"].Integer();
 	config.minGuardStrength = randomMapJson["minGuardStrength"].Integer();
 	config.defaultRoadType = randomMapJson["defaultRoadType"].String();
@@ -128,49 +89,19 @@ const CMapGenerator::Config & CMapGenerator::getConfig() const
 	return config;
 }
 
-void CMapGenerator::initTiles()
+CMapGenerator::~CMapGenerator()
 {
-	map->initTerrain();
-
-	int width = map->width;
-	int height = map->height;
-
-	int level = map->twoLevel ? 2 : 1;
-	tiles = new CTileInfo**[width];
-	for (int i = 0; i < width; ++i)
-	{
-		tiles[i] = new CTileInfo*[height];
-		for (int j = 0; j < height; ++j)
-		{
-			tiles[i][j] = new CTileInfo[level];
-		}
-	}
-
-	zoneColouring.resize(boost::extents[map->twoLevel ? 2 : 1][map->width][map->height]);
 }
 
-CMapGenerator::~CMapGenerator()
+const CMapGenOptions& CMapGenerator::getMapGenOptions() const
 {
-	if (tiles)
-	{
-		int width = mapGenOptions.getWidth();
-		int height = mapGenOptions.getHeight();
-		for (int i=0; i < width; i++)
-		{
-			for(int j=0; j < height; j++)
-			{
-				delete [] tiles[i][j];
-			}
-			delete [] tiles[i];
-		}
-		delete [] tiles;
-	}
+	return mapGenOptions;
 }
 
 void CMapGenerator::initPrisonsRemaining()
 {
 	prisonsRemaining = 0;
-	for (auto isAllowed : map->allowedHeroes)
+	for (auto isAllowed : map->map().allowedHeroes)
 	{
 		if (isAllowed)
 			prisonsRemaining++;
@@ -187,40 +118,26 @@ void CMapGenerator::initQuestArtsRemaining()
 	}
 }
 
-const CMapGenOptions& CMapGenerator::getMapGenOptions() const
-{
-	return mapGenOptions;
-}
-
-CMapEditManager* CMapGenerator::getEditManager() const
-{
-	if(!map)
-		return nullptr;
-	return map->getEditManager();
-}
-
 std::unique_ptr<CMap> CMapGenerator::generate()
 {
-	map = make_unique<CMap>();
 	try
 	{
-		map->getEditManager()->getUndoManager().setUndoRedoLimit(0);
-		//FIXME:  somehow mapGenOption is nullptr at this point :?
 		addHeaderInfo();
-		initTiles();
+		map->initTiles(*this);
 		initPrisonsRemaining();
 		initQuestArtsRemaining();
 		genZones();
-		map->calculateGuardingGreaturePositions(); //clear map so that all tiles are unguarded
+		map->map().calculateGuardingGreaturePositions(); //clear map so that all tiles are unguarded
+		map->addModificators();
 		fillZones();
 		//updated guarded tiles will be calculated in CGameState::initMapObjects()
-		zones.clear();
+		map->getZones().clear();
 	}
 	catch (rmgException &e)
 	{
 		logGlobal->error("Random map generation received exception: %s", e.what());
 	}
-	return std::move(map);
+	return std::move(map->mapInstance);
 }
 
 std::string CMapGenerator::getMapDescription() const
@@ -239,7 +156,7 @@ std::string CMapGenerator::getMapDescription() const
     std::stringstream ss;
     ss << boost::str(boost::format(std::string("Map created by the Random Map Generator.\nTemplate was %s, Random seed was %d, size %dx%d") +
         ", levels %s, players %d, computers %d, water %s, monster %s, VCMI map") % mapTemplate->getName() %
-		randomSeed % map->width % map->height % (map->twoLevel ? "2" : "1") % static_cast<int>(mapGenOptions.getPlayerCount()) %
+		randomSeed % map->map().width % map->map().height % (map->map().twoLevel ? "2" : "1") % static_cast<int>(mapGenOptions.getPlayerCount()) %
 		static_cast<int>(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] %
 		monsterStrengthStr[monsterStrengthIndex]);
 
@@ -329,626 +246,107 @@ void CMapGenerator::addPlayerInfo()
 		auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand);
 		player.team = TeamID(*itTeam);
 		teamNumbers[j].erase(itTeam);
-		map->players[pSettings.getColor().getNum()] = player;
+		map->map().players[pSettings.getColor().getNum()] = player;
 	}
 
-	map->howManyTeams = (mapGenOptions.getTeamCount() == 0 ? mapGenOptions.getPlayerCount() : mapGenOptions.getTeamCount())
+	map->map().howManyTeams = (mapGenOptions.getTeamCount() == 0 ? mapGenOptions.getPlayerCount() : mapGenOptions.getTeamCount())
 			+ (mapGenOptions.getCompOnlyTeamCount() == 0 ? mapGenOptions.getCompOnlyPlayerCount() : mapGenOptions.getCompOnlyTeamCount());
 }
 
 void CMapGenerator::genZones()
 {
-	getEditManager()->clearTerrain(&rand);
-	getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight()));
-	getEditManager()->drawTerrain(Terrain("grass"), &rand);
-
-	auto tmpl = mapGenOptions.getMapTemplate();
-	zones.clear();
-	for(const auto & option : tmpl->getZones())
-	{
-		auto zone = std::make_shared<CRmgTemplateZone>(this);
-		zone->setOptions(*option.second.get());
-		zones[zone->getId()] = zone;
-	}
-
-	CZonePlacer placer(this);
+	CZonePlacer placer(*map);
 	placer.placeZones(&rand);
-	placer.assignZones();
-	
-	//add special zone for water
-	zoneWater.first = zones.size() + 1;
-	zoneWater.second = std::make_shared<CRmgTemplateZone>(this);
-	{
-		rmg::ZoneOptions options;
-		options.setId(zoneWater.first);
-		options.setType(ETemplateZoneType::WATER);
-		zoneWater.second->setOptions(options);
-	}
+	placer.assignZones(&rand);
 
 	logGlobal->info("Zones generated successfully");
 }
 
 void CMapGenerator::createWaterTreasures()
 {
+	if(!getZoneWater())
+		return;
+	
 	//add treasures on water
 	for(auto & treasureInfo : getConfig().waterTreasure)
 	{
-		getZoneWater().second->addTreasureInfo(treasureInfo);
+		getZoneWater()->addTreasureInfo(treasureInfo);
 	}
 }
 
-void CMapGenerator::prepareWaterTiles()
-{
-	for(auto & t : zoneWater.second->getTileInfo())
-		if(shouldBeBlocked(t))
-			setOccupied(t, ETileType::POSSIBLE);
-}
-
 void CMapGenerator::fillZones()
 {
-	//init native town count with 0
-	for (auto faction : VLC->townh->getAllowedFactions())
-		zonesPerFaction[faction] = 0;
-
 	findZonesForQuestArts();
+	createWaterTreasures();
 
 	logGlobal->info("Started filling zones");
 
 	//we need info about all town types to evaluate dwellings and pandoras with creatures properly
 	//place main town in the middle
-	for(auto it : zones)
-		it.second->initTownType();
 	
-	//make sure there are some free tiles in the zone
-	for(auto it : zones)
+	for(auto it : map->getZones())
+	{
+		it.second->initModificators();
 		it.second->initFreeTiles();
-	
-	for(auto it : zones)
-		it.second->createBorder(); //once direct connections are done
-	
-#ifdef _BETA
-	dump(false);
-#endif
-	
-	for(auto it : zones)
-		it.second->createWater(getMapGenOptions().getWaterContent());
-	
-	zoneWater.second->waterInitFreeTiles();
-	
-#ifdef _BETA
-	dump(false);
-#endif
-
-	createDirectConnections(); //direct
-	createConnections2(); //subterranean gates and monoliths
-	
-	for(auto it : zones)
-		zoneWater.second->waterConnection(*it.second);
-	
-	createWaterTreasures();
-	zoneWater.second->initFreeTiles();
-	zoneWater.second->fill();
+	}
 
-	std::vector<std::shared_ptr<CRmgTemplateZone>> treasureZones;
-	for(auto it : zones)
+	std::vector<std::shared_ptr<Zone>> treasureZones;
+	for(auto it : map->getZones())
 	{
-		it.second->fill();
+		it.second->processModificators();
+		
 		if (it.second->getType() == ETemplateZoneType::TREASURE)
 			treasureZones.push_back(it.second);
 	}
-	
-#ifdef _BETA
-	dump(false);
-#endif
-		
-	//set apriopriate free/occupied tiles, including blocked underground rock
-	createObstaclesCommon1();
-	//set back original terrain for underground zones
-	for(auto it : zones)
-		it.second->createObstacles1();
-	
-	createObstaclesCommon2();
-	//place actual obstacles matching zone terrain
-	for(auto it : zones)
-		it.second->createObstacles2();
-		
-	zoneWater.second->createObstacles2();
-	
-#ifdef _BETA
-	dump(false);
-#endif
-
-	#define PRINT_MAP_BEFORE_ROADS false
-	if (PRINT_MAP_BEFORE_ROADS) //enable to debug
-	{
-		std::ofstream out("road_debug.txt");
-		int levels = map->twoLevel ? 2 : 1;
-		int width = map->width;
-		int height = map->height;
-		for (int k = 0; k < levels; k++)
-		{
-			for (int j = 0; j<height; j++)
-			{
-				for (int i = 0; i<width; i++)
-				{
-					char t = '?';
-					switch (getTile(int3(i, j, k)).getTileType())
-					{
-					case ETileType::FREE:
-						t = ' '; break;
-					case ETileType::BLOCKED:
-						t = '#'; break;
-					case ETileType::POSSIBLE:
-						t = '-'; break;
-					case ETileType::USED:
-						t = 'O'; break;
-					}
-
-					out << t;
-				}
-				out << std::endl;
-			}
-			out << std::endl;
-		}
-		out << std::endl;
-	}
-
-	for(auto it : zones)
-	{
-		it.second->connectRoads(); //draw roads after everything else has been placed
-	}
 
 	//find place for Grail
 	if(treasureZones.empty())
 	{
-		for(auto it : zones)
-			treasureZones.push_back(it.second);
+		for(auto it : map->getZones())
+			if(it.second->getType() != ETemplateZoneType::WATER)
+				treasureZones.push_back(it.second);
 	}
 	auto grailZone = *RandomGeneratorUtil::nextItem(treasureZones, rand);
 
-	map->grailPos = *RandomGeneratorUtil::nextItem(*grailZone->getFreePaths(), rand);
+	map->map().grailPos = *RandomGeneratorUtil::nextItem(grailZone->freePaths().getTiles(), rand);
 
 	logGlobal->info("Zones filled successfully");
 }
 
-void CMapGenerator::createObstaclesCommon1()
-{
-	if(map->twoLevel) //underground
-	{
-		//negative approach - create rock tiles first, then make sure all accessible tiles have no rock
-		std::vector<int3> rockTiles;
-
-		for(int x = 0; x < map->width; x++)
-		{
-			for(int y = 0; y < map->height; y++)
-			{
-				int3 tile(x, y, 1);
-				if (shouldBeBlocked(tile))
-				{
-					rockTiles.push_back(tile);
-				}
-			}
-		}
-		getEditManager()->getTerrainSelection().setSelection(rockTiles);
-		
-		//collect all rock terrain types
-		std::vector<Terrain> rockTerrains;
-		for(auto & terrain : Terrain::Manager::terrains())
-			if(!terrain.isPassable())
-				rockTerrains.push_back(terrain);
-		auto rockTerrain = *RandomGeneratorUtil::nextItem(rockTerrains, rand);
-		
-		getEditManager()->drawTerrain(rockTerrain, &rand);
-	}
-}
-
-void CMapGenerator::createObstaclesCommon2()
-{
-	if(map->twoLevel)
-	{
-		//finally mark rock tiles as occupied, spawn no obstacles there
-		for(int x = 0; x < map->width; x++)
-		{
-			for(int y = 0; y < map->height; y++)
-			{
-				int3 tile(x, y, 1);
-				if(!map->getTile(tile).terType.isPassable())
-				{
-					setOccupied(tile, ETileType::USED);
-				}
-			}
-		}
-	}
-
-	//tighten obstacles to improve visuals
-
-	for (int i = 0; i < 3; ++i)
-	{
-		int blockedTiles = 0;
-		int freeTiles = 0;
-
-		for (int z = 0; z < (map->twoLevel ? 2 : 1); z++)
-		{
-			for (int x = 0; x < map->width; x++)
-			{
-				for (int y = 0; y < map->height; y++)
-				{
-					int3 tile(x, y, z);
-					if (!isPossible(tile)) //only possible tiles can change
-						continue;
-
-					int blockedNeighbours = 0;
-					int freeNeighbours = 0;
-					foreach_neighbour(tile, [this, &blockedNeighbours, &freeNeighbours](int3 &pos)
-					{
-						if (this->isBlocked(pos))
-							blockedNeighbours++;
-						if (this->isFree(pos))
-							freeNeighbours++;
-					});
-					if (blockedNeighbours > 4)
-					{
-						setOccupied(tile, ETileType::BLOCKED);
-						blockedTiles++;
-					}
-					else if (freeNeighbours > 4)
-					{
-						setOccupied(tile, ETileType::FREE);
-						freeTiles++;
-					}
-				}
-			}
-		}
-		logGlobal->trace("Set %d tiles to BLOCKED and %d tiles to FREE", blockedTiles, freeTiles);
-	}
-}
-
 void CMapGenerator::findZonesForQuestArts()
 {
 	//we want to place arties in zones that were not yet filled (higher index)
 
 	for (auto connection : mapGenOptions.getMapTemplate()->getConnections())
 	{
-		auto zoneA = zones[connection.getZoneA()];
-		auto zoneB = zones[connection.getZoneB()];
+		auto zoneA = map->getZones()[connection.getZoneA()];
+		auto zoneB = map->getZones()[connection.getZoneB()];
 
 		if (zoneA->getId() > zoneB->getId())
 		{
-			zoneB->setQuestArtZone(zoneA);
+			if(auto * m = zoneB->getModificator<TreasurePlacer>())
+				zoneB->getModificator<TreasurePlacer>()->setQuestArtZone(zoneA.get());
 		}
 		else if (zoneA->getId() < zoneB->getId())
 		{
-			zoneA->setQuestArtZone(zoneB);
-		}
-	}
-}
-
-void CMapGenerator::createDirectConnections()
-{
-	bool waterMode = getMapGenOptions().getWaterContent() != EWaterContent::NONE;
-	
-	for (auto connection : mapGenOptions.getMapTemplate()->getConnections())
-	{
-		auto zoneA = zones[connection.getZoneA()];
-		auto zoneB = zones[connection.getZoneB()];
-
-		//rearrange tiles in random order
-		const auto & tiles = zoneA->getTileInfo();
-
-		int3 guardPos(-1,-1,-1);
-
-		int3 posA = zoneA->getPos();
-		int3 posB = zoneB->getPos();
-		// auto zoneAid = zoneA->getId();
-		auto zoneBid = zoneB->getId();
-		
-		if (posA.z == posB.z)
-		{
-			std::vector<int3> middleTiles;
-			for (const auto& tile : tiles)
-			{
-				if (isUsed(tile) || getZoneID(tile)==zoneWater.first) //tiles may be occupied by towns or water
-					continue;
-				foreachDirectNeighbour(tile, [tile, &middleTiles, this, zoneBid](int3 & pos) //must be direct since paths also also generated between direct neighbours
-				{
-					if(getZoneID(pos) == zoneBid)
-						middleTiles.push_back(tile);
-				});
-			}
-
-			//find tiles with minimum manhattan distance from center of the mass of zone border
-			size_t tilesCount = middleTiles.size() ? middleTiles.size() : 1;
-			int3 middleTile = std::accumulate(middleTiles.begin(), middleTiles.end(), int3(0, 0, 0));
-			middleTile.x /= (si32)tilesCount;
-			middleTile.y /= (si32)tilesCount;
-			middleTile.z /= (si32)tilesCount; //TODO: implement division operator for int3?
-			boost::sort(middleTiles, [middleTile](const int3 &lhs, const int3 &rhs) -> bool
-			{
-				//choose tiles with both corrdinates in the middle
-				return lhs.mandist2d(middleTile) < rhs.mandist2d(middleTile);
-			});
-
-			//remove 1/4 tiles from each side - path should cross zone borders at smooth angle
-			size_t removedCount = tilesCount / 4; //rounded down
-			middleTiles.erase(middleTiles.end() - removedCount, middleTiles.end());
-			middleTiles.erase(middleTiles.begin(), middleTiles.begin() + removedCount);
-
-			RandomGeneratorUtil::randomShuffle(middleTiles, rand);
-			for (auto tile : middleTiles)
-			{
-				guardPos = tile;
-				if (guardPos.valid())
-				{
-					//zones can make paths only in their own area
-					zoneA->connectWithCenter(guardPos, true, true);
-					zoneB->connectWithCenter(guardPos, true, true);
-
-					bool monsterPresent = zoneA->addMonster(guardPos, connection.getGuardStrength(), false, true);
-					zoneB->updateDistances(guardPos); //place next objects away from guard in both zones
-
-					//set free tile only after connection is made to the center of the zone
-					if (!monsterPresent)
-						setOccupied(guardPos, ETileType::FREE); //just in case monster is too weak to spawn
-
-					zoneA->addRoadNode(guardPos);
-					zoneB->addRoadNode(guardPos);
-					break; //we're done with this connection
-				}
-			}
-		}
-		
-		if (!guardPos.valid())
-		{
-			if(!waterMode || posA.z != posB.z || !zoneWater.second->waterKeepConnection(connection.getZoneA(), connection.getZoneB()))
-				connectionsLeft.push_back(connection);
-		}
-	}
-}
-
-void CMapGenerator::createConnections2()
-{
-	for (auto & connection : connectionsLeft)
-	{
-		auto zoneA = zones[connection.getZoneA()];
-		auto zoneB = zones[connection.getZoneB()];
-
-		int3 guardPos(-1, -1, -1);
-
-		int3 posA = zoneA->getPos();
-		int3 posB = zoneB->getPos();
-
-		auto strength = connection.getGuardStrength();
-
-		if (posA.z != posB.z) //try to place subterranean gates
-		{
-			auto sgt = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0)->getTemplates().front();
-			auto tilesBlockedByObject = sgt.getBlockedOffsets();
-
-			auto factory = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0);
-			auto gate1 = factory->create(ObjectTemplate());
-			auto gate2 = factory->create(ObjectTemplate());
-
-			while (!guardPos.valid())
-			{
-				bool continueOuterLoop = false;
-				//find common tiles for both zones
-				auto tileSetA = zoneA->getPossibleTiles(),
-					tileSetB = zoneB->getPossibleTiles();
-
-				std::vector<int3> tilesA(tileSetA.begin(), tileSetA.end()),
-					tilesB(tileSetB.begin(), tileSetB.end());
-
-				std::vector<int3> commonTiles;
-
-				auto lambda = [](const int3 & lhs, const int3 & rhs) -> bool
-				{
-					//https://stackoverflow.com/questions/45966807/c-invalid-comparator-assert
-					return std::tie(lhs.x, lhs.y) < std::tie(rhs.x, rhs.y); //ignore z coordinate
-				};
-				//required for set_intersection
-				boost::sort(tilesA, lambda);
-				boost::sort(tilesB, lambda);
-
-				boost::set_intersection(tilesA, tilesB, std::back_inserter(commonTiles), lambda);
-
-				vstd::erase_if(commonTiles, [](const int3 &tile) -> bool
-				{
-					return (!tile.x) || (!tile.y); //gates shouldn't go outside map (x = 0) and look bad at the very top (y = 0)
-				});
-
-				if (commonTiles.empty())
-					break; //nothing more to do
-
-				boost::sort(commonTiles, [posA, posB](const int3 &lhs, const int3 &rhs) -> bool
-				{
-					//choose tiles which are equidistant to zone centers
-					return (std::abs<double>(posA.dist2dSQ(lhs) - posB.dist2dSQ(lhs)) < std::abs<double>((posA.dist2dSQ(rhs) - posB.dist2dSQ(rhs))));
-				});
-
-				for (auto tile : commonTiles)
-				{
-					tile.z = posA.z;
-					int3 otherTile = tile;
-					otherTile.z = posB.z;
-
-					float distanceFromA = static_cast<float>(posA.dist2d(tile));
-					float distanceFromB = static_cast<float>(posB.dist2d(otherTile));
-
-					if (distanceFromA > 5 && distanceFromB > 5)
-					{
-						if (zoneA->areAllTilesAvailable(gate1, tile, tilesBlockedByObject) &&
-							zoneB->areAllTilesAvailable(gate2, otherTile, tilesBlockedByObject))
-						{
-							if (zoneA->getAccessibleOffset(sgt, tile).valid() && zoneB->getAccessibleOffset(sgt, otherTile).valid())
-							{
-								EObjectPlacingResult::EObjectPlacingResult result1 = zoneA->tryToPlaceObjectAndConnectToPath(gate1, tile);
-								EObjectPlacingResult::EObjectPlacingResult result2 = zoneB->tryToPlaceObjectAndConnectToPath(gate2, otherTile);
-
-								if ((result1 == EObjectPlacingResult::SUCCESS) && (result2 == EObjectPlacingResult::SUCCESS))
-								{
-									zoneA->placeObject(gate1, tile);
-									zoneA->guardObject(gate1, strength, true, true);
-									zoneB->placeObject(gate2, otherTile);
-									zoneB->guardObject(gate2, strength, true, true);
-									guardPos = tile; //set to break the loop
-									break;
-								}
-								else if ((result1 == EObjectPlacingResult::SEALED_OFF) || (result2 == EObjectPlacingResult::SEALED_OFF))
-								{
-									//sealed-off tiles were blocked, exit inner loop and get another tile set
-									continueOuterLoop = true;
-									break;
-								}
-								else
-									continue; //try with another position
-							}
-						}
-					}
-				}
-				if (!continueOuterLoop) //we didn't find ANY tile - break outer loop
-					break;
-			}
-			if (!guardPos.valid()) //cleanup? is this safe / enough?
-			{
-				delete gate1;
-				delete gate2;
-			}
-		}
-		if (!guardPos.valid())
-		{
-			auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, getNextMonlithIndex());
-			auto teleport1 = factory->create(ObjectTemplate());
-			auto teleport2 = factory->create(ObjectTemplate());
-
-			zoneA->addRequiredObject(teleport1, strength);
-			zoneB->addRequiredObject(teleport2, strength);
+			if(auto * m = zoneA->getModificator<TreasurePlacer>())
+				zoneA->getModificator<TreasurePlacer>()->setQuestArtZone(zoneB.get());
 		}
 	}
 }
 
 void CMapGenerator::addHeaderInfo()
 {
-	map->version = EMapFormat::VCMI;
-	map->width = mapGenOptions.getWidth();
-	map->height = mapGenOptions.getHeight();
-	map->twoLevel = mapGenOptions.getHasTwoLevels();
-	map->name = VLC->generaltexth->allTexts[740];
-	map->description = getMapDescription();
-	map->difficulty = 1;
+	map->map().version = EMapFormat::VCMI;
+	map->map().width = mapGenOptions.getWidth();
+	map->map().height = mapGenOptions.getHeight();
+	map->map().twoLevel = mapGenOptions.getHasTwoLevels();
+	map->map().name = VLC->generaltexth->allTexts[740];
+	map->map().description = getMapDescription();
+	map->map().difficulty = 1;
 	addPlayerInfo();
 }
 
-void CMapGenerator::checkIsOnMap(const int3& tile) const
-{
-	if (!map->isInTheMap(tile))
-		throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile.toString()));
-}
-
-
-CMapGenerator::Zones & CMapGenerator::getZones()
-{
-	return zones;
-}
-
-bool CMapGenerator::isBlocked(const int3 &tile) const
-{
-	checkIsOnMap(tile);
-
-	return tiles[tile.x][tile.y][tile.z].isBlocked();
-}
-bool CMapGenerator::shouldBeBlocked(const int3 &tile) const
-{
-	checkIsOnMap(tile);
-
-	return tiles[tile.x][tile.y][tile.z].shouldBeBlocked();
-}
-bool CMapGenerator::isPossible(const int3 &tile) const
-{
-	checkIsOnMap(tile);
-
-	return tiles[tile.x][tile.y][tile.z].isPossible();
-}
-bool CMapGenerator::isFree(const int3 &tile) const
-{
-	checkIsOnMap(tile);
-
-	return tiles[tile.x][tile.y][tile.z].isFree();
-}
-bool CMapGenerator::isUsed(const int3 &tile) const
-{
-	checkIsOnMap(tile);
-
-	return tiles[tile.x][tile.y][tile.z].isUsed();
-}
-
-bool CMapGenerator::isRoad(const int3& tile) const
-{
-	checkIsOnMap(tile);
-
-	return tiles[tile.x][tile.y][tile.z].isRoad();
-}
-
-void CMapGenerator::setOccupied(const int3 &tile, ETileType::ETileType state)
-{
-	checkIsOnMap(tile);
-
-	tiles[tile.x][tile.y][tile.z].setOccupied(state);
-}
-
-void CMapGenerator::setRoad(const int3& tile, const std::string & roadType)
-{
-	checkIsOnMap(tile);
-
-	tiles[tile.x][tile.y][tile.z].setRoadType(roadType);
-}
-
-
-CTileInfo CMapGenerator::getTile(const int3& tile) const
-{
-	checkIsOnMap(tile);
-
-	return tiles[tile.x][tile.y][tile.z];
-}
-
-TRmgTemplateZoneId CMapGenerator::getZoneID(const int3& tile) const
-{
-	checkIsOnMap(tile);
-
-	return zoneColouring[tile.z][tile.x][tile.y];
-}
-
-void CMapGenerator::setZoneID(const int3& tile, TRmgTemplateZoneId zid)
-{
-	checkIsOnMap(tile);
-
-	zoneColouring[tile.z][tile.x][tile.y] = zid;
-}
-
-bool CMapGenerator::isAllowedSpell(SpellID sid) const
-{
-	assert(sid >= 0);
-	if (sid < map->allowedSpell.size())
-	{
-		return map->allowedSpell[sid];
-	}
-	else
-		return false;
-}
-
-void CMapGenerator::setNearestObjectDistance(int3 &tile, float value)
-{
-	checkIsOnMap(tile);
-
-	tiles[tile.x][tile.y][tile.z].setNearestObjectDistance(value);
-}
-
-float CMapGenerator::getNearestObjectDistance(const int3 &tile) const
-{
-	checkIsOnMap(tile);
-
-	return tiles[tile.x][tile.y][tile.z].getNearestObjectDistance();
-}
-
 int CMapGenerator::getNextMonlithIndex()
 {
 	if (monolithIndex >= VLC->objtypeh->knownSubObjects(Obj::MONOLITH_TWO_WAY).size())
@@ -961,76 +359,27 @@ int CMapGenerator::getPrisonsRemaning() const
 {
 	return prisonsRemaining;
 }
+
 void CMapGenerator::decreasePrisonsRemaining()
 {
 	prisonsRemaining = std::max (0, prisonsRemaining - 1);
 }
 
-std::vector<ArtifactID> CMapGenerator::getQuestArtsRemaning() const
+const std::vector<ArtifactID> & CMapGenerator::getQuestArtsRemaning() const
 {
 	return questArtifacts;
 }
-void CMapGenerator::banQuestArt(ArtifactID id)
-{
-	map->allowedArtifact[id] = false;
-	vstd::erase_if_present (questArtifacts, id);
-}
 
-void CMapGenerator::registerZone (TFaction faction)
-{
-	zonesPerFaction[faction]++;
-	zonesTotal++;
-}
-ui32 CMapGenerator::getZoneCount(TFaction faction)
-{
-	return zonesPerFaction[faction];
-}
-ui32 CMapGenerator::getTotalZoneCount() const
-{
-	return zonesTotal;
-}
-CMapGenerator::Zones::value_type CMapGenerator::getZoneWater() const
+void CMapGenerator::banQuestArt(ArtifactID id)
 {
-	return zoneWater;
+	map->map().allowedArtifact[id] = false;
+	vstd::erase_if_present(questArtifacts, id);
 }
 
-void CMapGenerator::dump(bool zoneId)
+Zone * CMapGenerator::getZoneWater() const
 {
-	static int id = 0;
-	std::ofstream out(boost::to_string(boost::format("zone_%d.txt") % id++));
-	int levels = map->twoLevel ? 2 : 1;
-	int width =  map->width;
-	int height = map->height;
-	for (int k = 0; k < levels; k++)
-	{
-		for(int j=0; j<height; j++)
-		{
-			for (int i=0; i<width; i++)
-			{
-				if(zoneId)
-				{
-					out << getZoneID(int3(i, j, k));
-				}
-				else
-				{
-					char t = '?';
-					switch (getTile(int3(i, j, k)).getTileType())
-					{
-						case ETileType::FREE:
-							t = ' '; break;
-						case ETileType::BLOCKED:
-							t = '#'; break;
-						case ETileType::POSSIBLE:
-							t = '-'; break;
-						case ETileType::USED:
-							t = 'O'; break;
-					}
-					out << t;
-				}
-			}
-			out << std::endl;
-		}
-		out << std::endl;
-	}
-	out << std::endl;
+	for(auto & z : map->getZones())
+		if(z.second->getType() == ETemplateZoneType::WATER)
+			return z.second.get();
+	return nullptr;
 }

+ 8 - 73
lib/rmg/CMapGenerator.h

@@ -16,36 +16,15 @@
 #include "../int3.h"
 #include "CRmgTemplate.h"
 
-class CMap;
 class CRmgTemplate;
-class CRmgTemplateZone;
 class CMapGenOptions;
-class CTerrainViewPatternConfig;
-class CMapEditManager;
 class JsonNode;
-class CMapGenerator;
-class CTileInfo;
+class RmgMap;
+class CMap;
+class Zone;
 
 typedef std::vector<JsonNode> JsonVector;
 
-class rmgException : public std::exception
-{
-	std::string msg;
-public:
-	explicit rmgException(const std::string& _Message) : msg(_Message)
-	{
-	}
-
-	virtual ~rmgException() throw ()
-	{
-	};
-
-	const char *what() const throw () override
-	{
-		return msg.c_str();
-	}
-};
-
 /// The map generator creates a map randomly.
 class DLL_LINKAGE CMapGenerator
 {
@@ -57,7 +36,6 @@ public:
 		std::vector<CTreasureInfo> waterTreasure;
 		int shipyardGuard;
 		int mineExtraResources;
-		std::map<Res::ERes, int> mineValues;
 		int minGuardStrength;
 		std::string defaultRoadType;
 		int treasureValueLimit;
@@ -68,84 +46,44 @@ public:
 		std::vector<int> questValues, questRewardValues;
 	};
 	
-	using Zones = std::map<TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>>;
-
 	explicit CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed = std::time(nullptr));
 	~CMapGenerator(); // required due to std::unique_ptr
 	
 	const Config & getConfig() const;
 	
-	mutable std::unique_ptr<CMap> map;
 	CRandomGenerator rand;
 	
-	CMapEditManager* getEditManager() const;
 	const CMapGenOptions& getMapGenOptions() const;
 	
 	std::unique_ptr<CMap> generate();
 
-	Zones & getZones();
-	void createDirectConnections();
-	void createConnections2();
 	void findZonesForQuestArts();
-	void foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo);
-	void foreachDirectNeighbour(const int3 &pos, std::function<void(int3& pos)> foo);
-	void foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo);
-
-	bool isBlocked(const int3 &tile) const;
-	bool shouldBeBlocked(const int3 &tile) const;
-	bool isPossible(const int3 &tile) const;
-	bool isFree(const int3 &tile) const;
-	bool isUsed(const int3 &tile) const;
-	bool isRoad(const int3 &tile) const;
-
-	void setOccupied(const int3 &tile, ETileType::ETileType state);
-	void setRoad(const int3 &tile, const std::string & roadType);
-
-	CTileInfo getTile(const int3 & tile) const;
-	bool isAllowedSpell(SpellID sid) const;
-
-	float getNearestObjectDistance(const int3 &tile) const;
-	void setNearestObjectDistance(int3 &tile, float value);
 
 	int getNextMonlithIndex();
 	int getPrisonsRemaning() const;
 	void decreasePrisonsRemaining();
-	std::vector<ArtifactID> getQuestArtsRemaning() const;
+	const std::vector<ArtifactID> & getQuestArtsRemaning() const;
 	void banQuestArt(ArtifactID id);
-
-	void registerZone (TFaction faction);
-	ui32 getZoneCount(TFaction faction);
-	ui32 getTotalZoneCount() const;
 	
-	Zones::value_type getZoneWater() const;
+	Zone * getZoneWater() const;
 	void createWaterTreasures();
-	void prepareWaterTiles();
 
-	TRmgTemplateZoneId getZoneID(const int3& tile) const;
-	void setZoneID(const int3& tile, TRmgTemplateZoneId zid);
+	int getRandomSeed() const;
 	
-	void dump(bool zoneId);
-
 private:
 	int randomSeed;
 	CMapGenOptions& mapGenOptions;
 	Config config;
+	std::unique_ptr<RmgMap> map;
 	
 	std::vector<rmg::ZoneConnection> connectionsLeft;
-	Zones zones;
-	std::map<TFaction, ui32> zonesPerFaction;
-	ui32 zonesTotal; //zones that have their main town only
 	
-	std::pair<Zones::key_type, Zones::mapped_type> zoneWater;
-
-	CTileInfo*** tiles;
-	boost::multi_array<TRmgTemplateZoneId, 3> zoneColouring; //[z][x][y]
+	//std::pair<Zones::key_type, Zones::mapped_type> zoneWater;
 
 	int prisonsRemaining;
 	//int questArtsRemaining;
 	int monolithIndex;
 	std::vector<ArtifactID> questArtifacts;
-	void checkIsOnMap(const int3 &tile) const; //throws
 
 	/// Generation methods
 	void loadConfig();
@@ -156,10 +94,7 @@ private:
 	void initQuestArtsRemaining();
 	void addPlayerInfo();
 	void addHeaderInfo();
-	void initTiles();
 	void genZones();
 	void fillZones();
-	void createObstaclesCommon1();
-	void createObstaclesCommon2();
 
 };

+ 30 - 0
lib/rmg/CRmgTemplate.cpp

@@ -252,6 +252,11 @@ void ZoneOptions::setMonsterTypes(const std::set<TFaction> & value)
 	monsterTypes = value;
 }
 
+const std::set<TFaction> & ZoneOptions::getMonsterTypes() const
+{
+	return monsterTypes;
+}
+
 void ZoneOptions::setMinesInfo(const std::map<TResource, ui16> & value)
 {
 	mines = value;
@@ -302,6 +307,26 @@ std::vector<TRmgTemplateZoneId> ZoneOptions::getConnections() const
 	return connections;
 }
 
+bool ZoneOptions::areTownsSameType() const
+{
+	return townsAreSameType;
+}
+
+bool ZoneOptions::isMatchTerrainToTown() const
+{
+	return matchTerrainToTown;
+}
+
+const ZoneOptions::CTownInfo & ZoneOptions::getPlayerTowns() const
+{
+	return playerTowns;
+}
+
+const ZoneOptions::CTownInfo & ZoneOptions::getNeutralTowns() const
+{
+	return neutralTowns;
+}
+
 void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 {
 	static const std::vector<std::string> zoneTypes =
@@ -421,6 +446,11 @@ int ZoneConnection::getGuardStrength() const
 {
 	return guardStrength;
 }
+	
+bool operator==(const ZoneConnection & l, const ZoneConnection & r)
+{
+	return l.zoneA == r.zoneA && l.zoneB == r.zoneB && l.guardStrength == r.guardStrength;
+}
 
 void ZoneConnection::serializeJson(JsonSerializeFormat & handler)
 {

+ 10 - 3
lib/rmg/CRmgTemplate.h

@@ -17,7 +17,6 @@
 #include "CMapGenOptions.h"
 
 class JsonSerializeFormat;
-class Terrain;
 
 namespace ETemplateZoneType
 {
@@ -58,6 +57,8 @@ public:
 	int getGuardStrength() const;
 
 	void serializeJson(JsonSerializeFormat & handler);
+	
+	friend bool operator==(const ZoneConnection &, const ZoneConnection &);
 private:
 	TRmgTemplateZoneId zoneA;
 	TRmgTemplateZoneId zoneB;
@@ -105,8 +106,11 @@ public:
 	const std::set<Terrain> & getTerrainTypes() const;
 	void setTerrainTypes(const std::set<Terrain> & value);
 
+	const CTownInfo & getPlayerTowns() const;
+	const CTownInfo & getNeutralTowns() const;
 	std::set<TFaction> getDefaultTownTypes() const;
 	const std::set<TFaction> & getTownTypes() const;
+	const std::set<TFaction> & getMonsterTypes() const;
 
 	void setTownTypes(const std::set<TFaction> & value);
 	void setMonsterTypes(const std::set<TFaction> & value);
@@ -126,6 +130,11 @@ public:
 	std::vector<TRmgTemplateZoneId> getConnections() const;
 
 	void serializeJson(JsonSerializeFormat & handler);
+	
+	EMonsterStrength::EMonsterStrength zoneMonsterStrength;
+	
+	bool areTownsSameType() const;
+	bool isMatchTerrainToTown() const;
 
 protected:
 	TRmgTemplateZoneId id;
@@ -141,8 +150,6 @@ protected:
 	std::set<TFaction> townTypes;
 	std::set<TFaction> monsterTypes;
 
-	EMonsterStrength::EMonsterStrength zoneMonsterStrength;
-
 	std::map<TResource, ui16> mines; //obligatory mines to spawn in this zone
 
 	std::vector<CTreasureInfo> treasureInfo;

+ 0 - 3387
lib/rmg/CRmgTemplateZone.cpp

@@ -1,3387 +0,0 @@
-/*
- * CRmgTemplateZone.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 "CRmgTemplateZone.h"
-#include "../mapping/CMapEditManager.h"
-#include "../mapping/CMap.h"
-
-#include "../VCMI_Lib.h"
-#include "../CTownHandler.h"
-#include "../CCreatureHandler.h"
-#include "../spells/CSpellHandler.h" //for choosing random spells
-
-#include "../mapObjects/CommonConstructors.h"
-#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
-#include "../mapObjects/CGPandoraBox.h"
-#include "../mapObjects/CRewardableObject.h"
-
-class CMap;
-class CMapEditManager;
-//class CGObjectInstance;
-
-using namespace rmg; //TODO: move all to namespace
-
-
-
-void CRmgTemplateZone::addRoadNode(const int3& node)
-{
-	roadNodes.insert(node);
-}
-
-CTileInfo::CTileInfo():nearestObjectDistance(float(INT_MAX)), terrain()
-{
-	occupied = ETileType::POSSIBLE; //all tiles are initially possible to place objects or passages
-}
-
-float CTileInfo::getNearestObjectDistance() const
-{
-	return nearestObjectDistance;
-}
-
-void CTileInfo::setNearestObjectDistance(float value)
-{
-	nearestObjectDistance = std::max<float>(0, value); //never negative (or unitialized)
-}
-bool CTileInfo::shouldBeBlocked() const
-{
-	return occupied == ETileType::BLOCKED;
-}
-bool CTileInfo::isBlocked() const
-{
-	return occupied == ETileType::BLOCKED || occupied == ETileType::USED;
-}
-bool CTileInfo::isPossible() const
-{
-	return occupied == ETileType::POSSIBLE;
-}
-bool CTileInfo::isFree() const
-{
-	return occupied == ETileType::FREE;
-}
-
-bool CTileInfo::isRoad() const
-{
-	return roadType != ROAD_NAMES[0];
-}
-
-bool CTileInfo::isUsed() const
-{
-	return occupied == ETileType::USED;
-}
-void CTileInfo::setOccupied(ETileType::ETileType value)
-{
-	occupied = value;
-}
-
-ETileType::ETileType CTileInfo::getTileType() const
-{
-	return occupied;
-}
-
-Terrain CTileInfo::getTerrainType() const
-{
-	return terrain;
-}
-
-void CTileInfo::setTerrainType(Terrain value)
-{
-	terrain = value;
-}
-
-void CTileInfo::setRoadType(const std::string & value)
-{
-	roadType = value;
-//	setOccupied(ETileType::FREE);
-}
-
-
-CRmgTemplateZone::CRmgTemplateZone(CMapGenerator * Gen)
-	: ZoneOptions(),
-	townType(ETownType::NEUTRAL),
-	terrainType (Terrain("grass")),
-	minGuardedValue(0),
-	questArtZone(),
-	gen(Gen)
-{
-
-}
-
-bool CRmgTemplateZone::isUnderground() const
-{
-	return getPos().z;
-}
-
-void CRmgTemplateZone::setOptions(const ZoneOptions& options)
-{
-	ZoneOptions::operator=(options);
-}
-
-void CRmgTemplateZone::setQuestArtZone(std::shared_ptr<CRmgTemplateZone> otherZone)
-{
-	questArtZone = otherZone;
-}
-
-std::set<int3>* CRmgTemplateZone::getFreePaths()
-{
-	return &freePaths;
-}
-
-void CRmgTemplateZone::addFreePath(const int3 & p)
-{
-	gen->setOccupied(p, ETileType::FREE);
-	freePaths.insert(p);
-}
-
-float3 CRmgTemplateZone::getCenter() const
-{
-	return center;
-}
-void CRmgTemplateZone::setCenter(const float3 &f)
-{
-	//limit boundaries to (0,1) square
-
-	//alternate solution - wrap zone around unitary square. If it doesn't fit on one side, will come out on the opposite side
-	center = f;
-
-	center.x = static_cast<float>(std::fmod(center.x, 1));
-	center.y = static_cast<float>(std::fmod(center.y, 1));
-
-	if(center.x < 0) //fmod seems to work only for positive numbers? we want to stay positive
-		center.x = 1 - std::abs(center.x);
-	if(center.y < 0)
-		center.y = 1 - std::abs(center.y);
-}
-
-
-bool CRmgTemplateZone::pointIsIn(int x, int y)
-{
-	return true;
-}
-
-int3 CRmgTemplateZone::getPos() const
-{
-	return pos;
-}
-void CRmgTemplateZone::setPos(const int3 &Pos)
-{
-	pos = Pos;
-}
-
-void CRmgTemplateZone::addTile (const int3 &Pos)
-{
-	tileinfo.insert(Pos);
-}
-
-void CRmgTemplateZone::removeTile(const int3 & Pos)
-{
-	tileinfo.erase(Pos);
-	possibleTiles.erase(Pos);
-}
-
-std::set<int3> CRmgTemplateZone::getTileInfo () const
-{
-	return tileinfo;
-}
-std::set<int3> CRmgTemplateZone::getPossibleTiles() const
-{
-	return possibleTiles;
-}
-
-std::set<int3> CRmgTemplateZone::collectDistantTiles (float distance) const
-{
-	//TODO: mark tiles beyond zone as unavailable, but allow to connect with adjacent zones
-
-	//for (auto tile : tileinfo)
-	//{
-	//	if (tile.dist2d(this->pos) > distance)
-	//	{
-	//		gen->setOccupied(tile, ETileType::USED);
-	//		//gen->setOccupied(tile, ETileType::BLOCKED); //fixme: crash at rendering?
-	//	}
-	//}
-	std::set<int3> discardedTiles;
-	for(auto& tile : tileinfo)
-	{
-		if(tile.dist2d(this->pos) > distance)
-		{
-			discardedTiles.insert(tile);
-		}
-	};
-	return discardedTiles;
-}
-
-void CRmgTemplateZone::clearTiles()
-{
-	tileinfo.clear();
-}
-
-void CRmgTemplateZone::initFreeTiles ()
-{
-	vstd::copy_if(tileinfo, vstd::set_inserter(possibleTiles), [this](const int3 &tile) -> bool
-	{
-		return gen->isPossible(tile);
-	});
-	if(freePaths.empty())
-	{
-		addFreePath(pos); //zone must have at least one free tile where other paths go - for instance in the center
-	}
-}
-
-void CRmgTemplateZone::createBorder()
-{
-	for(auto tile : tileinfo)
-	{
-		bool edge = false;
-		gen->foreach_neighbour(tile, [this, &edge](int3 &pos)
-		{
-			if (edge)
-				return; //optimization - do it only once
-			if (gen->getZoneID(pos) != id) //optimization - better than set search
-			{
-				//bugfix with missing pos
-				if (gen->isPossible(pos))
-					gen->setOccupied(pos, ETileType::BLOCKED);
-				//we are edge if at least one tile does not belong to zone
-				//mark all nearby tiles blocked and we're done
-				gen->foreach_neighbour (pos, [this](int3 &nearbyPos)
-				{
-					if (gen->isPossible(nearbyPos))
-						gen->setOccupied(nearbyPos, ETileType::BLOCKED);
-				});
-				edge = true;
-			}
-		});
-	}
-}
-
-void CRmgTemplateZone::createWater(EWaterContent::EWaterContent waterContent, bool debug)
-{
-	if(waterContent == EWaterContent::NONE || isUnderground())
-		return; //do nothing
-	
-	std::set<int3> waterTiles = collectDistantTiles((float)(getSize() + 1));
-	
-	//add border tiles as water for ISLANDS
-	if(waterContent == EWaterContent::ISLANDS)
-	{
-		for(auto& tile : tileinfo)
-		{
-			if(gen->shouldBeBlocked(tile))
-			{
-				waterTiles.insert(tile);
-			}
-		}
-	}
-	
-	std::list<int3> tilesQueue(waterTiles.begin(), waterTiles.end()); //tiles need to be processed
-	std::set<int3> tilesChecked = waterTiles; //tiles already processed
-	std::map<int, std::set<int3>> coastTilesMap; //key: distance to water; value: tiles with that distance
-	std::map<int3, int> tilesDist; //key: tile; value: distance to water
-	
-	//optimization: prefill distance for all tiles marked for water with 0
-	for(auto& tile : waterTiles)
-	{
-		tilesDist[tile] = 0;
-	}
-	
-	//fills the distance-to-water map
-	while(!tilesQueue.empty())
-	{
-		int3 src = tilesQueue.front();
-		tilesQueue.pop_front();
-		gen->foreachDirectNeighbour(src, [this, &src, &tilesDist, &tilesChecked, &coastTilesMap, &tilesQueue](const int3 & dst)
-		{
-			if(tilesChecked.find(dst) != tilesChecked.end())
-				return;
-			
-			if(tileinfo.find(dst) != tileinfo.end())
-			{
-				tilesDist[dst] = tilesDist[src] + 1;
-				coastTilesMap[tilesDist[dst]].insert(dst);
-				tilesChecked.insert(dst);
-				tilesQueue.push_back(dst);
-			}
-		});
-	}
-	
-	//generating some irregularity of coast
-	int coastIdMax = fmin(sqrt(coastTilesMap.size()), 7.f); //size of coastTilesMap shows the most distant tile from water
-	assert(coastIdMax > 0);
-	tilesChecked.clear();
-	for(int coastId = coastIdMax; coastId >= 1; --coastId)
-	{
-		//amount of iterations shall be proportion of coast perimeter
-		const int coastLength = coastTilesMap[coastId].size() / (coastId + 3);
-		for(int coastIter = 0; coastIter < coastLength; ++coastIter)
-		{
-			int3 tile = *RandomGeneratorUtil::nextItem(coastTilesMap[coastId], gen->rand);
-			if(tilesChecked.find(tile) != tilesChecked.end())
-				continue;
-			if(gen->isUsed(tile) || gen->isFree(tile)) //prevent placing water nearby town
-				continue;
-			
-			tilesQueue.push_back(tile);
-			tilesChecked.insert(tile);
-		}
-	}
-	
-	//if tile is marked as water - connect it with "big" water
-	while(!tilesQueue.empty())
-	{
-		int3 src = tilesQueue.front();
-		tilesQueue.pop_front();
-		
-		if(waterTiles.find(src) != waterTiles.end())
-			continue;
-		
-		waterTiles.insert(src);
-
-		gen->foreach_neighbour(src, [&src, &tilesDist, &tilesChecked, &tilesQueue](const int3 & dst)
-		{
-			if(tilesChecked.find(dst) != tilesChecked.end())
-				return;
-			
-			if(tilesDist[dst] > 0 && tilesDist[src]-tilesDist[dst] == 1)
-			{
-				tilesQueue.push_back(dst);
-				tilesChecked.insert(dst);
-			}
-		});
-	}
-	
-	//start filtering of narrow places and coast atrifacts
-	std::vector<int3> waterAdd;
-	for(int coastId = 1; coastId <= coastIdMax; ++coastId)
-	{
-		for(auto& tile : coastTilesMap[coastId])
-		{
-			//collect neighbout water tiles
-			auto collectionLambda = [&waterTiles, &coastTilesMap](const int3 & t, std::set<int3> & outCollection)
-			{
-				if(waterTiles.find(t)!=waterTiles.end())
-				{
-					coastTilesMap[0].insert(t);
-					outCollection.insert(t);
-				}
-			};
-			std::set<int3> waterCoastDirect, waterCoastDiag;
-			gen->foreachDirectNeighbour(tile, std::bind(collectionLambda, std::placeholders::_1, std::ref(waterCoastDirect)));
-			gen->foreachDiagonalNeighbour(tile, std::bind(collectionLambda, std::placeholders::_1, std::ref(waterCoastDiag)));
-			int waterCoastDirectNum = waterCoastDirect.size();
-			int waterCoastDiagNum = waterCoastDiag.size();
-			
-			//remove tiles which are mostly covered by water
-			if(waterCoastDirectNum >= 3)
-			{
-				waterAdd.push_back(tile);
-				continue;
-			}
-			if(waterCoastDiagNum == 4 && waterCoastDirectNum == 2)
-			{
-				waterAdd.push_back(tile);
-				continue;
-			}
-			if(waterCoastDirectNum == 2 && waterCoastDiagNum >= 2)
-			{
-				int3 diagSum, dirSum;
-				for(auto & i : waterCoastDiag)
-					diagSum += i - tile;
-				for(auto & i : waterCoastDirect)
-					dirSum += i - tile;
-				if(diagSum == int3() || dirSum == int3())
-				{
-					waterAdd.push_back(tile);
-					continue;
-				}
-				if(waterCoastDiagNum == 3 && diagSum != dirSum)
-				{
-					waterAdd.push_back(tile);
-					continue;
-				}
-			}
-		}
-	}
-	for(auto & i : waterAdd)
-		waterTiles.insert(i);
-	
-	//filtering tiny "lakes"
-	for(auto& tile : coastTilesMap[0]) //now it's only coast-water tiles
-	{
-		if(waterTiles.find(tile) == waterTiles.end()) //for ground tiles
-			continue;
-		
-		std::vector<int3> groundCoast;
-		gen->foreachDirectNeighbour(tile, [this, &waterTiles, &groundCoast](const int3 & t)
-		{
-			if(waterTiles.find(t) == waterTiles.end() && tileinfo.find(t) != tileinfo.end()) //for ground tiles of same zone
-			{
-				groundCoast.push_back(t);
-			}
-		});
-		
-		if(groundCoast.size() >= 3)
-		{
-			waterTiles.erase(tile);
-		}
-		else
-		{
-			if(groundCoast.size() == 2)
-			{
-				if(groundCoast[0] + groundCoast[1] == int3())
-				{
-					waterTiles.erase(tile);
-				}
-			}
-			else
-			{
-				if(!groundCoast.empty())
-				{
-					coastTiles.insert(tile);
-				}
-			}
-		}
-	}
-	
-	//do not set water on tiles belong to other zones
-	vstd::erase_if(coastTiles, [&waterTiles](const int3 & tile)
-	{
-		return waterTiles.find(tile) == waterTiles.end();
-	});
-	
-	//transforming waterTiles to actual water
-	for(auto& tile : waterTiles)
-	{
-		gen->getZoneWater().second->addTile(tile);
-		gen->setZoneID(tile, gen->getZoneWater().first);
-		gen->setOccupied(tile, ETileType::POSSIBLE);
-		tileinfo.erase(tile);
-		possibleTiles.erase(tile);
-	}
-}
-
-void CRmgTemplateZone::waterInitFreeTiles()
-{
-	std::set<int3> tilesAll(tileinfo.begin(), tileinfo.end()); //water tiles
-	std::list<int3> tilesQueue; //tiles need to be processed
-	std::set<int3> tilesChecked;
-	
-	//lambda for increasing distance of negihbour tiles
-	auto lakeSearch = [this, &tilesAll, &tilesQueue](const int3 & dst)
-	{
-		if(tilesAll.find(dst) == tilesAll.end())
-		{
-			if(lakes.back().tiles.find(dst)==lakes.back().tiles.end())
-			{
-				//we reach land! let's store this information
-				assert(gen->getZoneID(dst) != gen->getZoneWater().first);
-				lakes.back().connectedZones.insert(gen->getZoneID(dst));
-				lakes.back().coast.insert(dst);
-				lakes.back().distance[dst] = 0;
-				return;
-			}
-		}
-		else
-		{
-			if(lakes.back().tiles.insert(dst).second)
-			{
-				tilesQueue.push_back(dst);
-			}
-		}
-	};
-	
-	while(!tilesAll.empty())
-	{
-		//add some random tile as initial
-		tilesQueue.push_back(*tilesAll.begin());
-		setPos(tilesQueue.front());
-		addFreePath(tilesQueue.front());
-		lakes.emplace_back();
-		lakes.back().tiles.insert(tilesQueue.front());
-		
-		//find lake
-		while(!tilesQueue.empty())
-		{
-			int3 tile = tilesQueue.front();
-			tilesQueue.pop_front();
-			gen->foreachDirectNeighbour(tile, lakeSearch);
-		}
-		
-		//fill distance map
-		tilesQueue.assign(lakes.back().coast.begin(), lakes.back().coast.end());
-		while(!tilesQueue.empty())
-		{
-			int3 src = tilesQueue.front();
-			tilesQueue.pop_front();
-			gen->foreachDirectNeighbour(src, [this, &src, &tilesChecked, &tilesQueue](const int3 & dst)
-			{
-				if(tilesChecked.find(dst) != tilesChecked.end())
-					return;
-				
-				if(lakes.back().tiles.find(dst) != lakes.back().tiles.end())
-				{
-					lakes.back().distance[dst] = lakes.back().distance[src] + 1;
-					tilesChecked.insert(dst);
-					tilesQueue.push_back(dst);
-				}
-			});
-		}
-		
-		//cleanup
-		int lakeIdx = lakes.size();
-		for(auto& t : lakes.back().tiles)
-		{
-			assert(lakeMap.find(t) == lakeMap.end());
-			lakeMap[t] = lakeIdx;
-			tilesAll.erase(t);
-		}
-	}
-	
-#ifdef _BETA
-	{
-		std::ofstream out1("lakes_id.txt");
-		std::ofstream out2("lakes_map.txt");
-		std::ofstream out3("lakes_dist.txt");
-		int levels = gen->map->twoLevel ? 2 : 1;
-		int width =  gen->map->width;
-		int height = gen->map->height;
-		for (int k = 0; k < levels; k++)
-		{
-			for(int j=0; j<height; j++)
-			{
-				for (int i=0; i<width; i++)
-				{
-					int3 tile{i,j,k};
-					if(lakeMap[tile]>9)
-						out1 << '#';
-					else
-						out1 << lakeMap[tile];
-					
-					bool found = false;
-					for(auto& lake : lakes)
-					{
-						if(lake.coast.count(tile))
-						{
-							out2 << '@';
-							out3 << lake.distance[tile];
-							found = true;
-						}
-						else if(lake.tiles.count(tile))
-						{
-							out2 << '~';
-							out3 << lake.distance[tile];
-							found = true;
-						}
-					}
-					if(!found)
-					{
-						out2 << ' ';
-						out3 << ' ';
-					}
-				}
-				out1 << std::endl;
-				out2 << std::endl;
-				out3 << std::endl;
-			}
-			out1 << std::endl;
-			out2 << std::endl;
-			out3 << std::endl;
-		}
-		out1 << std::endl;
-		out2 << std::endl;
-		out3 << std::endl;
-	}
-#endif
-}
-
-bool CRmgTemplateZone::waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB)
-{
-	for(auto & lake : lakes)
-	{
-		if(lake.connectedZones.count(zoneA) && lake.connectedZones.count(zoneB))
-		{
-			lake.keepConnections.insert(zoneA);
-			lake.keepConnections.insert(zoneB);
-			return true;
-		}
-	}
-	return false;
-}
-
-void CRmgTemplateZone::waterConnection(CRmgTemplateZone& dst)
-{
-	if(isUnderground() || dst.getCoastTiles().empty())
-		return;
-	
-	//block zones are not connected by template
-	for(auto& lake : lakes)
-	{
-		if(lake.connectedZones.count(dst.getId()))
-		{
-			if(!lake.keepConnections.count(dst.getId()))
-			{
-				for(auto & ct : lake.coast)
-				{
-					if(gen->getZoneID(ct) == dst.getId() && gen->isPossible(ct))
-						gen->setOccupied(ct, ETileType::BLOCKED);
-				}
-				continue;
-			}
-	
-			int3 coastTile(-1, -1, -1);
-			int zoneTowns = dst.playerTowns.getTownCount() + dst.playerTowns.getCastleCount() +
-							dst.neutralTowns.getTownCount() + dst.neutralTowns.getCastleCount();
-			
-			if(dst.getType() == ETemplateZoneType::PLAYER_START || dst.getType() == ETemplateZoneType::CPU_START || zoneTowns)
-			{
-				coastTile = dst.createShipyard(lake.tiles, gen->getConfig().shipyardGuard);
-				if(!coastTile.valid())
-				{
-					coastTile = makeBoat(dst.getId(), lake.tiles);
-				}
-			}
-			else
-			{
-				coastTile = makeBoat(dst.getId(), lake.tiles);
-			}
-				
-			if(coastTile.valid())
-			{
-				if(connectPath(coastTile, true))
-				{
-					addFreePath(coastTile);
-				}
-				else
-					logGlobal->error("Cannot build water route for zone %d", dst.getId());
-			}
-			else
-				logGlobal->error("No entry from water to zone %d", dst.getId());
-					
-		}
-	}
-}
-
-const std::set<int3>& CRmgTemplateZone::getCoastTiles() const
-{
-	return coastTiles;
-}
-
-bool CRmgTemplateZone::isWaterConnected(TRmgTemplateZoneId zone, const int3 & tile) const
-{
-	int lakeId = gen->getZoneWater().second->lakeMap.at(tile);
-	if(lakeId == 0)
-		return false;
-	
-	return  gen->getZoneWater().second->lakes.at(lakeId - 1).connectedZones.count(zone) &&
-			gen->getZoneWater().second->lakes.at(lakeId - 1).keepConnections.count(zone);
-}
-
-void CRmgTemplateZone::fractalize()
-{
-	for (auto tile : tileinfo)
-	{
-		if (gen->isFree(tile))
-			freePaths.insert(tile);
-	}
-	std::vector<int3> clearedTiles (freePaths.begin(), freePaths.end());
-	std::set<int3> possibleTiles;
-	std::set<int3> tilesToIgnore; //will be erased in this iteration
-
-	//the more treasure density, the greater distance between paths. Scaling is experimental.
-	int totalDensity = 0;
-	for(auto ti : treasureInfo)
-		totalDensity += ti.density;
-	const float minDistance = 10 * 10; //squared
-
-	for(auto tile : tileinfo)
-	{
-		if(gen->isPossible(tile))
-			possibleTiles.insert(tile);
-	}
-	assert (clearedTiles.size()); //this should come from zone connections
-
-	std::vector<int3> nodes; //connect them with a grid
-
-	if(type != ETemplateZoneType::JUNCTION)
-	{
-		//junction is not fractalized, has only one straight path
-		//everything else remains blocked
-		while(!possibleTiles.empty())
-		{
-			//link tiles in random order
-			std::vector<int3> tilesToMakePath(possibleTiles.begin(), possibleTiles.end());
-			RandomGeneratorUtil::randomShuffle(tilesToMakePath, gen->rand);
-
-			int3 nodeFound(-1, -1, -1);
-
-			for(auto tileToMakePath : tilesToMakePath)
-			{
-				//find closest free tile
-				float currentDistance = 1e10;
-				int3 closestTile(-1, -1, -1);
-
-				for(auto clearTile : clearedTiles)
-				{
-					float distance = static_cast<float>(tileToMakePath.dist2dSQ(clearTile));
-
-					if(distance < currentDistance)
-					{
-						currentDistance = distance;
-						closestTile = clearTile;
-					}
-					if(currentDistance <= minDistance)
-					{
-						//this tile is close enough. Forget about it and check next one
-						tilesToIgnore.insert(tileToMakePath);
-						break;
-					}
-				}
-				//if tiles is not close enough, make path to it
-				if (currentDistance > minDistance)
-				{
-					nodeFound = tileToMakePath;
-					nodes.push_back(nodeFound);
-					clearedTiles.push_back(nodeFound); //from now on nearby tiles will be considered handled
-					break; //next iteration - use already cleared tiles
-				}
-			}
-
-			for(auto tileToClear : tilesToIgnore)
-			{
-				//these tiles are already connected, ignore them
-				vstd::erase_if_present(possibleTiles, tileToClear);
-			}
-			if(!nodeFound.valid()) //nothing else can be done (?)
-				break;
-			tilesToIgnore.clear();
-		}
-	}
-
-	//cut straight paths towards the center. A* is too slow for that.
-	for (auto node : nodes)
-	{
-		auto subnodes = nodes;
-		boost::sort(subnodes, [&node](const int3& ourNode, const int3& otherNode) -> bool
-		{
-			return node.dist2dSQ(ourNode) < node.dist2dSQ(otherNode);
-		});
-
-		std::vector <int3> nearbyNodes;
-		if (subnodes.size() >= 2)
-		{
-			nearbyNodes.push_back(subnodes[1]); //node[0] is our node we want to connect
-		}
-		if (subnodes.size() >= 3)
-		{
-			nearbyNodes.push_back(subnodes[2]);
-		}
-
-		//connect with all the paths
-		crunchPath(node, findClosestTile(freePaths, node), true, &freePaths);
-		//connect with nearby nodes
-		for (auto nearbyNode : nearbyNodes)
-		{
-			crunchPath(node, nearbyNode, true, &freePaths); //do not allow to make another path network
-		}
-	}
-	for (auto node : nodes)
-		gen->setOccupied(node, ETileType::FREE); //make sure they are clear
-
-	//now block most distant tiles away from passages
-
-	float blockDistance = minDistance * 0.25f;
-
-	for (auto tile : tileinfo)
-	{
-		if(!gen->isPossible(tile))
-			continue;
-		
-		if(freePaths.count(tile))
-			continue;
-
-		bool closeTileFound = false;
-
-		for(auto clearTile : freePaths)
-		{
-			float distance = static_cast<float>(tile.dist2dSQ(clearTile));
-
-			if(distance < blockDistance)
-			{
-				closeTileFound = true;
-				break;
-			}
-		}
-		if (!closeTileFound) //this tile is far enough from passages
-			gen->setOccupied(tile, ETileType::BLOCKED);
-	}
-
-	#define PRINT_FRACTALIZED_MAP false
-	if (PRINT_FRACTALIZED_MAP) //enable to debug
-	{
-		std::ofstream out(boost::to_string(boost::format("zone_%d.txt") % id));
-		int levels = gen->map->twoLevel ? 2 : 1;
-		int width =  gen->map->width;
-		int height = gen->map->height;
-		for (int k = 0; k < levels; k++)
-		{
-			for(int j=0; j<height; j++)
-			{
-				for (int i=0; i<width; i++)
-				{
-					char t = '?';
-					switch (gen->getTile(int3(i, j, k)).getTileType())
-					{
-						case ETileType::FREE:
-							t = ' '; break;
-						case ETileType::BLOCKED:
-							t = '#'; break;
-						case ETileType::POSSIBLE:
-							t = '-'; break;
-						case ETileType::USED:
-							t = 'O'; break;
-					}
-
-					out << t;
-				}
-				out << std::endl;
-			}
-			out << std::endl;
-		}
-		out << std::endl;
-	}
-}
-
-void CRmgTemplateZone::connectLater()
-{
-	for (const int3 & node : tilesToConnectLater)
-	{
-		if (!connectWithCenter(node, true))
-			logGlobal->error("Failed to connect node %s with center of the zone", node.toString());
-	}
-}
-
-bool CRmgTemplateZone::crunchPath(const int3 &src, const int3 &dst, bool onlyStraight, std::set<int3>* clearedTiles)
-{
-/*
-make shortest path with free tiles, reachning dst or closest already free tile. Avoid blocks.
-do not leave zone border
-*/
-	bool result = false;
-	bool end = false;
-
-	int3 currentPos = src;
-	float distance = static_cast<float>(currentPos.dist2dSQ (dst));
-
-	while (!end)
-	{
-		if (currentPos == dst)
-		{
-			result = true;
-			break;
-		}
-
-		auto lastDistance = distance;
-
-		auto processNeighbours = [this, &currentPos, dst, &distance, &result, &end, clearedTiles](int3 &pos)
-		{
-			if (!result) //not sure if lambda is worth it...
-			{
-				if (pos == dst)
-				{
-					result = true;
-					end = true;
-				}
-				if (pos.dist2dSQ (dst) < distance)
-				{
-					if (!gen->isBlocked(pos))
-					{
-						if (gen->getZoneID(pos) == id)
-						{
-							if (gen->isPossible(pos))
-							{
-								gen->setOccupied (pos, ETileType::FREE);
-								if (clearedTiles)
-									clearedTiles->insert(pos);
-								currentPos = pos;
-								distance = static_cast<float>(currentPos.dist2dSQ (dst));
-							}
-							else if (gen->isFree(pos))
-							{
-								end = true;
-								result = true;
-							}
-						}
-					}
-				}
-			}
-		};
-
-		if (onlyStraight)
-			gen->foreachDirectNeighbour (currentPos, processNeighbours);
-		else
-			gen->foreach_neighbour (currentPos,processNeighbours);
-
-		int3 anotherPos(-1, -1, -1);
-
-		if (!(result || distance < lastDistance)) //we do not advance, use more advanced pathfinding algorithm?
-		{
-			//try any nearby tiles, even if its not closer than current
-			float lastDistance = 2 * distance; //start with significantly larger value
-
-			auto processNeighbours2 = [this, &currentPos, dst, &lastDistance, &anotherPos, clearedTiles](int3 &pos)
-			{
-				if (currentPos.dist2dSQ(dst) < lastDistance) //try closest tiles from all surrounding unused tiles
-				{
-					if (gen->getZoneID(pos) == id)
-					{
-						if (gen->isPossible(pos))
-						{
-							if (clearedTiles)
-								clearedTiles->insert(pos);
-							anotherPos = pos;
-							lastDistance = static_cast<float>(currentPos.dist2dSQ(dst));
-						}
-					}
-				}
-			};
-			if (onlyStraight)
-				gen->foreachDirectNeighbour(currentPos, processNeighbours2);
-			else
-				gen->foreach_neighbour(currentPos, processNeighbours2);
-
-
-			if (anotherPos.valid())
-			{
-				if (clearedTiles)
-					clearedTiles->insert(anotherPos);
-				gen->setOccupied(anotherPos, ETileType::FREE);
-				currentPos = anotherPos;
-			}
-		}
-		if (!(result || distance < lastDistance || anotherPos.valid()))
-		{
-			//FIXME: seemingly this condition is messed up, tells nothing
-			//logGlobal->warn("No tile closer than %s found on path from %s to %s", currentPos, src , dst);
-			break;
-		}
-	}
-
-	return result;
-}
-boost::heap::priority_queue<CRmgTemplateZone::TDistance, boost::heap::compare<CRmgTemplateZone::NodeComparer>> CRmgTemplateZone::createPriorityQueue()
-{
-	return boost::heap::priority_queue<TDistance, boost::heap::compare<NodeComparer>>();
-}
-
-bool CRmgTemplateZone::createRoad(const int3& src, const int3& dst)
-{
-	//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
-
-	std::set<int3> closed;    // The set of nodes already evaluated.
-	auto pq = createPriorityQueue();    // The set of tentative nodes to be evaluated, initially containing the start node
-	std::map<int3, int3> cameFrom;  // The map of navigated nodes.
-	std::map<int3, float> distances;
-
-	gen->setRoad (src, ROAD_NAMES[0]); //just in case zone guard already has road under it. Road under nodes will be added at very end
-
-	cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition
-	pq.push(std::make_pair(src, 0.f));
-	distances[src] = 0.f;
-	// Cost from start along best known path.
-
-	while (!pq.empty())
-	{
-		auto node = pq.top();
-		pq.pop(); //remove top element
-		int3 currentNode = node.first;
-		closed.insert (currentNode);
-		auto currentTile = &gen->map->getTile(currentNode);
-
-		if (currentNode == dst || gen->isRoad(currentNode))
-		{
-			// The goal node was reached. Trace the path using
-			// the saved parent information and return path
-			int3 backTracking = currentNode;
-			while (cameFrom[backTracking].valid())
-			{
-				// add node to path
-				roads.insert(backTracking);
-				gen->setRoad(backTracking, gen->getConfig().defaultRoadType);
-				//logGlobal->trace("Setting road at tile %s", backTracking);
-				// do the same for the predecessor
-				backTracking = cameFrom[backTracking];
-			}
-			return true;
-		}
-		else
-		{
-			bool directNeighbourFound = false;
-			float movementCost = 1;
-
-			auto foo = [this, &pq, &distances, &closed, &cameFrom, &currentNode, &currentTile, &node, &dst, &directNeighbourFound, &movementCost](int3& pos) -> void
-			{
-				if (vstd::contains(closed, pos)) //we already visited that node
-					return;
-				float distance = node.second + movementCost;
-				float bestDistanceSoFar = std::numeric_limits<float>::max();
-				auto it = distances.find(pos);
-				if (it != distances.end())
-					bestDistanceSoFar = it->second;
-
-				if (distance < bestDistanceSoFar)
-				{
-					auto tile = &gen->map->getTile(pos);
-					bool canMoveBetween = gen->map->canMoveBetween(currentNode, pos);
-
-					if ((gen->isFree(pos) && gen->isFree(currentNode)) //empty path
-						|| ((tile->visitable || currentTile->visitable) && canMoveBetween) //moving from or to visitable object
-						|| pos == dst) //we already compledted the path
-					{
-						if (gen->getZoneID(pos) == id || pos == dst) //otherwise guard position may appear already connected to other zone.
-						{
-							cameFrom[pos] = currentNode;
-							distances[pos] = distance;
-							pq.push(std::make_pair(pos, distance));
-							directNeighbourFound = true;
-						}
-					}
-				}
-			};
-
-			gen->foreachDirectNeighbour (currentNode, foo); // roads cannot be rendered correctly for diagonal directions
-			if (!directNeighbourFound)
-			{
-				movementCost = 2.1f; //moving diagonally is penalized over moving two tiles straight
-				gen->foreachDiagonalNeighbour(currentNode, foo);
-			}
-		}
-
-	}
-	logGlobal->warn("Failed to create road from %s to %s", src.toString(), dst.toString());
-	return false;
-
-}
-
-bool CRmgTemplateZone::connectPath(const int3& src, bool onlyStraight)
-///connect current tile to any other free tile within zone
-{
-	//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
-
-	std::set<int3> closed;    // The set of nodes already evaluated.
-	auto open = createPriorityQueue();    // The set of tentative nodes to be evaluated, initially containing the start node
-	std::map<int3, int3> cameFrom;  // The map of navigated nodes.
-	std::map<int3, float> distances;
-
-	//int3 currentNode = src;
-
-	cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition
-	distances[src] = 0.f;
-	open.push(std::make_pair(src, 0.f));
-	// Cost from start along best known path.
-	// Estimated total cost from start to goal through y.
-
-	while (!open.empty())
-	{
-		auto node = open.top();
-		open.pop();
-		int3 currentNode = node.first;
-
-		closed.insert(currentNode);
-
-		if (gen->isFree(currentNode)) //we reached free paths, stop
-		{
-			// Trace the path using the saved parent information and return path
-			int3 backTracking = currentNode;
-			while (cameFrom[backTracking].valid())
-			{
-				gen->setOccupied(backTracking, ETileType::FREE);
-				backTracking = cameFrom[backTracking];
-			}
-			return true;
-		}
-		else
-		{
-			auto foo = [this, &open, &closed, &cameFrom, &currentNode, &distances](int3& pos) -> void
-			{
-				if (vstd::contains(closed, pos))
-					return;
-
-				//no paths through blocked or occupied tiles, stay within zone
-				if (gen->isBlocked(pos) || gen->getZoneID(pos) != id)
-					return;
-
-				int distance = static_cast<int>(distances[currentNode]) + 1;
-				int bestDistanceSoFar = std::numeric_limits<int>::max();
-				auto it = distances.find(pos);
-				if (it != distances.end())
-					bestDistanceSoFar = static_cast<int>(it->second);
-
-				if (distance < bestDistanceSoFar)
-				{
-					cameFrom[pos] = currentNode;
-					open.push(std::make_pair(pos, (float)distance));
-					distances[pos] = static_cast<float>(distance);
-				}
-			};
-
-			if (onlyStraight)
-				gen->foreachDirectNeighbour(currentNode, foo);
-			else
-				gen->foreach_neighbour(currentNode, foo);
-		}
-
-	}
-	for (auto tile : closed) //these tiles are sealed off and can't be connected anymore
-	{
-		if(gen->isPossible(tile))
-			gen->setOccupied (tile, ETileType::BLOCKED);
-		vstd::erase_if_present(possibleTiles, tile);
-	}
-	return false;
-}
-
-bool CRmgTemplateZone::connectWithCenter(const int3& src, bool onlyStraight, bool passThroughBlocked)
-///connect current tile to any other free tile within zone
-{
-	//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
-
-	std::set<int3> closed;    // The set of nodes already evaluated.
-	auto open = createPriorityQueue(); // The set of tentative nodes to be evaluated, initially containing the start node
-	std::map<int3, int3> cameFrom;  // The map of navigated nodes.
-	std::map<int3, float> distances;
-
-	cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition
-	distances[src] = 0;
-	open.push(std::make_pair(src, 0.f));
-	// Cost from start along best known path.
-
-	while (!open.empty())
-	{
-		auto node = open.top();
-		open.pop();
-		int3 currentNode = node.first;
-
-		closed.insert(currentNode);
-
-		if (currentNode == pos) //we reached center of the zone, stop
-		{
-			// Trace the path using the saved parent information and return path
-			int3 backTracking = currentNode;
-			while (cameFrom[backTracking].valid())
-			{
-				gen->setOccupied(backTracking, ETileType::FREE);
-				backTracking = cameFrom[backTracking];
-			}
-			return true;
-		}
-		else
-		{
-			auto foo = [this, &open, &closed, &cameFrom, &currentNode, &distances, passThroughBlocked](int3& pos) -> void
-			{
-				if (vstd::contains(closed, pos))
-					return;
-
-				if (gen->getZoneID(pos) != id)
-					return;
-
-				float movementCost = 0;
-				
-				if (gen->isFree(pos))
-					movementCost = 1;
-				else if (gen->isPossible(pos))
-					movementCost = 2;
-				else if(passThroughBlocked && gen->shouldBeBlocked(pos))
-					movementCost = 3;
-				else
-					return;
-
-				float distance = distances[currentNode] + movementCost; //we prefer to use already free paths
-				int bestDistanceSoFar = std::numeric_limits<int>::max(); //FIXME: boost::limits
-				auto it = distances.find(pos);
-				if (it != distances.end())
-					bestDistanceSoFar = static_cast<int>(it->second);
-
-				if (distance < bestDistanceSoFar)
-				{
-					cameFrom[pos] = currentNode;
-					open.push(std::make_pair(pos, distance));
-					distances[pos] = distance;
-				}
-			};
-
-			if (onlyStraight)
-				gen->foreachDirectNeighbour(currentNode, foo);
-			else
-				gen->foreach_neighbour(currentNode, foo);
-		}
-
-	}
-	return false;
-}
-
-
-void CRmgTemplateZone::addRequiredObject(CGObjectInstance * obj, si32 strength)
-{
-	requiredObjects.push_back(std::make_pair(obj, strength));
-}
-void CRmgTemplateZone::addCloseObject(CGObjectInstance * obj, si32 strength)
-{
-	closeObjects.push_back(std::make_pair(obj, strength));
-}
-void CRmgTemplateZone::addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget)
-{
-	nearbyObjects.push_back(std::make_pair(obj, nearbyTarget));
-}
-void CRmgTemplateZone::addObjectAtPosition(CGObjectInstance * obj, const int3 & position, si32 strength)
-{
-	//TODO: use strength
-	instantObjects.push_back(std::make_pair(obj, position));
-}
-
-void CRmgTemplateZone::addToConnectLater(const int3& src)
-{
-	tilesToConnectLater.insert(src);
-}
-
-int CRmgTemplateZone::chooseRandomAppearance(si32 ObjID) const
-{
-	auto factories = VLC->objtypeh->knownSubObjects(ObjID);
-	vstd::erase_if(factories, [this, ObjID](si32 f)
-	{
-		return VLC->objtypeh->getHandlerFor(ObjID, f)->getTemplates(terrainType).empty();
-	});
-	
-	return *RandomGeneratorUtil::nextItem(factories, gen->rand);
-}
-
-bool CRmgTemplateZone::addMonster(int3 &pos, si32 strength, bool clearSurroundingTiles, bool zoneGuard)
-{
-	//precalculate actual (randomized) monster strength based on this post
-	//http://forum.vcmi.eu/viewtopic.php?p=12426#12426
-
-	int mapMonsterStrength = gen->getMapGenOptions().getMonsterStrength();
-	int monsterStrength = (zoneGuard ? 0 : zoneMonsterStrength) + mapMonsterStrength - 1; //array index from 0 to 4
-	static const int value1[] = {2500, 1500, 1000, 500, 0};
-	static const int value2[] = {7500, 7500, 7500, 5000, 5000};
-	static const float multiplier1[] = {0.5, 0.75, 1.0, 1.5, 1.5};
-	static const float multiplier2[] = {0.5, 0.75, 1.0, 1.0, 1.5};
-
-	int strength1 = static_cast<int>(std::max(0.f, (strength - value1[monsterStrength]) * multiplier1[monsterStrength]));
-	int strength2 = static_cast<int>(std::max(0.f, (strength - value2[monsterStrength]) * multiplier2[monsterStrength]));
-
-	strength = strength1 + strength2;
-	if (strength < gen->getConfig().minGuardStrength)
-		return false; //no guard at all
-
-	CreatureID creId = CreatureID::NONE;
-	int amount = 0;
-	std::vector<CreatureID> possibleCreatures;
-	for (auto cre : VLC->creh->objects)
-	{
-		if (cre->special)
-			continue;
-		if (!cre->AIValue) //bug #2681
-			continue;
-		if (!vstd::contains(monsterTypes, cre->faction))
-			continue;
-		if (((si32)(cre->AIValue * (cre->ammMin + cre->ammMax) / 2) < strength) && (strength < (si32)cre->AIValue * 100)) //at least one full monster. size between average size of given stack and 100
-		{
-			possibleCreatures.push_back(cre->idNumber);
-		}
-	}
-	if (possibleCreatures.size())
-	{
-		creId = *RandomGeneratorUtil::nextItem(possibleCreatures, gen->rand);
-		amount = strength / VLC->creh->objects[creId]->AIValue;
-		if (amount >= 4)
-			amount = static_cast<int>(amount * gen->rand.nextDouble(0.75, 1.25));
-	}
-	else //just pick any available creature
-	{
-		creId = CreatureID(132); //Azure Dragon
-		amount = strength / VLC->creh->objects[creId]->AIValue;
-	}
-
-	auto guardFactory = VLC->objtypeh->getHandlerFor(Obj::MONSTER, creId);
-
-	auto guard = (CGCreature *) guardFactory->create(ObjectTemplate());
-	guard->character = CGCreature::HOSTILE;
-	auto  hlp = new CStackInstance(creId, amount);
-	//will be set during initialization
-	guard->putStack(SlotID(0), hlp);
-
-	placeObject(guard, pos);
-
-	if (clearSurroundingTiles)
-	{
-		//do not spawn anything near monster
-		gen->foreach_neighbour (pos, [this](int3 pos)
-		{
-			if (gen->isPossible(pos))
-				gen->setOccupied(pos, ETileType::FREE);
-		});
-	}
-
-	return true;
-}
-
-bool CRmgTemplateZone::createTreasurePile(int3 &pos, float minDistance, const CTreasureInfo& treasureInfo)
-{
-	CTreasurePileInfo info;
-
-	std::map<int3, CGObjectInstance *> treasures;
-	std::set<int3> boundary;
-	int3 guardPos (-1,-1,-1);
-	info.nextTreasurePos = pos;
-
-	int maxValue = treasureInfo.max;
-	int minValue = treasureInfo.min;
-
-	ui32 desiredValue = (gen->rand.nextInt(minValue, maxValue));
-
-	int currentValue = 0;
-	CGObjectInstance * object = nullptr;
-	while (currentValue <= (int)desiredValue - 100) //no objects with value below 100 are available
-	{
-		treasures[info.nextTreasurePos] = nullptr;
-
-		for (auto treasurePos : treasures)
-		{
-			gen->foreach_neighbour(treasurePos.first, [&boundary](int3 pos)
-			{
-				boundary.insert(pos);
-			});
-		}
-		for (auto treasurePos : treasures)
-		{
-			//leaving only boundary around objects
-			vstd::erase_if_present(boundary, treasurePos.first);
-		}
-
-		for (auto tile : boundary)
-		{
-			//we can't extend boundary anymore
-			if (!(gen->isBlocked(tile) || gen->isPossible(tile)))
-				break;
-		}
-
-		ObjectInfo oi = getRandomObject(info, desiredValue, maxValue, currentValue);
-		if (!oi.value) //0 value indicates no object
-		{
-			vstd::erase_if_present(treasures, info.nextTreasurePos);
-			break;
-		}
-		else
-		{
-			object = oi.generateObject();
-			object->appearance = oi.templ;
-
-			//remove from possible objects
-			auto oiptr = std::find(possibleObjects.begin(), possibleObjects.end(), oi);
-			assert (oiptr != possibleObjects.end());
-			oiptr->maxPerZone--;
-			if (!oiptr->maxPerZone)
-				possibleObjects.erase(oiptr);
-
-			//update treasure pile area
-			int3 visitablePos = info.nextTreasurePos;
-
-			if (oi.templ.isVisitableFromTop())
-				info.visitableFromTopPositions.insert(visitablePos); //can be accessed from any direction
-			else
-				info.visitableFromBottomPositions.insert(visitablePos); //can be accessed only from bottom or side
-
-			for (auto blockedOffset : oi.templ.getBlockedOffsets())
-			{
-				int3 blockPos = info.nextTreasurePos + blockedOffset + oi.templ.getVisitableOffset(); //object will be moved to align vistable pos to treasure pos
-				info.occupiedPositions.insert(blockPos);
-				info.blockedPositions.insert(blockPos);
-			}
-			info.occupiedPositions.insert(visitablePos + oi.templ.getVisitableOffset());
-
-			currentValue += oi.value;
-
-			treasures[info.nextTreasurePos] = object;
-
-			//now find place for next object
-			int3 placeFound(-1,-1,-1);
-
-			//randomize next position from among possible ones
-			std::vector<int3> boundaryCopy (boundary.begin(), boundary.end());
-			//RandomGeneratorUtil::randomShuffle(boundaryCopy, gen->rand);
-			auto chooseTopTile = [](const int3 & lhs, const int3 & rhs) -> bool
-			{
-				return lhs.y < rhs.y;
-			};
-			boost::sort(boundaryCopy, chooseTopTile); //start from top tiles to allow objects accessible from bottom
-
-			for (auto tile : boundaryCopy)
-			{
-				if (gen->isPossible(tile) && gen->getZoneID(tile) == getId()) //we can place new treasure only on possible tile
-				{
-					bool here = true;
-					gen->foreach_neighbour (tile, [this, &here, minDistance](int3 pos)
-					{
-						if (!(gen->isBlocked(pos) || gen->isPossible(pos)) || gen->getZoneID(pos) != getId() || gen->getNearestObjectDistance(pos) < minDistance)
-							here = false;
-					});
-					if (here)
-					{
-						placeFound = tile;
-						break;
-					}
-				}
-			}
-			if (placeFound.valid())
-				info.nextTreasurePos = placeFound;
-			else
-				break; //no more place to add any objects
-		}
-	}
-
-	if (treasures.size())
-	{
-		//find object closest to free path, then connect it to the middle of the zone
-
-		int3 closestTile = int3(-1,-1,-1);
-		float minTreasureDistance = 1e10;
-
-		for (auto visitablePos : info.visitableFromBottomPositions) //objects that are not visitable from top must be accessible from bottom or side
-		{
-			int3 closestFreeTile = findClosestTile(freePaths, visitablePos);
-			if (closestFreeTile.dist2d(visitablePos) < minTreasureDistance)
-			{
-				closestTile = visitablePos + int3 (0, 1, 0); //start below object (y+1), possibly even outside the map, to not make path up through it
-				minTreasureDistance = static_cast<float>(closestFreeTile.dist2d(visitablePos));
-			}
-		}
-		for (auto visitablePos : info.visitableFromTopPositions) //all objects are accessible from any direction
-		{
-			int3 closestFreeTile = findClosestTile(freePaths, visitablePos);
-			if (closestFreeTile.dist2d(visitablePos) < minTreasureDistance)
-			{
-				closestTile = visitablePos;
-				minTreasureDistance = static_cast<float>(closestFreeTile.dist2d(visitablePos));
-			}
-		}
-		assert (closestTile.valid());
-
-		for (auto tile : info.occupiedPositions)
-		{
-			if (gen->map->isInTheMap(tile) && gen->isPossible(tile) && gen->getZoneID(tile)==id) //pile boundary may reach map border
-				gen->setOccupied(tile, ETileType::BLOCKED); //so that crunch path doesn't cut through objects
-		}
-
-		if (!connectPath (closestTile, false)) //this place is sealed off, need to find new position
-		{
-			return false;
-		}
-
-		//update boundary around our objects, including knowledge about objects visitable from bottom
-		boundary.clear();
-
-		for (auto tile : info.visitableFromBottomPositions)
-		{
-			gen->foreach_neighbour(tile, [tile, &boundary](int3 pos)
-			{
-				if (pos.y >= tile.y) //don't block these objects from above
-					boundary.insert(pos);
-			});
-		}
-		for (auto tile : info.visitableFromTopPositions)
-		{
-			gen->foreach_neighbour(tile, [&boundary](int3 pos)
-			{
-				boundary.insert(pos);
-			});
-		}
-
-		bool isPileGuarded = isGuardNeededForTreasure(currentValue);
-
-		for (auto tile : boundary) //guard must be standing there
-		{
-			if (gen->isFree(tile)) //this tile could be already blocked, don't place a monster here
-			{
-				guardPos = tile;
-				break;
-			}
-		}
-
-		if (guardPos.valid())
-		{
-			for (auto treasure : treasures)
-			{
-				int3 visitableOffset = treasure.second->getVisitableOffset();
-				placeObject(treasure.second, treasure.first + visitableOffset);
-			}
-			if (isPileGuarded && addMonster(guardPos, currentValue, false))
-			{//block only if the object is guarded
-				for (auto tile : boundary)
-				{
-					if (gen->isPossible(tile))
-						gen->setOccupied(tile, ETileType::BLOCKED);
-				}
-				//do not spawn anything near monster
-				gen->foreach_neighbour(guardPos, [this](int3 pos)
-				{
-					if (gen->isPossible(pos))
-						gen->setOccupied(pos, ETileType::FREE);
-				});
-			}
-		}
-		else if (isPileGuarded)//we couldn't make a connection to this location, block it
-		{
-			for (auto treasure : treasures)
-			{
-				if (gen->isPossible(treasure.first))
-					gen->setOccupied(treasure.first, ETileType::BLOCKED);
-
-				delete treasure.second;
-			}
-		}
-
-		return true;
-	}
-	else //we did not place eveyrthing successfully
-	{
-		if(gen->isPossible(pos))
-			gen->setOccupied(pos, ETileType::BLOCKED); //TODO: refactor stop condition
-		vstd::erase_if_present(possibleTiles, pos);
-		return false;
-	}
-}
-void CRmgTemplateZone::initTownType ()
-{
-	//FIXME: handle case that this player is not present -> towns should be set to neutral
-	int totalTowns = 0;
-
-	//cut a ring around town to ensure crunchPath always hits it.
-	auto cutPathAroundTown = [this](const CGTownInstance * town)
-	{
-		auto clearPos = [this](const int3 & pos)
-		{
-			if (gen->isPossible(pos))
-				gen->setOccupied(pos, ETileType::FREE);
-		};
-		for (auto blockedTile : town->getBlockedPos())
-		{
-			gen->foreach_neighbour(blockedTile, clearPos);
-		}
-		//clear town entry
-		gen->foreach_neighbour(town->visitablePos()+int3{0,1,0}, clearPos);
-	};
-
-	auto addNewTowns = [&totalTowns, this, &cutPathAroundTown](int count, bool hasFort, PlayerColor player)
-	{
-		for (int i = 0; i < count; i++)
-		{
-			si32 subType = townType;
-
-			if(totalTowns>0)
-			{
-				if(!this->townsAreSameType)
-				{
-					if (townTypes.size())
-						subType = *RandomGeneratorUtil::nextItem(townTypes, gen->rand);
-					else
-						subType = *RandomGeneratorUtil::nextItem(getDefaultTownTypes(), gen->rand); //it is possible to have zone with no towns allowed
-				}
-			}
-
-			auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, subType);
-			auto town = (CGTownInstance *) townFactory->create(ObjectTemplate());
-			town->ID = Obj::TOWN;
-
-			town->tempOwner = player;
-			if (hasFort)
-				town->builtBuildings.insert(BuildingID::FORT);
-			town->builtBuildings.insert(BuildingID::DEFAULT);
-
-			for(auto spell : VLC->spellh->objects) //add all regular spells to town
-			{
-				if(!spell->isSpecial() && !spell->isCreatureAbility())
-					town->possibleSpells.push_back(spell->id);
-			}
-
-			if (totalTowns <= 0)
-			{
-				//FIXME: discovered bug with small zones - getPos is close to map boarder and we have outOfMap exception
-				//register MAIN town of zone
-				gen->registerZone(town->subID);
-				//first town in zone goes in the middle
-				placeObject(town, getPos() + town->getVisitableOffset(), true);
-				cutPathAroundTown(town);
-				setPos(town->visitablePos()); //roads lead to mian town
-			}
-			else
-				addRequiredObject (town);
-			totalTowns++;
-		}
-	};
-
-
-	if ((type == ETemplateZoneType::CPU_START) || (type == ETemplateZoneType::PLAYER_START))
-	{
-		//set zone types to player faction, generate main town
-		logGlobal->info("Preparing playing zone");
-		int player_id = *owner - 1;
-		auto & playerInfo = gen->map->players[player_id];
-		PlayerColor player(player_id);
-		if (playerInfo.canAnyonePlay())
-		{
-			player = PlayerColor(player_id);
-			townType = gen->getMapGenOptions().getPlayersSettings().find(player)->second.getStartingTown();
-
-			if (townType == CMapGenOptions::CPlayerSettings::RANDOM_TOWN)
-				randomizeTownType(true);
-		}
-		else //no player - randomize town
-		{
-			player = PlayerColor::NEUTRAL;
-			randomizeTownType();
-		}
-
-		auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, townType);
-
-		CGTownInstance * town = (CGTownInstance *) townFactory->create(ObjectTemplate());
-		town->tempOwner = player;
-		town->builtBuildings.insert(BuildingID::FORT);
-		town->builtBuildings.insert(BuildingID::DEFAULT);
-
-		for(auto spell : VLC->spellh->objects) //add all regular spells to town
-		{
-			if(!spell->isSpecial() && !spell->isCreatureAbility())
-				town->possibleSpells.push_back(spell->id);
-		}
-		//towns are big objects and should be centered around visitable position
-		placeObject(town, getPos() + town->getVisitableOffset(), true);
-		cutPathAroundTown(town);
-		setPos(town->visitablePos()); //roads lead to mian town
-
-		totalTowns++;
-		//register MAIN town of zone only
-		gen->registerZone (town->subID);
-
-		if (playerInfo.canAnyonePlay()) //configure info for owning player
-		{
-			logGlobal->trace("Fill player info %d", player_id);
-
-			// Update player info
-			playerInfo.allowedFactions.clear();
-			playerInfo.allowedFactions.insert(townType);
-			playerInfo.hasMainTown = true;
-			playerInfo.posOfMainTown = town->pos;
-			playerInfo.generateHeroAtMainTown = true;
-
-			//now create actual towns
-			addNewTowns(playerTowns.getCastleCount() - 1, true, player);
-			addNewTowns(playerTowns.getTownCount(), false, player);
-		}
-		else
-		{
-			addNewTowns(playerTowns.getCastleCount() - 1, true, PlayerColor::NEUTRAL);
-			addNewTowns(playerTowns.getTownCount(), false, PlayerColor::NEUTRAL);
-		}
-	}
-	else //randomize town types for any other zones as well
-	{
-		randomizeTownType();
-	}
-
-	addNewTowns (neutralTowns.getCastleCount(), true, PlayerColor::NEUTRAL);
-	addNewTowns (neutralTowns.getTownCount(), false, PlayerColor::NEUTRAL);
-
-	if (!totalTowns) //if there's no town present, get random faction for dwellings and pandoras
-	{
-		//25% chance for neutral
-		if (gen->rand.nextInt(1, 100) <= 25)
-		{
-			townType = ETownType::NEUTRAL;
-		}
-		else
-		{
-			if (townTypes.size())
-				townType = *RandomGeneratorUtil::nextItem(townTypes, gen->rand);
-			else if (monsterTypes.size())
-				townType = *RandomGeneratorUtil::nextItem(monsterTypes, gen->rand); //this happens in Clash of Dragons in treasure zones, where all towns are banned
-			else //just in any case
-				randomizeTownType();
-		}
-	}
-}
-
-void CRmgTemplateZone::randomizeTownType(bool matchUndergroundType)
-{
-	auto townTypesAllowed = (townTypes.size() ? townTypes : getDefaultTownTypes());
-	if(matchUndergroundType && gen->getMapGenOptions().getHasTwoLevels())
-	{
-		std::set<TFaction> townTypesVerify;
-		for(TFaction factionIdx : townTypesAllowed)
-		{
-			bool preferUnderground = (*VLC->townh)[factionIdx]->preferUndergroundPlacement;
-			if(isUnderground() ? preferUnderground : !preferUnderground)
-			{
-				townTypesVerify.insert(factionIdx);
-			}
-		}
-		if(!townTypesVerify.empty())
-			townTypesAllowed = townTypesVerify;
-	}
-	
-	townType = *RandomGeneratorUtil::nextItem(townTypesAllowed, gen->rand);
-}
-
-void CRmgTemplateZone::initTerrainType ()
-{
-	if (type==ETemplateZoneType::WATER)
-	{
-		//collect all water terrain types
-		std::vector<Terrain> waterTerrains;
-		for(auto & terrain : Terrain::Manager::terrains())
-			if(terrain.isWater())
-				waterTerrains.push_back(terrain);
-		
-		terrainType = *RandomGeneratorUtil::nextItem(waterTerrains, gen->rand);
-	}
-	else
-	{
-		if (matchTerrainToTown && townType != ETownType::NEUTRAL)
-		{
-			terrainType = (*VLC->townh)[townType]->nativeTerrain;
-		}
-		else
-		{
-			terrainType = *RandomGeneratorUtil::nextItem(terrainTypes, gen->rand);
-		}
-
-		//TODO: allow new types of terrain?
-		{
-			if(isUnderground())
-			{
-				if(!vstd::contains(gen->getConfig().terrainUndergroundAllowed, terrainType))
-				{
-					//collect all underground terrain types
-					std::vector<Terrain> undegroundTerrains;
-					for(auto & terrain : Terrain::Manager::terrains())
-						if(terrain.isUnderground())
-							undegroundTerrains.push_back(terrain);
-					
-					terrainType = *RandomGeneratorUtil::nextItem(undegroundTerrains, gen->rand);
-				}
-			}
-			else
-			{
-				if(vstd::contains(gen->getConfig().terrainGroundProhibit, terrainType) || terrainType.isUnderground())
-					terrainType = Terrain("dirt");
-			}
-		}
-	}
-	paintZoneTerrain (terrainType);
-}
-
-void CRmgTemplateZone::paintZoneTerrain (Terrain terrainType)
-{
-	std::vector<int3> tiles(tileinfo.begin(), tileinfo.end());
-	gen->getEditManager()->getTerrainSelection().setSelection(tiles);
-	gen->getEditManager()->drawTerrain(terrainType, &gen->rand);
-}
-
-bool CRmgTemplateZone::placeMines ()
-{
-	using namespace Res;
-	std::vector<CGMine*> createdMines;
-	
-	for(const auto & mineInfo : mines)
-	{
-		ERes res = (ERes)mineInfo.first;
-		for(int i = 0; i < mineInfo.second; ++i)
-		{
-			auto mine = (CGMine*) VLC->objtypeh->getHandlerFor(Obj::MINE, res)->create(ObjectTemplate());
-			mine->producedResource = res;
-			mine->tempOwner = PlayerColor::NEUTRAL;
-			mine->producedQuantity = mine->defaultResProduction();
-			createdMines.push_back(mine);
-			
-			if(!i && (res == ERes::WOOD || res == ERes::ORE))
-				addCloseObject(mine, gen->getConfig().mineValues.at(res)); //only first wood&ore mines are close
-			else
-				addRequiredObject(mine, gen->getConfig().mineValues.at(res));
-		}
-	}
-	
-	//create extra resources
-	if(int extraRes = gen->getConfig().mineExtraResources)
-	{
-		for(auto * mine : createdMines)
-		{
-			for(int rc = gen->rand.nextInt(1, extraRes); rc > 0; --rc)
-			{
-				auto resourse = (CGResource*) VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create(ObjectTemplate());
-				resourse->amount = CGResource::RANDOM_AMOUNT;
-				addNearbyObject(resourse, mine);
-			}
-		}
-	}
-		
-
-	return true;
-}
-
-EObjectPlacingResult::EObjectPlacingResult CRmgTemplateZone::tryToPlaceObjectAndConnectToPath(CGObjectInstance * obj, const int3 & pos)
-{
-	//check if we can find a path around this object. Tiles will be set to "USED" after object is successfully placed.
-	obj->pos = pos;
-	gen->setOccupied(obj->visitablePos(), ETileType::BLOCKED);
-	for (auto tile : obj->getBlockedPos())
-	{
-		if (gen->map->isInTheMap(tile))
-			gen->setOccupied(tile, ETileType::BLOCKED);
-	}
-	int3 accessibleOffset = getAccessibleOffset(obj->appearance, pos);
-	if (!accessibleOffset.valid())
-	{
-		logGlobal->warn("Cannot access required object at position %s, retrying", pos.toString());
-		return EObjectPlacingResult::CANNOT_FIT;
-	}
-	if (!connectPath(accessibleOffset, true))
-	{
-		logGlobal->trace("Failed to create path to required object at position %s, retrying", pos.toString());
-		return EObjectPlacingResult::SEALED_OFF;
-	}
-	else
-		return EObjectPlacingResult::SUCCESS;
-}
-
-bool CRmgTemplateZone::createRequiredObjects()
-{
-	logGlobal->trace("Creating required objects");
-
-	for(const auto &object : requiredObjects)
-	{
-		auto obj = object.first;
-		if (!obj->appearance.canBePlacedAt(terrainType))
-			continue;
-		
-		int3 pos;
-		while (true)
-		{
-			if (!findPlaceForObject(obj, 3, pos))
-			{
-				logGlobal->error("Failed to fill zone %d due to lack of space", id);
-				return false;
-			}
-
-			if (tryToPlaceObjectAndConnectToPath(obj, pos) == EObjectPlacingResult::SUCCESS)
-			{
-				//paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones
-				placeObject(obj, pos);
-				guardObject(obj, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY), true);
-				break;
-			}
-		}
-	}
-
-	for (const auto &obj : closeObjects)
-	{
-		setTemplateForObject(obj.first);
-		if (!obj.first->appearance.canBePlacedAt(terrainType))
-			continue;
-		
-		auto tilesBlockedByObject = obj.first->getBlockedOffsets();
-
-		bool finished = false;
-		bool attempt = true;
-		while (!finished && attempt)
-		{
-			attempt = false;
-
-			std::vector<int3> tiles(possibleTiles.begin(), possibleTiles.end());
-			//new tiles vector after each object has been placed, OR misplaced area has been sealed off
-
-			boost::remove_if(tiles, [obj, this](int3 &tile)-> bool
-			{
-				//object must be accessible from at least one surounding tile
-				return !this->isAccessibleFromSomewhere(obj.first->appearance, tile);
-			});
-
-			auto targetPosition = requestedPositions.find(obj.first)!=requestedPositions.end() ? requestedPositions[obj.first] : pos;
-			// smallest distance to zone center, greatest distance to nearest object
-			auto isCloser = [this, &targetPosition, &tilesBlockedByObject](const int3 & lhs, const int3 & rhs) -> bool
-			{
-				float lDist = std::numeric_limits<float>::max();
-				float rDist = std::numeric_limits<float>::max();
-				for(int3 t : tilesBlockedByObject)
-				{
-					t += targetPosition;
-					lDist = fmin(lDist, static_cast<float>(t.dist2d(lhs)));
-					rDist = fmin(rDist, static_cast<float>(t.dist2d(rhs)));
-				}
-				lDist *= (lDist > 12) ? 10 : 1; //objects within 12 tile radius are preferred (smaller distance rating)
-				rDist *= (rDist > 12) ? 10 : 1;
-
-				return (lDist * 0.5f - std::sqrt(gen->getNearestObjectDistance(lhs))) < (rDist * 0.5f - std::sqrt(gen->getNearestObjectDistance(rhs)));
-			};
-			
-			boost::sort(tiles, isCloser);
-
-			if (tiles.empty())
-			{
-				logGlobal->error("Failed to fill zone %d due to lack of space", id);
-				return false;
-			}
-			for (auto tile : tiles)
-			{
-				//code partially adapted from findPlaceForObject()
-				if(!areAllTilesAvailable(obj.first, tile, tilesBlockedByObject))
-					continue;
-
-				attempt = true;
-
-				EObjectPlacingResult::EObjectPlacingResult result = tryToPlaceObjectAndConnectToPath(obj.first, tile);
-				if (result == EObjectPlacingResult::SUCCESS)
-				{
-					placeObject(obj.first, tile);
-					guardObject(obj.first, obj.second, (obj.first->ID == Obj::MONOLITH_TWO_WAY), true);
-					finished = true;
-					break;
-				}
-				else if (result == EObjectPlacingResult::CANNOT_FIT)
-					continue; // next tile
-				else if (result == EObjectPlacingResult::SEALED_OFF)
-				{
-					break; //tiles expired, pick new ones
-				}
-				else
-					throw (rmgException("Wrong result of tryToPlaceObjectAndConnectToPath()"));
-			}
-		}
-	}
-
-	//create nearby objects (e.g. extra resources close to mines)
-	for(const auto & object : nearbyObjects)
-	{
-		auto obj = object.first;
-		std::set<int3> possiblePositions;
-		for (auto blockedTile : object.second->getBlockedPos())
-		{
-			gen->foreachDirectNeighbour(blockedTile, [this, &possiblePositions](int3 pos)
-			{
-				if (!gen->isBlocked(pos) && tileinfo.count(pos))
-				{
-					//some resources still could be unaccessible, at least one free cell shall be
-					gen->foreach_neighbour(pos, [this, &possiblePositions, &pos](int3 p)
-												{
-						if(gen->isFree(p))
-							possiblePositions.insert(pos);
-					});
-				}
-			});
-		}
-
-		if(possiblePositions.empty())
-		{
-			delete obj; //is it correct way to prevent leak?
-		}
-		else
-		{
-			auto pos = *RandomGeneratorUtil::nextItem(possiblePositions, gen->rand);
-			placeObject(obj, pos);
-		}
-	}
-	
-	//create object on specific positions
-	//TODO: implement guards
-	for (const auto &obj : instantObjects)
-	{
-		if(tryToPlaceObjectAndConnectToPath(obj.first, obj.second)==EObjectPlacingResult::SUCCESS)
-		{
-			placeObject(obj.first, obj.second);
-			//TODO: guardObject(...)
-		}
-	}
-
-	requiredObjects.clear();
-	closeObjects.clear();
-	nearbyObjects.clear();
-	instantObjects.clear();
-
-	return true;
-}
-
-int3 CRmgTemplateZone::makeBoat(TRmgTemplateZoneId land, const std::set<int3> & lake)
-{
-	std::set<int3> lakeCoast;
-	std::set_intersection(gen->getZones()[land]->getCoastTiles().begin(), gen->getZones()[land]->getCoastTiles().end(), lake.begin(), lake.end(), std::inserter(lakeCoast, lakeCoast.begin()));
-
-	if(lakeCoast.empty())
-		return int3(-1, -1, -1);
-
-	for(int randomAttempts = 0; randomAttempts<5; ++randomAttempts)
-	{
-		auto coastTile = *RandomGeneratorUtil::nextItem(lakeCoast, gen->rand);
-		if(gen->getZoneID(coastTile) == gen->getZoneWater().first && isWaterConnected(land, coastTile) && makeBoat(land, coastTile))
-			return coastTile;
-	}
-	//if no success on random selection, use brute force
-	for(const auto& coastTile : lakeCoast)
-	{
-		if(gen->getZoneID(coastTile) == gen->getZoneWater().first && isWaterConnected(land, coastTile) && makeBoat(land, coastTile))
-			return coastTile;
-	}
-	return int3(-1,-1,-1);
-}
-
-bool CRmgTemplateZone::makeBoat(TRmgTemplateZoneId land, const int3 & coast)
-{
-	//verify coast
-	if(gen->getZoneWater().first != id)
-		throw rmgException("Cannot make a ship: not a water zone");
-	if(gen->getZoneID(coast) != id)
-		throw rmgException("Cannot make a ship: coast tile doesn't belong to water");
-	
-	//find zone for ship boarding
-	std::vector<int3> landTiles;
-	gen->foreach_neighbour(coast, [this, &landTiles, land](const int3 & t)
-	{
-		if(land == gen->getZoneID(t) && gen->isPossible(t))
-		{
-			landTiles.push_back(t);
-		}
-	});
-	
-	if(landTiles.empty())
-		return false;
-	
-	int3 landTile = {-1, -1, -1};
-	for(auto& lt : landTiles)
-	{
-		if(gen->getZones()[land]->connectPath(lt, false))
-		{
-			landTile = lt;
-			gen->setOccupied(landTile, ETileType::FREE);
-			break;
-		}
-	}
-	
-	if(!landTile.valid())
-		return false;
-	
-	auto subObjects = VLC->objtypeh->knownSubObjects(Obj::BOAT);
-	auto* boat = (CGBoat*)VLC->objtypeh->getHandlerFor(Obj::BOAT, *RandomGeneratorUtil::nextItem(subObjects, gen->rand))->create(ObjectTemplate());
-	
-	auto targetPos = boat->getVisitableOffset() + coast + int3{1, 0, 0}; //+1 offset for boat - bug?
-	if (gen->map->isInTheMap(targetPos) && gen->isPossible(targetPos) && gen->getZoneID(targetPos) == getId())
-	{
-		//don't connect to path because it's not initialized
-		addObjectAtPosition(boat, targetPos);
-		gen->setOccupied(targetPos, ETileType::USED);
-		return true;
-	}
-	
-	return false;
-}
-
-int3 CRmgTemplateZone::createShipyard(const std::set<int3> & lake, si32 guardStrength)
-{
-	std::set<int3> lakeCoast;
-	std::set_intersection(getCoastTiles().begin(), getCoastTiles().end(), lake.begin(), lake.end(), std::inserter(lakeCoast, lakeCoast.begin()));
-	for(int randomAttempts = 0; randomAttempts < 5; ++randomAttempts)
-	{
-		auto coastTile = *RandomGeneratorUtil::nextItem(lakeCoast, gen->rand);
-		if(gen->getZoneID(coastTile) == gen->getZoneWater().first && isWaterConnected(id, coastTile) && createShipyard(coastTile, guardStrength))
-			return coastTile;
-	}
-	//if no success on random selection, use brute force
-	for(const auto& coastTile : lakeCoast)
-	{
-		if(gen->getZoneID(coastTile) == gen->getZoneWater().first && isWaterConnected(id, coastTile) && createShipyard(coastTile, guardStrength))
-			return coastTile;
-	}
-	return int3(-1,-1,-1);
-}
-
-bool CRmgTemplateZone::createShipyard(const int3 & position, si32 guardStrength)
-{
-	int subtype = chooseRandomAppearance(Obj::SHIPYARD);
-	auto shipyard = (CGShipyard*) VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, subtype)->create(ObjectTemplate());
-	shipyard->tempOwner = PlayerColor::NEUTRAL;
-	
-	setTemplateForObject(shipyard);
-	std::vector<int3> outOffsets;
-	auto tilesBlockedByObject = shipyard->getBlockedOffsets();
-	tilesBlockedByObject.insert(shipyard->getVisitableOffset());
-	shipyard->getOutOffsets(outOffsets);
-	
-	int3 targetTile(-1, -1, -1);
-	std::set<int3> shipAccessCandidates;
-	
-	for(const auto & outOffset : outOffsets)
-	{
-		auto candidateTile = position - outOffset;
-		std::set<int3> tilesBlockedAbsolute;
-		
-		//check space under object
-		bool allClear = true;
-		for(const auto & objectTileOffset : tilesBlockedByObject)
-		{
-			auto objectTile = candidateTile + objectTileOffset;
-			tilesBlockedAbsolute.insert(objectTile);
-			if(!gen->map->isInTheMap(objectTile) || !gen->isPossible(objectTile) || gen->getZoneID(objectTile)!=id)
-			{
-				allClear = false;
-				break;
-			}
-		}
-		if(!allClear) //cannot place shipyard anyway
-			continue;
-		
-		//prepare temporary map
-		for(auto& blockedPos : tilesBlockedAbsolute)
-			gen->setOccupied(blockedPos, ETileType::USED);
-		
-		
-		//check if boarding position is accessible
-		gen->foreach_neighbour(position, [this, &shipAccessCandidates](const int3 & v)
-		{
-			if(!gen->isBlocked(v) && gen->getZoneID(v)==id)
-			{
-				//make sure that it's possible to create path to boarding position
-				if(connectWithCenter(v, false, false))
-					shipAccessCandidates.insert(v);
-			}
-		});
-		
-		//check if we can connect shipyard entrance with path
-		if(!connectWithCenter(candidateTile + shipyard->getVisitableOffset(), false))
-			shipAccessCandidates.clear();
-				
-		//rollback temporary map
-		for(auto& blockedPos : tilesBlockedAbsolute)
-			gen->setOccupied(blockedPos, ETileType::POSSIBLE);
-		
-		if(!shipAccessCandidates.empty() && isAccessibleFromSomewhere(shipyard->appearance, candidateTile))
-		{
-			targetTile = candidateTile;
-			break; //no need to check other offsets as we already found position
-		}
-		
-		shipAccessCandidates.clear(); //invalidate positions
-	}
-	
-	if(!targetTile.valid())
-	{
-		delete shipyard;
-		return false;
-	}
-	
-	if(tryToPlaceObjectAndConnectToPath(shipyard, targetTile)==EObjectPlacingResult::SUCCESS)
-	{
-		placeObject(shipyard, targetTile);
-		guardObject(shipyard, guardStrength, false, true);
-	
-		for(auto& accessPosition : shipAccessCandidates)
-		{
-			if(connectPath(accessPosition, false))
-			{
-				gen->setOccupied(accessPosition, ETileType::FREE);
-				return true;
-			}
-		}
-	}
-	
-	logGlobal->warn("Cannot find path to shipyard boarding position");
-	delete shipyard;
-	return false;
-}
-
-void CRmgTemplateZone::createTreasures()
-{
-	int mapMonsterStrength = gen->getMapGenOptions().getMonsterStrength();
-	int monsterStrength = zoneMonsterStrength + mapMonsterStrength - 1; //array index from 0 to 4
-
-	static int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 };
-	minGuardedValue = minGuardedValues[monsterStrength];
-
-	auto valueComparator = [](const CTreasureInfo & lhs, const CTreasureInfo & rhs) -> bool
-	{
-		return lhs.max > rhs.max;
-	};
-
-	//place biggest treasures first at large distance, place smaller ones inbetween
-	boost::sort(treasureInfo, valueComparator);
-
-	//sort treasures by ascending value so we can stop checking treasures with too high value
-	boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool
-	{
-		return oi1.value < oi2.value;
-	});
-
-	int totalDensity = 0;
-	for (auto t : treasureInfo)
-	{
-		//discard objects with too high value to be ever placed
-		vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool
-		{
-			return oi.value > t.max;
-		});
-
-		totalDensity += t.density;
-
-		//treasure density is inversely proportional to zone size but must be scaled back to map size
-		//also, normalize it to zone count - higher count means relatively smaller zones
-
-		//this is squared distance for optimization purposes
-		const float minDistance = std::max<float>((125.f / totalDensity), 2.0f);
-		//distance lower than 2 causes objects to overlap and crash
-
-		bool stop = false;
-		do {
-			//optimization - don't check tiles which are not allowed
-			vstd::erase_if(possibleTiles, [this](const int3 &tile) -> bool
-			{
-				//for water area we sholdn't place treasures close to coast
-				for(auto & lake : lakes)
-					if(vstd::contains(lake.distance, tile) && lake.distance[tile] < 2)
-						return true;
-				
-				return !gen->isPossible(tile) || gen->getZoneID(tile)!=getId();
-			});
-
-
-			int3 treasureTilePos;
-			//If we are able to place at least one object with value lower than minGuardedValue, it's ok
-			do
-			{
-				if (!findPlaceForTreasurePile(minDistance, treasureTilePos, t.min))
-				{
-					stop = true;
-					break;
-				}
-			}
-			while (!createTreasurePile(treasureTilePos, minDistance, t)); //failed creation - position was wrong, cannot connect it
-
-		} while (!stop);
-	}
-}
-
-void CRmgTemplateZone::createObstacles1()
-{
-	if (pos.z) //underground
-	{
-		//now make sure all accessible tiles have no additional rock on them
-
-		std::vector<int3> accessibleTiles;
-		for (auto tile : tileinfo)
-		{
-			if (gen->isFree(tile) || gen->isUsed(tile))
-			{
-				accessibleTiles.push_back(tile);
-			}
-		}
-		gen->getEditManager()->getTerrainSelection().setSelection(accessibleTiles);
-		gen->getEditManager()->drawTerrain(terrainType, &gen->rand);
-	}
-}
-
-void CRmgTemplateZone::createObstacles2()
-{
-
-	typedef std::vector<ObjectTemplate> obstacleVector;
-	//obstacleVector possibleObstacles;
-
-	std::map <ui8, obstacleVector> obstaclesBySize;
-	typedef std::pair <ui8, obstacleVector> obstaclePair;
-	std::vector<obstaclePair> possibleObstacles;
-
-	//get all possible obstacles for this terrain
-	for (auto primaryID : VLC->objtypeh->knownObjects())
-	{
-		for (auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
-		{
-			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
-			if (handler->isStaticObject())
-			{
-				for (auto temp : handler->getTemplates())
-				{
-					if (temp.canBePlacedAt(terrainType) && temp.getBlockMapOffset().valid())
-						obstaclesBySize[(ui8)temp.getBlockedOffsets().size()].push_back(temp);
-				}
-			}
-		}
-	}
-	for (auto o : obstaclesBySize)
-	{
-		possibleObstacles.push_back (std::make_pair(o.first, o.second));
-	}
-	boost::sort (possibleObstacles, [](const obstaclePair &p1, const obstaclePair &p2) -> bool
-	{
-		return p1.first > p2.first; //bigger obstacles first
-	});
-
-	auto sel = gen->getEditManager()->getTerrainSelection();
-	sel.clearSelection();
-
-	auto tryToPlaceObstacleHere = [this, &possibleObstacles](int3& tile, int index)-> bool
-	{
-		auto temp = *RandomGeneratorUtil::nextItem(possibleObstacles[index].second, gen->rand);
-		int3 obstaclePos = tile + temp.getBlockMapOffset();
-		if (canObstacleBePlacedHere(temp, obstaclePos)) //can be placed here
-		{
-			auto obj = VLC->objtypeh->getHandlerFor(temp.id, temp.subid)->create(temp);
-			placeObject(obj, obstaclePos, false);
-			return true;
-		}
-		return false;
-	};
-
-	//reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left
-	for (auto tile : boost::adaptors::reverse(tileinfo))
-	{
-		//fill tiles that should be blocked with obstacles
-		if (gen->shouldBeBlocked(tile))
-		{
-			//start from biggets obstacles
-			for (int i = 0; i < possibleObstacles.size(); i++)
-			{
-				if (tryToPlaceObstacleHere(tile, i))
-					break;
-			}
-		}
-	}
-	//cleanup - remove unused possible tiles to make space for roads
-	for (auto tile : tileinfo)
-	{
-		if (gen->isPossible(tile))
-		{
-			gen->setOccupied (tile, ETileType::FREE);
-		}
-	}
-}
-
-void CRmgTemplateZone::connectRoads()
-{
-	logGlobal->debug("Started building roads");
-
-	std::set<int3> roadNodesCopy(roadNodes);
-	std::set<int3> processed;
-
-	while(!roadNodesCopy.empty())
-	{
-		int3 node = *roadNodesCopy.begin();
-		roadNodesCopy.erase(node);
-		int3 cross(-1, -1, -1);
-
-		auto comparator = [=](int3 lhs, int3 rhs) { return node.dist2dSQ(lhs)  < node.dist2dSQ(rhs); };
-
-		if (processed.size()) //connect with already existing network
-		{
-			cross = *boost::range::min_element(processed, comparator); //find another remaining node
-		}
-		else if (roadNodesCopy.size()) //connect with any other unconnected node
-		{
-			cross = *boost::range::min_element(roadNodesCopy, comparator); //find another remaining node
-		}
-		else //no other nodes left, for example single road node in this zone
-			break;
-
-		logGlobal->debug("Building road from %s to %s", node.toString(), cross.toString());
-		if (createRoad(node, cross))
-		{
-			processed.insert(cross); //don't draw road starting at end point which is already connected
-			vstd::erase_if_present(roadNodesCopy, cross);
-		}
-
-		processed.insert(node);
-	}
-
-	drawRoads();
-
-	logGlobal->debug("Finished building roads");
-}
-
-void CRmgTemplateZone::drawRoads()
-{
-	std::vector<int3> tiles;
-	for (auto tile : roads)
-	{
-		if(gen->map->isInTheMap(tile))
-			tiles.push_back (tile);
-	}
-	for (auto tile : roadNodes)
-	{
-		if (gen->getZoneID(tile) == id) //mark roads for our nodes, but not for zone guards in other zones
-			tiles.push_back(tile);
-	}
-
-	gen->getEditManager()->getTerrainSelection().setSelection(tiles);
-	gen->getEditManager()->drawRoad(gen->getConfig().defaultRoadType, &gen->rand);
-}
-
-
-bool CRmgTemplateZone::fill()
-{
-	initTerrainType();
-	
-	addAllPossibleObjects();
-	
-	//zone center should be always clear to allow other tiles to connect
-	initFreeTiles();
-	connectLater(); //ideally this should work after fractalize, but fails
-	fractalize();
-	placeMines();
-	createRequiredObjects();
-	createTreasures();
-
-	logGlobal->info("Zone %d filled successfully", id);
-	return true;
-}
-
-bool CRmgTemplateZone::findPlaceForTreasurePile(float min_dist, int3 &pos, int value)
-{
-	float best_distance = 0;
-	bool result = false;
-
-	bool needsGuard = isGuardNeededForTreasure(value);
-
-	//logGlobal->info("Min dist for density %f is %d", density, min_dist);
-	for(auto tile : possibleTiles)
-	{
-		auto dist = gen->getNearestObjectDistance(tile);
-
-		if ((dist >= min_dist) && (dist > best_distance))
-		{
-			bool allTilesAvailable = true;
-			gen->foreach_neighbour (tile, [this, &allTilesAvailable, needsGuard](int3 neighbour)
-			{
-				if (!(gen->isPossible(neighbour) || gen->shouldBeBlocked(neighbour) || gen->getZoneID(neighbour)==getId() || (!needsGuard && gen->isFree(neighbour))))
-				{
-					allTilesAvailable = false; //all present tiles must be already blocked or ready for new objects
-				}
-			});
-			if (allTilesAvailable)
-			{
-				best_distance = dist;
-				pos = tile;
-				result = true;
-			}
-		}
-	}
-	if (result)
-	{
-		gen->setOccupied(pos, ETileType::BLOCKED); //block that tile //FIXME: why?
-	}
-	return result;
-}
-
-bool CRmgTemplateZone::canObstacleBePlacedHere(ObjectTemplate &temp, int3 &pos)
-{
-	if (!gen->map->isInTheMap(pos)) //blockmap may fit in the map, but botom-right corner does not
-		return false;
-
-	auto tilesBlockedByObject = temp.getBlockedOffsets();
-
-	for (auto blockingTile : tilesBlockedByObject)
-	{
-		int3 t = pos + blockingTile;
-		if (!gen->map->isInTheMap(t) || !(gen->isPossible(t) || gen->shouldBeBlocked(t)) || !temp.canBePlacedAt(gen->map->getTile(t).terType))
-		{
-			return false; //if at least one tile is not possible, object can't be placed here
-		}
-	}
-	return true;
-}
-
-bool CRmgTemplateZone::isAccessibleFromSomewhere(ObjectTemplate & appearance, const int3 & tile) const
-{
-	return getAccessibleOffset(appearance, tile).valid();
-}
-
-int3 CRmgTemplateZone::getAccessibleOffset(ObjectTemplate & appearance, const int3 & tile) const
-{
-	auto tilesBlockedByObject = appearance.getBlockedOffsets();
-
-	int3 ret(-1, -1, -1);
-	for (int x = -1; x < 2; x++)
-	{
-		for (int y = -1; y <2; y++)
-		{
-			if (x && y) //check only if object is visitable from another tile
-			{
-				int3 offset = int3(x, y, 0) - appearance.getVisitableOffset();
-				if (!vstd::contains(tilesBlockedByObject, offset))
-				{
-					int3 nearbyPos = tile + offset;
-					if (gen->map->isInTheMap(nearbyPos))
-					{
-						if (appearance.isVisitableFrom(x, y) && !gen->isBlocked(nearbyPos) && tileinfo.find(nearbyPos) != tileinfo.end())
-							ret = nearbyPos;
-					}
-				}
-			}
-		}
-	}
-	return ret;
-}
-
-void CRmgTemplateZone::setTemplateForObject(CGObjectInstance* obj)
-{
-	if (obj->appearance.id == Obj::NO_OBJ)
-	{
-		auto templates = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID)->getTemplates(gen->map->getTile(getPos()).terType);
-		if (templates.empty())
-			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % obj->ID % obj->subID % pos.toString()));
-
-		obj->appearance = templates.front();
-	}
-}
-
-bool CRmgTemplateZone::areAllTilesAvailable(CGObjectInstance* obj, int3& tile, const std::set<int3>& tilesBlockedByObject) const
-{
-	for (auto blockingTile : tilesBlockedByObject)
-	{
-		int3 t = tile + blockingTile;
-		if (!gen->map->isInTheMap(t) || !gen->isPossible(t) || gen->getZoneID(t)!=getId())
-		{
-			//if at least one tile is not possible, object can't be placed here
-			return false;
-		}
-	}
-	return true;
-}
-
-bool CRmgTemplateZone::findPlaceForObject(CGObjectInstance* obj, si32 min_dist, int3 &pos)
-{
-	//we need object apperance to deduce free tile
-	setTemplateForObject(obj);
-
-	int best_distance = 0;
-	bool result = false;
-
-	auto tilesBlockedByObject = obj->getBlockedOffsets();
-
-	for (auto tile : tileinfo)
-	{
-		//object must be accessible from at least one surounding tile
-		if (!isAccessibleFromSomewhere(obj->appearance, tile))
-			continue;
-
-		auto ti = gen->getTile(tile);
-		auto dist = ti.getNearestObjectDistance();
-		//avoid borders
-		if (gen->isPossible(tile) && (dist >= min_dist) && (dist > best_distance))
-		{
-			if (areAllTilesAvailable(obj, tile, tilesBlockedByObject))
-			{
-				best_distance = static_cast<int>(dist);
-				pos = tile;
-				result = true;
-			}
-		}
-	}
-	if (result)
-	{
-		gen->setOccupied(pos, ETileType::BLOCKED); //block that tile
-	}
-	return result;
-}
-
-void CRmgTemplateZone::checkAndPlaceObject(CGObjectInstance* object, const int3 &pos)
-{
-	if (!gen->map->isInTheMap(pos))
-		throw rmgException(boost::to_string(boost::format("Position of object %d at %s is outside the map") % object->id % pos.toString()));
-	object->pos = pos;
-
-	if (object->isVisitable() && !gen->map->isInTheMap(object->visitablePos()))
-		throw rmgException(boost::to_string(boost::format("Visitable tile %s of object %d at %s is outside the map") % object->visitablePos().toString() % object->id % object->pos.toString()));
-	for (auto tile : object->getBlockedPos())
-	{
-		if (!gen->map->isInTheMap(tile))
-			throw rmgException(boost::to_string(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % object->id % object->pos.toString()));
-	}
-
-	if (object->appearance.id == Obj::NO_OBJ)
-	{
-		auto terrainType = gen->map->getTile(pos).terType;
-		auto h = VLC->objtypeh->getHandlerFor(object->ID, object->subID);
-		auto templates = h->getTemplates(terrainType);
-		if (templates.empty())
-			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % object->ID % object->subID % pos.toString() % terrainType));
-
-		object->appearance = templates.front();
-	}
-
-	gen->getEditManager()->insertObject(object);
-}
-
-void CRmgTemplateZone::placeObject(CGObjectInstance* object, const int3 &pos, bool updateDistance)
-{
-	checkAndPlaceObject (object, pos);
-
-	auto points = object->getBlockedPos();
-	if (object->isVisitable())
-		points.insert(pos + object->getVisitableOffset());
-	points.insert(pos);
-	for(auto p : points)
-	{
-		if (gen->map->isInTheMap(p))
-		{
-			gen->setOccupied(p, ETileType::USED);
-		}
-	}
-	if (updateDistance)
-		updateDistances(pos);
-
-	switch (object->ID)
-	{
-	case Obj::TOWN:
-	case Obj::RANDOM_TOWN:
-	case Obj::MONOLITH_TWO_WAY:
-	case Obj::MONOLITH_ONE_WAY_ENTRANCE:
-	case Obj::MONOLITH_ONE_WAY_EXIT:
-	case Obj::SUBTERRANEAN_GATE:
-	case Obj::SHIPYARD:
-		{
-			addRoadNode(object->visitablePos());
-		}
-		break;
-
-	default:
-		break;
-	}
-}
-
-void CRmgTemplateZone::updateDistances(const int3 & pos)
-{
-	for (auto tile : possibleTiles) //don't need to mark distance for not possible tiles
-	{
-		ui32 d = pos.dist2dSQ(tile); //optimization, only relative distance is interesting
-		gen->setNearestObjectDistance(tile, std::min((float)d, gen->getNearestObjectDistance(tile)));
-	}
-}
-
-void CRmgTemplateZone::placeAndGuardObject(CGObjectInstance* object, const int3 &pos, si32 str, bool zoneGuard)
-{
-	placeObject(object, pos);
-	guardObject(object, str, zoneGuard);
-}
-
-void CRmgTemplateZone::placeSubterraneanGate(int3 pos, si32 guardStrength)
-{
-	auto factory = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0);
-	auto gate = factory->create(ObjectTemplate());
-	placeObject (gate, pos, true);
-	addToConnectLater (getAccessibleOffset (gate->appearance, pos)); //guard will be placed on accessibleOffset
-	guardObject (gate, guardStrength, true);
-}
-
-std::vector<int3> CRmgTemplateZone::getAccessibleOffsets (const CGObjectInstance* object)
-{
-	//get all tiles from which this object can be accessed
-	int3 visitable = object->visitablePos();
-	std::vector<int3> tiles;
-
-	auto tilesBlockedByObject = object->getBlockedPos(); //absolue value, as object is already placed
-
-	gen->foreach_neighbour(visitable, [&](int3& pos)
-	{
-		if (gen->isPossible(pos) || gen->isFree(pos))
-		{
-			if (!vstd::contains(tilesBlockedByObject, pos))
-			{
-				if (object->appearance.isVisitableFrom(pos.x - visitable.x, pos.y - visitable.y) && !gen->isBlocked(pos)) //TODO: refactor - info about visitability from absolute coordinates
-				{
-					tiles.push_back(pos);
-				}
-			}
-
-		};
-	});
-
-	return tiles;
-}
-
-bool CRmgTemplateZone::isGuardNeededForTreasure(int value)
-{
-	return getType() != ETemplateZoneType::WATER && value > minGuardedValue;
-}
-
-bool CRmgTemplateZone::guardObject(CGObjectInstance* object, si32 str, bool zoneGuard, bool addToFreePaths)
-{
-	std::vector<int3> tiles = getAccessibleOffsets(object);
-
-	int3 guardTile(-1, -1, -1);
-
-	if (tiles.size())
-	{
-		//guardTile = tiles.front();
-		guardTile = getAccessibleOffset(object->appearance, object->pos);
-		logGlobal->trace("Guard object at %s", object->pos.toString());
-	}
-	else
-	{
-		logGlobal->error("Failed to guard object at %s", object->pos.toString());
-		return false;
-	}
-
-	if (addMonster (guardTile, str, false, zoneGuard)) //do not place obstacles around unguarded object
-	{
-		for (auto pos : tiles)
-		{
-			if (gen->isPossible(pos) && gen->getZoneID(pos) == id)
-				gen->setOccupied(pos, ETileType::BLOCKED);
-		}
-		gen->foreach_neighbour (guardTile, [&](int3& pos)
-		{
-			if (gen->isPossible(pos) && gen->getZoneID(pos) == id)
-				gen->setOccupied(pos, ETileType::FREE);
-		});
-
-		gen->setOccupied (guardTile, ETileType::USED);
-	}
-	else //allow no guard or other object in front of this object
-	{
-		for (auto tile : tiles)
-			if (gen->isPossible(tile))
-				gen->setOccupied(tile, ETileType::FREE);
-	}
-
-	return true;
-}
-
-ObjectInfo CRmgTemplateZone::getRandomObject(CTreasurePileInfo &info, ui32 desiredValue, ui32 maxValue, ui32 currentValue)
-{
-	//int objectsVisitableFromBottom = 0; //for debug
-
-	std::vector<std::pair<ui32, ObjectInfo*>> thresholds; //handle complex object via pointer
-	ui32 total = 0;
-
-	//calculate actual treasure value range based on remaining value
-	ui32 maxVal = desiredValue - currentValue;
-	ui32 minValue = static_cast<ui32>(0.25f * (desiredValue - currentValue));
-
-	//roulette wheel
-	for (ObjectInfo &oi : possibleObjects) //copy constructor turned out to be costly
-	{
-		if (oi.value > maxVal)
-			break; //this assumes values are sorted in ascending order
-		if (oi.value >= minValue && oi.maxPerZone > 0)
-		{
-			int3 newVisitableOffset = oi.templ.getVisitableOffset(); //visitablePos assumes object will be shifter by visitableOffset
-			int3 newVisitablePos = info.nextTreasurePos;
-
-			if (!oi.templ.isVisitableFromTop())
-			{
-				//objectsVisitableFromBottom++;
-				//there must be free tiles under object
-				auto blockedOffsets = oi.templ.getBlockedOffsets();
-				if (!isAccessibleFromSomewhere(oi.templ, newVisitablePos))
-					continue;
-			}
-
-			//NOTE: y coordinate grows downwards
-			if (info.visitableFromBottomPositions.size() + info.visitableFromTopPositions.size()) //do not try to match first object in zone
-			{
-				bool fitsHere = false;
-
-				if (oi.templ.isVisitableFromTop()) //new can be accessed from any direction
-				{
-					for (auto tile : info.visitableFromTopPositions)
-					{
-						int3 actualTile = tile + newVisitableOffset;
-						if (newVisitablePos.areNeighbours(actualTile)) //we access other removable object from any position
-						{
-							fitsHere = true;
-							break;
-						}
-					}
-					for (auto tile : info.visitableFromBottomPositions)
-					{
-						int3 actualTile = tile + newVisitableOffset;
-						if (newVisitablePos.areNeighbours(actualTile) && newVisitablePos.y >= actualTile.y) //we access existing static object from side or bottom only
-						{
-							fitsHere = true;
-							break;
-						}
-					}
-				}
-				else //if new object is not visitable from top, it must be accessible from below or side
-				{
-					for (auto tile : info.visitableFromTopPositions)
-					{
-						int3 actualTile = tile + newVisitableOffset;
-						if (newVisitablePos.areNeighbours(actualTile) && newVisitablePos.y <= actualTile.y) //we access existing removable object from top or side only
-						{
-							fitsHere = true;
-							break;
-						}
-					}
-					for (auto tile : info.visitableFromBottomPositions)
-					{
-						int3 actualTile = tile + newVisitableOffset;
-						if (newVisitablePos.areNeighbours(actualTile) && newVisitablePos.y == actualTile.y) //we access other static object from side only
-						{
-							fitsHere = true;
-							break;
-						}
-					}
-				}
-				if (!fitsHere)
-					continue;
-			}
-
-			//now check blockmap, including our already reserved pile area
-
-			bool fitsBlockmap = true;
-
-			std::set<int3> blockedOffsets = oi.templ.getBlockedOffsets();
-			blockedOffsets.insert (newVisitableOffset);
-			for (auto blockingTile : blockedOffsets)
-			{
-				int3 t = info.nextTreasurePos + newVisitableOffset + blockingTile;
-				if (!gen->map->isInTheMap(t) || vstd::contains(info.occupiedPositions, t))
-				{
-					fitsBlockmap = false; //if at least one tile is not possible, object can't be placed here
-					break;
-				}
-				if (!(gen->isPossible(t) || gen->isBlocked(t))) //blocked tiles of object may cover blocked tiles, but not used or free tiles
-				{
-					fitsBlockmap = false;
-					break;
-				}
-			}
-			if (!fitsBlockmap)
-				continue;
-
-			total += oi.probability;
-
-			thresholds.push_back (std::make_pair (total, &oi));
-		}
-	}
-
-	if(thresholds.empty())
-	{
-		ObjectInfo oi;
-		//Generate pandora Box with gold if the value is extremely high
-		if(minValue > gen->getConfig().treasureValueLimit) //we don't have object valuable enough
-		{
-			oi.generateObject = [minValue]() -> CGObjectInstance *
-			{
-				auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
-				auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
-				obj->resources[Res::GOLD] = minValue;
-				return obj;
-			};
-			oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType);
-			oi.value = minValue;
-			oi.probability = 0;
-		}
-		else //generate empty object with 0 value if the value if we can't spawn anything
-		{
-			oi.generateObject = []() -> CGObjectInstance *
-			{
-				return nullptr;
-			};
-			oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType); //TODO: null template or something? should be never used, but hell knows
-			oi.value = 0; // this field is checked to determine no object
-			oi.probability = 0;
-		}
-		return oi;
-	}
-	else
-	{
-		int r = gen->rand.nextInt (1, total);
-
-		//binary search = fastest
-		auto it = std::lower_bound(thresholds.begin(), thresholds.end(), r,
-			[](const std::pair<ui32, ObjectInfo*> &rhs, const int lhs)->bool
-		{
-			return (int)rhs.first < lhs;
-		});
-		return *(it->second);
-	}
-}
-
-void CRmgTemplateZone::addAllPossibleObjects()
-{
-	ObjectInfo oi;
-
-	int numZones = static_cast<int>(gen->getZones().size());
-
-	for (auto primaryID : VLC->objtypeh->knownObjects())
-	{
-		for (auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
-		{
-			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
-			if (!handler->isStaticObject() && handler->getRMGInfo().value)
-			{
-				for (auto temp : handler->getTemplates())
-				{
-					if (temp.canBePlacedAt(terrainType))
-					{
-						oi.generateObject = [temp]() -> CGObjectInstance *
-						{
-							return VLC->objtypeh->getHandlerFor(temp.id, temp.subid)->create(temp);
-						};
-						auto rmgInfo = handler->getRMGInfo();
-						oi.value = rmgInfo.value;
-						oi.probability = rmgInfo.rarity;
-						oi.templ = temp;
-						oi.maxPerZone = rmgInfo.zoneLimit;
-						vstd::amin(oi.maxPerZone, rmgInfo.mapLimit / numZones); //simple, but should distribute objects evenly on large maps
-						possibleObjects.push_back(oi);
-					}
-				}
-			}
-		}
-	}
-	
-	if(type == ETemplateZoneType::WATER)
-		return;
-
-	//prisons
-	//levels 1, 5, 10, 20, 30
-	static int prisonsLevels = std::min(gen->getConfig().prisonExperience.size(), gen->getConfig().prisonValues.size());
-	for(int i = 0; i < prisonsLevels; i++)
-	{
-		oi.generateObject = [i, this]() -> CGObjectInstance *
-		{
-			std::vector<ui32> possibleHeroes;
-			for(int j = 0; j < gen->map->allowedHeroes.size(); j++)
-			{
-				if(gen->map->allowedHeroes[j])
-					possibleHeroes.push_back(j);
-			}
-
-			auto hid = *RandomGeneratorUtil::nextItem(possibleHeroes, gen->rand);
-			auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0);
-			auto obj = (CGHeroInstance *) factory->create(ObjectTemplate());
-
-
-			obj->subID = hid; //will be initialized later
-			obj->exp = gen->getConfig().prisonExperience[i];
-			obj->setOwner(PlayerColor::NEUTRAL);
-			gen->map->allowedHeroes[hid] = false; //ban this hero
-			gen->decreasePrisonsRemaining();
-			obj->appearance = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(terrainType).front(); //can't init template with hero subID
-
-			return obj;
-		};
-		oi.setTemplate(Obj::PRISON, 0, terrainType);
-		oi.value = gen->getConfig().prisonValues[i];
-		oi.probability = 30;
-		oi.maxPerZone = gen->getPrisonsRemaning() / 5; //probably not perfect, but we can't generate more prisons than hereos.
-		possibleObjects.push_back(oi);
-	}
-
-	//all following objects are unlimited
-	oi.maxPerZone = std::numeric_limits<ui32>().max();
-	
-	std::vector<CCreature *> creatures; //native creatures for this zone
-	for (auto cre : VLC->creh->objects)
-	{
-		if (!cre->special && cre->faction == townType)
-		{
-			creatures.push_back(cre);
-		}
-	}
-
-	//dwellings
-	auto dwellingTypes = {Obj::CREATURE_GENERATOR1, Obj::CREATURE_GENERATOR4};
-
-	for(auto dwellingType : dwellingTypes)
-	{
-		auto subObjects = VLC->objtypeh->knownSubObjects(dwellingType);
-
-		if(dwellingType == Obj::CREATURE_GENERATOR1)
-		{
-			//don't spawn original "neutral" dwellings that got replaced by Conflux dwellings in AB
-			static int elementalConfluxROE[] = {7, 13, 16, 47};
-			for(int i = 0; i < 4; i++)
-				vstd::erase_if_present(subObjects, elementalConfluxROE[i]);
-		}
-
-		for(auto secondaryID : subObjects)
-		{
-			auto dwellingHandler = dynamic_cast<const CDwellingInstanceConstructor *>(VLC->objtypeh->getHandlerFor(dwellingType, secondaryID).get());
-			auto creatures = dwellingHandler->getProducedCreatures();
-			if(creatures.empty())
-				continue;
-
-			auto cre = creatures.front();
-			if(cre->faction == townType)
-			{
-				float nativeZonesCount = static_cast<float>(gen->getZoneCount(cre->faction));
-				oi.value = static_cast<ui32>(cre->AIValue * cre->growth * (1 + (nativeZonesCount / gen->getTotalZoneCount()) + (nativeZonesCount / 2)));
-				oi.probability = 40;
-
-				for(auto tmplate : dwellingHandler->getTemplates())
-				{
-					if(tmplate.canBePlacedAt(terrainType))
-					{
-						oi.generateObject = [tmplate, secondaryID, dwellingType]() -> CGObjectInstance *
-						{
-							auto obj = VLC->objtypeh->getHandlerFor(dwellingType, secondaryID)->create(tmplate);
-							obj->tempOwner = PlayerColor::NEUTRAL;
-							return obj;
-						};
-
-						oi.templ = tmplate;
-						possibleObjects.push_back(oi);
-					}
-				}
-			}
-		}
-	}
-
-	for(int i = 0; i < gen->getConfig().scrollValues.size(); i++)
-	{
-		oi.generateObject = [i, this]() -> CGObjectInstance *
-		{
-			auto factory = VLC->objtypeh->getHandlerFor(Obj::SPELL_SCROLL, 0);
-			auto obj = (CGArtifact *) factory->create(ObjectTemplate());
-			std::vector<SpellID> out;
-
-			for (auto spell : VLC->spellh->objects) //spellh size appears to be greater (?)
-			{
-				if (gen->isAllowedSpell(spell->id) && spell->level == i + 1)
-				{
-					out.push_back(spell->id);
-				}
-			}
-			auto a = CArtifactInstance::createScroll(*RandomGeneratorUtil::nextItem(out, gen->rand));
-			obj->storedArtifact = a;
-			return obj;
-		};
-		oi.setTemplate(Obj::SPELL_SCROLL, 0, terrainType);
-		oi.value = gen->getConfig().scrollValues[i];
-		oi.probability = 30;
-		possibleObjects.push_back(oi);
-	}
-
-	//pandora box with gold
-	for(int i = 1; i < 5; i++)
-	{
-		oi.generateObject = [i]() -> CGObjectInstance *
-		{
-			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
-			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
-			obj->resources[Res::GOLD] = i * 5000;
-			return obj;
-		};
-		oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType);
-		oi.value = i * gen->getConfig().pandoraMultiplierGold;
-		oi.probability = 5;
-		possibleObjects.push_back(oi);
-	}
-
-	//pandora box with experience
-	for (int i = 1; i < 5; i++)
-	{
-		oi.generateObject = [i]() -> CGObjectInstance *
-		{
-			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
-			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
-			obj->gainedExp = i * 5000;
-			return obj;
-		};
-		oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType);
-		oi.value = i * gen->getConfig().pandoraMultiplierExperience;
-		oi.probability = 20;
-		possibleObjects.push_back(oi);
-	}
-
-	//pandora box with creatures
-	const std::vector<int> & tierValues = gen->getConfig().pandoraCreatureValues;
-
-	auto creatureToCount = [&tierValues](CCreature * creature) -> int
-	{
-		if (!creature->AIValue) //bug #2681
-			return 0; //this box won't be generated
-
-		int actualTier = creature->level > tierValues.size() ?
-						 tierValues.size() - 1 :
-						 creature->level - 1;
-		float creaturesAmount = ((float)tierValues[actualTier]) / creature->AIValue;
-		if (creaturesAmount <= 5)
-		{
-			creaturesAmount = boost::math::round(creaturesAmount); //allow single monsters
-			if (creaturesAmount < 1)
-				return 0;
-		}
-		else if (creaturesAmount <= 12)
-		{
-			(creaturesAmount /= 2) *= 2;
-		}
-		else if (creaturesAmount <= 50)
-		{
-			creaturesAmount = boost::math::round(creaturesAmount / 5) * 5;
-		}
-		else
-		{
-			creaturesAmount = boost::math::round(creaturesAmount / 10) * 10;
-		}
-		return static_cast<int>(creaturesAmount);
-	};
-
-	for (auto creature : creatures)
-	{
-		int creaturesAmount = creatureToCount(creature);
-		if (!creaturesAmount)
-			continue;
-
-		oi.generateObject = [creature, creaturesAmount]() -> CGObjectInstance *
-		{
-			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
-			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
-			auto stack = new CStackInstance(creature, creaturesAmount);
-			obj->creatures.putStack(SlotID(0), stack);
-			return obj;
-		};
-		oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType);
-		oi.value = static_cast<ui32>((2 * (creature->AIValue) * creaturesAmount * (1 + (float)(gen->getZoneCount(creature->faction)) / gen->getTotalZoneCount())) / 3);
-		oi.probability = 3;
-		possibleObjects.push_back(oi);
-	}
-
-	//Pandora with 12 spells of certain level
-	for (int i = 1; i <= GameConstants::SPELL_LEVELS; i++)
-	{
-		oi.generateObject = [i, this]() -> CGObjectInstance *
-		{
-			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
-			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
-
-			std::vector <CSpell *> spells;
-			for (auto spell : VLC->spellh->objects)
-			{
-				if (gen->isAllowedSpell(spell->id) && spell->level == i)
-					spells.push_back(spell);
-			}
-
-			RandomGeneratorUtil::randomShuffle(spells, gen->rand);
-			for (int j = 0; j < std::min(12, (int)spells.size()); j++)
-			{
-				obj->spells.push_back(spells[j]->id);
-			}
-
-			return obj;
-		};
-		oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType);
-		oi.value = (i + 1) * gen->getConfig().pandoraMultiplierSpells; //5000 - 15000
-		oi.probability = 2;
-		possibleObjects.push_back(oi);
-	}
-
-	//Pandora with 15 spells of certain school
-	for (int i = 0; i < 4; i++)
-	{
-		oi.generateObject = [i, this]() -> CGObjectInstance *
-		{
-			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
-			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
-
-			std::vector <CSpell *> spells;
-			for (auto spell : VLC->spellh->objects)
-			{
-				if (gen->isAllowedSpell(spell->id) && spell->school[(ESpellSchool)i])
-					spells.push_back(spell);
-			}
-
-			RandomGeneratorUtil::randomShuffle(spells, gen->rand);
-			for (int j = 0; j < std::min(15, (int)spells.size()); j++)
-			{
-				obj->spells.push_back(spells[j]->id);
-			}
-
-			return obj;
-		};
-		oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType);
-		oi.value = gen->getConfig().pandoraSpellSchool;
-		oi.probability = 2;
-		possibleObjects.push_back(oi);
-	}
-
-	// Pandora box with 60 random spells
-
-	oi.generateObject = [this]() -> CGObjectInstance *
-	{
-		auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
-		auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
-
-		std::vector <CSpell *> spells;
-		for (auto spell : VLC->spellh->objects)
-		{
-			if (gen->isAllowedSpell(spell->id))
-				spells.push_back(spell);
-		}
-
-		RandomGeneratorUtil::randomShuffle(spells, gen->rand);
-		for (int j = 0; j < std::min(60, (int)spells.size()); j++)
-		{
-			obj->spells.push_back(spells[j]->id);
-		}
-
-		return obj;
-	};
-	oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType);
-	oi.value = gen->getConfig().pandoraSpell60;
-	oi.probability = 2;
-	possibleObjects.push_back(oi);
-
-	//seer huts with creatures or generic rewards
-
-	if(questArtZone.lock()) //we won't be placing seer huts if there is no zone left to place arties
-	{
-		static const int genericSeerHuts = 8;
-		int seerHutsPerType = 0;
-		const int questArtsRemaining = static_cast<int>(gen->getQuestArtsRemaning().size());
-
-		//general issue is that not many artifact types are available for quests
-
-		if (questArtsRemaining >= genericSeerHuts + (int)creatures.size())
-		{
-			seerHutsPerType = questArtsRemaining / (genericSeerHuts + (int)creatures.size());
-		}
-		else if (questArtsRemaining >= genericSeerHuts)
-		{
-			seerHutsPerType = 1;
-		}
-		oi.maxPerZone = seerHutsPerType;
-
-		RandomGeneratorUtil::randomShuffle(creatures, gen->rand);
-
-		auto generateArtInfo = [this](ArtifactID id) -> ObjectInfo
-		{
-			ObjectInfo artInfo;
-			artInfo.probability = std::numeric_limits<ui16>::max(); //99,9% to spawn that art in first treasure pile
-			artInfo.maxPerZone = 1;
-			artInfo.value = 2000; //treasure art
-			artInfo.setTemplate(Obj::ARTIFACT, id, this->terrainType);
-			artInfo.generateObject = [id]() -> CGObjectInstance *
-			{
-				auto handler = VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, id);
-				return handler->create(handler->getTemplates().front());
-			};
-			return artInfo;
-		};
-
-		for(int i = 0; i < std::min((int)creatures.size(), questArtsRemaining - genericSeerHuts); i++)
-		{
-			auto creature = creatures[i];
-			int creaturesAmount = creatureToCount(creature);
-
-			if (!creaturesAmount)
-				continue;
-
-			int randomAppearance = chooseRandomAppearance(Obj::SEER_HUT);
-
-			oi.generateObject = [creature, creaturesAmount, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
-			{
-				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
-				auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
-				obj->rewardType = CGSeerHut::CREATURE;
-				obj->rID = creature->idNumber;
-				obj->rVal = creaturesAmount;
-
-				obj->quest->missionType = CQuest::MISSION_ART;
-				ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand);
-				obj->quest->m5arts.push_back(artid);
-				obj->quest->lastDay = -1;
-				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
-
-				gen->banQuestArt(artid);
-
-				this->questArtZone.lock()->possibleObjects.push_back (generateArtInfo(artid));
-
-				return obj;
-			};
-			oi.setTemplate(Obj::SEER_HUT, randomAppearance, terrainType);
-			oi.value = static_cast<ui32>(((2 * (creature->AIValue) * creaturesAmount * (1 + (float)(gen->getZoneCount(creature->faction)) / gen->getTotalZoneCount())) - 4000) / 3);
-			oi.probability = 3;
-			possibleObjects.push_back(oi);
-		}
-
-		static int seerLevels = std::min(gen->getConfig().questValues.size(), gen->getConfig().questRewardValues.size());
-		for(int i = 0; i < seerLevels; i++) //seems that code for exp and gold reward is similiar
-		{
-			int randomAppearance = chooseRandomAppearance(Obj::SEER_HUT);
-
-			oi.setTemplate(Obj::SEER_HUT, randomAppearance, terrainType);
-			oi.value = gen->getConfig().questValues[i];
-			oi.probability = 10;
-
-			oi.generateObject = [i, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
-			{
-				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
-				auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
-
-				obj->rewardType = CGSeerHut::EXPERIENCE;
-				obj->rID = 0; //unitialized?
-				obj->rVal = gen->getConfig().questRewardValues[i];
-
-				obj->quest->missionType = CQuest::MISSION_ART;
-				ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand);
-				obj->quest->m5arts.push_back(artid);
-				obj->quest->lastDay = -1;
-				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
-
-				gen->banQuestArt(artid);
-
-				this->questArtZone.lock()->possibleObjects.push_back(generateArtInfo(artid));
-
-				return obj;
-			};
-
-			possibleObjects.push_back(oi);
-
-			oi.generateObject = [i, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
-			{
-				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
-				auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
-				obj->rewardType = CGSeerHut::RESOURCES;
-				obj->rID = Res::GOLD;
-				obj->rVal = gen->getConfig().questRewardValues[i];
-
-				obj->quest->missionType = CQuest::MISSION_ART;
-				ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand);
-				obj->quest->m5arts.push_back(artid);
-				obj->quest->lastDay = -1;
-				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
-
-				gen->banQuestArt(artid);
-
-				this->questArtZone.lock()->possibleObjects.push_back(generateArtInfo(artid));
-
-				return obj;
-			};
-
-			possibleObjects.push_back(oi);
-		}
-	}
-}
-
-ObjectInfo::ObjectInfo()
-	: templ(), value(0), probability(0), maxPerZone(1)
-{
-
-}
-
-void ObjectInfo::setTemplate (si32 type, si32 subtype, Terrain terrainType)
-{
-	auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
-	if(!templHandler)
-		return;
-	
-	auto templates = templHandler->getTemplates(terrainType);
-	if(templates.empty())
-		return;
-	
-	templ = templates.front();
-}

+ 0 - 237
lib/rmg/CRmgTemplateZone.h

@@ -1,237 +0,0 @@
-/*
- * CRmgTemplateZone.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-
-#pragma once
-
-#include "../GameConstants.h"
-#include "CMapGenerator.h"
-#include "float3.h"
-#include "../int3.h"
-#include "CRmgTemplate.h"
-#include "../mapObjects/ObjectTemplate.h"
-#include <boost/heap/priority_queue.hpp> //A*
-#include "Terrain.h"
-
-class CMapGenerator;
-class CTileInfo;
-class int3;
-class CGObjectInstance;
-class ObjectTemplate;
-
-namespace EObjectPlacingResult
-{
-	enum EObjectPlacingResult
-	{
-		SUCCESS,
-		CANNOT_FIT,
-		SEALED_OFF
-	};
-}
-class DLL_LINKAGE CTileInfo
-{
-public:
-
-	CTileInfo();
-
-	float getNearestObjectDistance() const;
-	void setNearestObjectDistance(float value);
-	bool isBlocked() const;
-	bool shouldBeBlocked() const;
-	bool isPossible() const;
-	bool isFree() const;
-	bool isUsed() const;
-	bool isRoad() const;
-	void setOccupied(ETileType::ETileType value);
-	Terrain getTerrainType() const;
-	ETileType::ETileType getTileType() const;
-	void setTerrainType(Terrain value);
-
-	void setRoadType(const std::string & value);
-private:
-	float nearestObjectDistance;
-	ETileType::ETileType occupied;
-	Terrain terrain;
-	std::string roadType;
-};
-
-struct DLL_LINKAGE ObjectInfo
-{
-	ObjectTemplate templ;
-	ui32 value;
-	ui16 probability;
-	ui32 maxPerZone;
-	//ui32 maxPerMap; //unused
-	std::function<CGObjectInstance *()> generateObject;
-
-	void setTemplate (si32 type, si32 subtype, Terrain terrain);
-
-	ObjectInfo();
-
-	bool operator==(const ObjectInfo& oi) const { return (templ == oi.templ); }
-};
-
-struct DLL_LINKAGE CTreasurePileInfo
-{
-	std::set<int3> visitableFromBottomPositions; //can be visited only from bottom or side
-	std::set<int3> visitableFromTopPositions; //they can be visited from any direction
-	std::set<int3> blockedPositions;
-	std::set<int3> occupiedPositions; //blocked + visitable
-	int3 nextTreasurePos;
-};
-
-/// The CRmgTemplateZone describes a zone in a template.
-class DLL_LINKAGE CRmgTemplateZone : public rmg::ZoneOptions
-{
-public:
-	CRmgTemplateZone(CMapGenerator * Gen);
-
-	void setOptions(const rmg::ZoneOptions & options);
-	bool isUnderground() const;
-
-	float3 getCenter() const;
-	void setCenter(const float3 &f);
-	int3 getPos() const;
-	void setPos(const int3 &pos);
-	bool isAccessibleFromSomewhere(ObjectTemplate & appearance, const int3 & tile) const;
-	int3 getAccessibleOffset(ObjectTemplate & appearance, const int3 & tile) const;
-
-	void addTile (const int3 & pos);
-	void removeTile(const int3 & pos);
-	void initFreeTiles ();
-	std::set<int3> getTileInfo() const;
-	std::set<int3> getPossibleTiles() const;
-	std::set<int3> collectDistantTiles (float distance) const;
-	void clearTiles();
-
-	void addRequiredObject(CGObjectInstance * obj, si32 guardStrength=0);
-	void addCloseObject(CGObjectInstance * obj, si32 guardStrength = 0);
-	void addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget);
-	void addObjectAtPosition(CGObjectInstance * obj, const int3 & position, si32 guardStrength=0);
-
-	void addToConnectLater(const int3& src);
-	bool addMonster(int3 &pos, si32 strength, bool clearSurroundingTiles = true, bool zoneGuard = false);
-	bool createTreasurePile(int3 &pos, float minDistance, const CTreasureInfo& treasureInfo);
-	bool fill ();
-	bool placeMines ();
-	void initTownType ();
-	void paintZoneTerrain (Terrain terrainType);
-	void randomizeTownType(bool matchUndergroundType = false); //helper function
-	void initTerrainType ();
-	void createBorder();
-	void fractalize();
-	void connectLater();
-	EObjectPlacingResult::EObjectPlacingResult tryToPlaceObjectAndConnectToPath(CGObjectInstance * obj, const int3 & pos); //return true if the position can be connected
-	bool createRequiredObjects();
-	bool createShipyard(const int3 & pos, si32 guardStrength=0);
-	int3 createShipyard(const std::set<int3> & lake, si32 guardStrength=0);
-	bool makeBoat(TRmgTemplateZoneId land, const int3 & coast);
-	int3 makeBoat(TRmgTemplateZoneId land, const std::set<int3> & lake);
-	void createTreasures();
-	
-	void createWater(EWaterContent::EWaterContent waterContent, bool debug=false);
-	void waterInitFreeTiles();
-	void waterConnection(CRmgTemplateZone& dst);
-	bool waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB);
-	const std::set<int3>& getCoastTiles() const;
-	bool isWaterConnected(TRmgTemplateZoneId zone, const int3 & tile) const;
-	//void computeCoastTiles();
-	
-	void createObstacles1();
-	void createObstacles2();
-	bool crunchPath(const int3 &src, const int3 &dst, bool onlyStraight, std::set<int3>* clearedTiles = nullptr);
-	bool connectPath(const int3& src, bool onlyStraight);
-	bool connectWithCenter(const int3& src, bool onlyStraight, bool passTroughBlocked = false);
-	void updateDistances(const int3 & pos);
-
-	std::vector<int3> getAccessibleOffsets (const CGObjectInstance* object);
-	bool areAllTilesAvailable(CGObjectInstance* obj, int3& tile, const std::set<int3>& tilesBlockedByObject) const;
-
-	void setQuestArtZone(std::shared_ptr<CRmgTemplateZone> otherZone);
-	std::set<int3>* getFreePaths();
-	void addFreePath(const int3 &);
-
-	ObjectInfo getRandomObject (CTreasurePileInfo &info, ui32 desiredValue, ui32 maxValue, ui32 currentValue);
-
-	void placeSubterraneanGate(int3 pos, si32 guardStrength);
-	void placeObject(CGObjectInstance* object, const int3 &pos, bool updateDistance = true);
-	bool guardObject(CGObjectInstance* object, si32 str, bool zoneGuard = false, bool addToFreePaths = false);
-	void placeAndGuardObject(CGObjectInstance* object, const int3 &pos, si32 str, bool zoneGuard = false);
-	void addRoadNode(const int3 & node);
-	void connectRoads(); //fills "roads" according to "roadNodes"
-
-	//A* priority queue
-	typedef std::pair<int3, float> TDistance;
-	struct NodeComparer
-	{
-		bool operator()(const TDistance & lhs, const TDistance & rhs) const
-		{
-			return (rhs.second < lhs.second);
-		}
-	};
-	boost::heap::priority_queue<TDistance, boost::heap::compare<NodeComparer>> createPriorityQueue();
-
-private:
-	
-	//subclass to store disconnected parts of water zone
-	struct Lake
-	{
-		std::set<int3> tiles;
-		std::set<int3> coast;
-		std::map<int3, int> distance;
-		std::set<TRmgTemplateZoneId> connectedZones;
-		std::set<TRmgTemplateZoneId> keepConnections;
-	};
-	
-	CMapGenerator * gen;
-	
-	//template info
-	si32 townType;
-	Terrain terrainType;
-	std::weak_ptr<CRmgTemplateZone> questArtZone; //artifacts required for Seer Huts will be placed here - or not if null
-
-	std::vector<ObjectInfo> possibleObjects;
-	int minGuardedValue;
-	
-	//content info
-	std::vector<std::pair<CGObjectInstance*, ui32>> requiredObjects;
-	std::vector<std::pair<CGObjectInstance*, ui32>> closeObjects;
-	std::vector<std::pair<CGObjectInstance*, int3>> instantObjects;
-	std::vector<std::pair<CGObjectInstance*, CGObjectInstance*>> nearbyObjects;
-	std::vector<CGObjectInstance*> objects;
-	std::map<CGObjectInstance*, int3> requestedPositions;
-
-	//placement info
-	int3 pos;
-	float3 center;
-	std::set<int3> tileinfo; //irregular area assined to zone
-	std::set<int3> possibleTiles; //optimization purposes for treasure generation
-	std::set<int3> freePaths; //core paths of free tiles that all other objects will be linked to
-	std::set<int3> coastTiles; //tiles bordered to water
-
-	std::set<int3> roadNodes; //tiles to be connected with roads
-	std::set<int3> roads; //all tiles with roads
-	std::set<int3> tilesToConnectLater; //will be connected after paths are fractalized
-	std::vector<Lake> lakes; //disconnected parts of zone. Used to work with water zones
-	std::map<int3, int> lakeMap; //map tile on lakeId which is position of lake in lakes array +1
-	
-	bool createRoad(const int3 &src, const int3 &dst);
-	void drawRoads(); //actually updates tiles
-
-	bool pointIsIn(int x, int y);
-	void addAllPossibleObjects (); //add objects, including zone-specific, to possibleObjects
-	bool findPlaceForObject(CGObjectInstance* obj, si32 min_dist, int3 &pos);
-	bool findPlaceForTreasurePile(float min_dist, int3 &pos, int value);
-	bool canObstacleBePlacedHere(ObjectTemplate &temp, int3 &pos);
-	void setTemplateForObject(CGObjectInstance* obj);
-	void checkAndPlaceObject(CGObjectInstance* object, const int3 &pos);
-	int chooseRandomAppearance(si32 ObjID) const;
-	
-	bool isGuardNeededForTreasure(int value);
-};

+ 37 - 27
lib/rmg/CZonePlacer.cpp

@@ -11,15 +11,17 @@
 #include "StdInc.h"
 #include "../CRandomGenerator.h"
 #include "CZonePlacer.h"
-#include "CRmgTemplateZone.h"
 #include "../mapping/CMap.h"
 #include "../mapping/CMapEditManager.h"
+#include "RmgMap.h"
+#include "Zone.h"
+#include "Functions.h"
 
 class CRandomGenerator;
 
-CZonePlacer::CZonePlacer(CMapGenerator * Gen)
+CZonePlacer::CZonePlacer(RmgMap & map)
 	: width(0), height(0), scaleX(0), scaleY(0), mapSize(0), gravityConstant(0), stiffnessConstant(0),
-	gen(Gen)
+	map(map)
 {
 
 }
@@ -31,7 +33,7 @@ CZonePlacer::~CZonePlacer()
 
 int3 CZonePlacer::cords (const float3 f) const
 {
-	return int3((si32)std::max(0.f, (f.x * gen->map->width)-1), (si32)std::max(0.f, (f.y * gen->map->height-1)), f.z);
+	return int3((si32)std::max(0.f, (f.x * map.map().width)-1), (si32)std::max(0.f, (f.y * map.map().height-1)), f.z);
 }
 
 float CZonePlacer::getDistance (float distance) const
@@ -43,11 +45,15 @@ void CZonePlacer::placeZones(CRandomGenerator * rand)
 {
 	logGlobal->info("Starting zone placement");
 
-	width = gen->getMapGenOptions().getWidth();
-	height = gen->getMapGenOptions().getHeight();
+	width = map.getMapGenOptions().getWidth();
+	height = map.getMapGenOptions().getHeight();
 
-	auto zones = gen->getZones();
-	bool underground = gen->getMapGenOptions().getHasTwoLevels();
+	auto zones = map.getZones();
+	vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr)
+	{
+		return pr.second->getType() == ETemplateZoneType::WATER;
+	});
+	bool underground = map.getMapGenOptions().getHasTwoLevels();
 
 	/*
 	gravity-based algorithm
@@ -72,7 +78,7 @@ void CZonePlacer::placeZones(CRandomGenerator * rand)
 	float bestTotalDistance = 1e10;
 	float bestTotalOverlap = 1e10;
 
-	std::map<std::shared_ptr<CRmgTemplateZone>, float3> bestSolution;
+	std::map<std::shared_ptr<Zone>, float3> bestSolution;
 
 	TForceVector forces;
 	TForceVector totalForces; //  both attraction and pushback, overcomplicated?
@@ -173,7 +179,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
 			if (boost::optional<int> owner = zone.second->getOwner())
 			{
 				auto player = PlayerColor(*owner - 1);
-				auto playerSettings = gen->getMapGenOptions().getPlayersSettings();
+				auto playerSettings = map.getMapGenOptions().getPlayersSettings();
 				si32 faction = CMapGenOptions::CPlayerSettings::RANDOM_TOWN;
 				if (vstd::contains(playerSettings, player))
 					faction = playerSettings[player].getStartingTown();
@@ -351,7 +357,7 @@ void CZonePlacer::moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDista
 {
 	float maxRatio = 0;
 	const int maxDistanceMovementRatio = static_cast<int>(zones.size() * zones.size()); //experimental - the more zones, the greater total distance expected
-	std::shared_ptr<CRmgTemplateZone> misplacedZone;
+	std::shared_ptr<Zone> misplacedZone;
 
 	float totalDistance = 0;
 	float totalOverlap = 0;
@@ -371,7 +377,7 @@ void CZonePlacer::moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDista
 
 	if (maxRatio > maxDistanceMovementRatio && misplacedZone)
 	{
-		std::shared_ptr<CRmgTemplateZone> targetZone;
+		std::shared_ptr<Zone> targetZone;
 		float3 ourCenter = misplacedZone->getCenter();
 
 		if (totalDistance > totalOverlap)
@@ -450,20 +456,24 @@ d = 0.01 * dx^3 - 0.1618 * dx^2 + 1 * dx + ...
 	return dx * (1.0f + dx * (0.1f + dx * 0.01f)) + dy * (1.618f + dy * (-0.1618f + dy * 0.01618f));
 }
 
-void CZonePlacer::assignZones()
+void CZonePlacer::assignZones(CRandomGenerator * rand)
 {
 	logGlobal->info("Starting zone colouring");
 
-	auto width = gen->getMapGenOptions().getWidth();
-	auto height = gen->getMapGenOptions().getHeight();
+	auto width = map.getMapGenOptions().getWidth();
+	auto height = map.getMapGenOptions().getHeight();
 
 	//scale to Medium map to ensure smooth results
 	scaleX = 72.f / width;
 	scaleY = 72.f / height;
 
-	auto zones = gen->getZones();
+	auto zones = map.getZones();
+	vstd::erase_if(zones, [](const std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>> & pr)
+	{
+		return pr.second->getType() == ETemplateZoneType::WATER;
+	});
 
-	typedef std::pair<std::shared_ptr<CRmgTemplateZone>, float> Dpair;
+	typedef std::pair<std::shared_ptr<Zone>, float> Dpair;
 	std::vector <Dpair> distances;
 	distances.reserve(zones.size());
 
@@ -475,10 +485,10 @@ void CZonePlacer::assignZones()
 		return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize();
 	};
 
-	auto moveZoneToCenterOfMass = [](std::shared_ptr<CRmgTemplateZone> zone) -> void
+	auto moveZoneToCenterOfMass = [](std::shared_ptr<Zone> zone) -> void
 	{
 		int3 total(0, 0, 0);
-		auto tiles = zone->getTileInfo();
+		auto tiles = zone->area().getTiles();
 		for (auto tile : tiles)
 		{
 			total += tile;
@@ -488,7 +498,7 @@ void CZonePlacer::assignZones()
 		zone->setPos(int3(total.x / size, total.y / size, total.z / size));
 	};
 
-	int levels = gen->map->twoLevel ? 2 : 1;
+	int levels = map.map().twoLevel ? 2 : 1;
 
 	/*
 	1. Create Voronoi diagram
@@ -510,7 +520,7 @@ void CZonePlacer::assignZones()
 					else
 						distances.push_back(std::make_pair(zone.second, std::numeric_limits<float>::max()));
 				}
-				boost::min_element(distances, compareByDistance)->first->addTile(pos); //closest tile belongs to zone
+				boost::min_element(distances, compareByDistance)->first->area().add(pos); //closest tile belongs to zone
 			}
 		}
 	}
@@ -539,8 +549,8 @@ void CZonePlacer::assignZones()
 						distances.push_back (std::make_pair(zone.second, std::numeric_limits<float>::max()));
 				}
 				auto zone = boost::min_element(distances, compareByDistance)->first; //closest tile belongs to zone
-				zone->addTile(pos);
-				gen->setZoneID(pos, zone->getId());
+				zone->area().add(pos);
+				map.setZoneID(pos, zone->getId());
 			}
 		}
 	}
@@ -555,14 +565,14 @@ void CZonePlacer::assignZones()
 		{
 			if (!CREATE_FULL_UNDERGROUND)
 			{
-				auto discardTile = zone.second->collectDistantTiles((float)(zone.second->getSize() + 1));
-				for(auto& t : discardTile)
-					zone.second->removeTile(t);
+				auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f);
+				for(auto& t : discardTiles)
+					zone.second->area().erase(t);
 			}
 
 			//make sure that terrain inside zone is not a rock
 			//FIXME: reorder actions?
-			zone.second->paintZoneTerrain (Terrain("subterra"));
+			paintZoneTerrain(*zone.second, *rand, map, Terrain("subterra"));
 		}
 	}
 	logGlobal->info("Finished zone colouring");

+ 10 - 10
lib/rmg/CZonePlacer.h

@@ -10,32 +10,32 @@
 
 #pragma once
 
-#include "CMapGenerator.h"
 #include "float3.h"
 #include "../int3.h"
+#include "../GameConstants.h"
 
 class CZoneGraph;
 class CMap;
 class CRandomGenerator;
-class CRmgTemplateZone;
-class CMapGenerator;
+class RmgMap;
+class Zone;
 
-typedef std::vector<std::pair<TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>>> TZoneVector;
-typedef std::map <TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>> TZoneMap;
-typedef std::map <std::shared_ptr<CRmgTemplateZone>, float3> TForceVector;
-typedef std::map <std::shared_ptr<CRmgTemplateZone>, float> TDistanceVector;
+typedef std::vector<std::pair<TRmgTemplateZoneId, std::shared_ptr<Zone>>> TZoneVector;
+typedef std::map<TRmgTemplateZoneId, std::shared_ptr<Zone>> TZoneMap;
+typedef std::map<std::shared_ptr<Zone>, float3> TForceVector;
+typedef std::map<std::shared_ptr<Zone>, float> TDistanceVector;
 
 class CZonePlacer
 {
 public:
-	explicit CZonePlacer(CMapGenerator * gen);
+	explicit CZonePlacer(RmgMap & map);
 	int3 cords (const float3 f) const;
 	float metric (const int3 &a, const int3 &b) const;
 	float getDistance(float distance) const; //additional scaling without 0 divison
 	~CZonePlacer();
 
 	void placeZones(CRandomGenerator * rand);
-	void assignZones();
+	void assignZones(CRandomGenerator * rand);
 	
 private:
 	void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, CRandomGenerator * rand);
@@ -56,5 +56,5 @@ private:
     //float a1, b1, c1, a2, b2, c2;
 	//CMap * map;
 	//std::unique_ptr<CZoneGraph> graph;
-	CMapGenerator * gen;
+	RmgMap & map;
 };

+ 280 - 0
lib/rmg/ConnectionsPlacer.cpp

@@ -0,0 +1,280 @@
+/*
+ * ConnectionsPlacer.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 "ConnectionsPlacer.h"
+#include "CMapGenerator.h"
+#include "RmgMap.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "RmgPath.h"
+#include "RmgObject.h"
+#include "ObjectManager.h"
+#include "Functions.h"
+#include "RoadPlacer.h"
+#include "TileInfo.h"
+#include "WaterAdopter.h"
+#include "WaterProxy.h"
+#include "TownPlacer.h"
+
+void ConnectionsPlacer::process()
+{
+	collectNeighbourZones();
+	for(auto & c : dConnections)
+	{
+		if(c.getZoneA() != zone.getId() && c.getZoneB() != zone.getId())
+			continue;
+		
+		if(vstd::contains(dCompleted, c))
+			continue;
+		
+		selfSideDirectConnection(c);
+	}
+	
+	createBorder(map, zone);
+	
+	for(auto & c : dConnections)
+	{
+		if(c.getZoneA() != zone.getId() && c.getZoneB() != zone.getId())
+			continue;
+		
+		if(vstd::contains(dCompleted, c))
+			continue;
+		
+		selfSideIndirectConnection(c);
+	}
+}
+
+void ConnectionsPlacer::init()
+{
+	DEPENDENCY(WaterAdopter);
+	DEPENDENCY(TownPlacer);
+	POSTFUNCTION(RoadPlacer);
+	POSTFUNCTION(ObjectManager);
+	
+	for(auto c : map.getMapGenOptions().getMapTemplate()->getConnections())
+		addConnection(c);
+}
+
+void ConnectionsPlacer::addConnection(const rmg::ZoneConnection& connection)
+{
+	dConnections.push_back(connection);
+}
+
+void ConnectionsPlacer::otherSideConnection(const rmg::ZoneConnection & connection)
+{
+	dCompleted.push_back(connection);
+}
+
+void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & connection)
+{
+	bool success = false;
+	auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA());
+	auto & otherZone = map.getZones().at(otherZoneId);
+	
+	//1. Try to make direct connection
+	//Do if it's not prohibited by terrain settings
+	bool directProhibited = vstd::contains(Terrain::Manager::getInfo(zone.getTerrainType()).prohibitTransitions, otherZone->getTerrainType())
+						 || vstd::contains(Terrain::Manager::getInfo(otherZone->getTerrainType()).prohibitTransitions, zone.getTerrainType());
+	auto directConnectionIterator = dNeighbourZones.find(otherZoneId);
+	if(!directProhibited && directConnectionIterator != dNeighbourZones.end())
+	{
+		int3 guardPos(-1, -1, -1);
+		int3 borderPos;
+		while(!directConnectionIterator->second.empty())
+		{
+			borderPos = *RandomGeneratorUtil::nextItem(directConnectionIterator->second, generator.rand);
+			guardPos = zone.areaPossible().nearest(borderPos);
+			assert(borderPos != guardPos);
+			
+			auto safetyGap = rmg::Area({guardPos});
+			safetyGap.unite(safetyGap.getBorderOutside());
+			safetyGap.intersect(zone.areaPossible());
+			if(!safetyGap.empty())
+			{
+				safetyGap.intersect(otherZone->areaPossible());
+				if(safetyGap.empty())
+					break; //successfull position
+			}
+			
+			//failed position
+			directConnectionIterator->second.erase(borderPos);
+			guardPos = int3(-1, -1, -1);
+		}
+		
+		if(guardPos.valid())
+		{
+			assert(zone.getModificator<ObjectManager>());
+			auto & manager = *zone.getModificator<ObjectManager>();
+			auto * monsterType = manager.chooseGuard(connection.getGuardStrength(), true);
+			
+			rmg::Area border(zone.getArea().getBorder());
+			border.unite(otherZone->getArea().getBorder());
+			
+			auto costFunction = [&border](const int3 & s, const int3 & d)
+			{
+				return 1.f / (1.f + border.distanceSqr(d));
+			};
+			
+			auto ourArea = zone.areaPossible() + zone.freePaths();
+			auto theirArea = otherZone->areaPossible() + otherZone->freePaths();
+			theirArea.add(guardPos);
+			rmg::Path ourPath(ourArea), theirPath(theirArea);
+			ourPath.connect(zone.freePaths());
+			ourPath = ourPath.search(guardPos, true, costFunction);
+			theirPath.connect(otherZone->freePaths());
+			theirPath = theirPath.search(guardPos, true, costFunction);
+			
+			if(ourPath.valid() && theirPath.valid())
+			{
+				zone.connectPath(ourPath);
+				otherZone->connectPath(theirPath);
+				
+				if(monsterType)
+				{
+					rmg::Object monster(*monsterType);
+					monster.setPosition(guardPos);
+					manager.placeObject(monster, false, true);
+				}
+				else
+				{
+					zone.areaPossible().erase(guardPos);
+					zone.freePaths().add(guardPos);
+					map.setOccupied(guardPos, ETileType::FREE);
+				}
+				
+				assert(zone.getModificator<RoadPlacer>());
+				zone.getModificator<RoadPlacer>()->addRoadNode(guardPos);
+				
+				assert(otherZone->getModificator<RoadPlacer>());
+				otherZone->getModificator<RoadPlacer>()->addRoadNode(borderPos);
+				
+				assert(otherZone->getModificator<ConnectionsPlacer>());
+				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
+				
+				success = true;
+			}
+		}
+	}
+	
+	//2. connect via water
+	bool waterMode = map.getMapGenOptions().getWaterContent() != EWaterContent::NONE;
+	if(waterMode && zone.isUnderground() == otherZone->isUnderground())
+	{
+		if(generator.getZoneWater() && generator.getZoneWater()->getModificator<WaterProxy>())
+		{
+			if(generator.getZoneWater()->getModificator<WaterProxy>()->waterKeepConnection(connection.getZoneA(), connection.getZoneB()))
+			{
+				assert(otherZone->getModificator<ConnectionsPlacer>());
+				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
+				success = true;
+			}
+		}
+	}
+	
+	if(success)
+		dCompleted.push_back(connection);
+}
+
+void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & connection)
+{
+	bool success = false;
+	auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA());
+	auto & otherZone = map.getZones().at(otherZoneId);
+	
+	//3. place subterrain gates
+	if(zone.isUnderground() != otherZone->isUnderground())
+	{
+		int3 zShift(0, 0, zone.getPos().z - otherZone->getPos().z);
+		auto commonArea = zone.areaPossible() * (otherZone->areaPossible() + zShift);
+		if(!commonArea.empty())
+		{
+			assert(zone.getModificator<ObjectManager>());
+			auto & manager = *zone.getModificator<ObjectManager>();
+			
+			assert(otherZone->getModificator<ObjectManager>());
+			auto & managerOther = *otherZone->getModificator<ObjectManager>();
+			
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0);
+			auto gate1 = factory->create(ObjectTemplate());
+			auto gate2 = factory->create(ObjectTemplate());
+			rmg::Object rmgGate1(*gate1), rmgGate2(*gate2);
+			rmgGate1.setTemplate(zone.getTerrainType());
+			rmgGate2.setTemplate(otherZone->getTerrainType());
+			bool guarded1 = manager.addGuard(rmgGate1, connection.getGuardStrength(), true);
+			bool guarded2 = managerOther.addGuard(rmgGate2, connection.getGuardStrength(), true);
+			int minDist = 3;
+			
+			rmg::Path path2(otherZone->area());
+			rmg::Path path1 = manager.placeAndConnectObject(commonArea, rmgGate1, [this, minDist, &path2, &rmgGate1, &zShift, guarded2, &managerOther, &rmgGate2	](const int3 & tile)
+			{
+				auto ti = map.getTile(tile);
+				float dist = ti.getNearestObjectDistance();
+				if(dist < minDist)
+					return -1.f;
+				
+				rmg::Area toPlace(rmgGate1.getArea() + rmgGate1.getAccessibleArea());
+				toPlace.translate(-zShift);
+				
+				path2 = managerOther.placeAndConnectObject(toPlace, rmgGate2, minDist, guarded2, true, false);
+				
+				return path2.valid() ? 1.f : -1.f;
+			}, guarded1, true, false);
+			
+			if(path1.valid() && path2.valid())
+			{
+				zone.connectPath(path1);
+				otherZone->connectPath(path2);
+				
+				manager.placeObject(rmgGate1, guarded1, true);
+				managerOther.placeObject(rmgGate2, guarded2, true);
+				
+				assert(otherZone->getModificator<ConnectionsPlacer>());
+				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
+				
+				success = true;
+			}
+		}
+	}
+	
+	//4. place monoliths/portals
+	if(!success)
+	{
+		auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, generator.getNextMonlithIndex());
+		auto teleport1 = factory->create(ObjectTemplate());
+		auto teleport2 = factory->create(ObjectTemplate());
+		
+		zone.getModificator<ObjectManager>()->addRequiredObject(teleport1, connection.getGuardStrength());
+		otherZone->getModificator<ObjectManager>()->addRequiredObject(teleport2, connection.getGuardStrength());
+		
+		assert(otherZone->getModificator<ConnectionsPlacer>());
+		otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
+		
+		success = true;
+	}
+	
+	if(success)
+		dCompleted.push_back(connection);
+}
+
+void ConnectionsPlacer::collectNeighbourZones()
+{
+	auto border = zone.area().getBorderOutside();
+	for(auto & i : border)
+	{
+		if(!map.isOnMap(i))
+			continue;
+		
+		auto zid = map.getZoneID(i);
+		assert(zid != zone.getId());
+		dNeighbourZones[zid].insert(i);
+	}
+}

+ 35 - 0
lib/rmg/ConnectionsPlacer.h

@@ -0,0 +1,35 @@
+/*
+ * ConnectionsPlacer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+#include "RmgArea.h"
+
+class ConnectionsPlacer: public Modificator
+{
+public:
+	MODIFICATOR(ConnectionsPlacer);
+	
+	void process() override;
+	void init() override;
+	
+	void addConnection(const rmg::ZoneConnection& connection);
+	
+	void selfSideDirectConnection(const rmg::ZoneConnection & connection);
+	void selfSideIndirectConnection(const rmg::ZoneConnection & connection);
+	void otherSideConnection(const rmg::ZoneConnection & connection);
+	
+protected:
+	void collectNeighbourZones();
+	
+protected:
+	std::vector<rmg::ZoneConnection> dConnections, dCompleted;
+	std::map<TRmgTemplateZoneId, rmg::Tileset> dNeighbourZones;
+};

+ 220 - 0
lib/rmg/Functions.cpp

@@ -0,0 +1,220 @@
+/*
+ * Functions.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 "Functions.h"
+#include "CMapGenerator.h"
+#include "ObjectManager.h"
+#include "RoadPlacer.h"
+#include "TreasurePlacer.h"
+#include "ConnectionsPlacer.h"
+#include "TownPlacer.h"
+#include "WaterProxy.h"
+#include "WaterRoutes.h"
+#include "RmgMap.h"
+#include "TileInfo.h"
+#include "RmgPath.h"
+#include "../CTownHandler.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapping/CMap.h"
+#include "../mapObjects/CommonConstructors.h"
+#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
+#include "../VCMI_Lib.h"
+
+void createModificators(RmgMap & map)
+{
+	for(auto & z : map.getZones())
+	{
+		auto & zone = *z.second;
+		switch(zone.getType())
+		{
+			case ETemplateZoneType::WATER:
+				zone.addModificator<ObjectManager>();
+				zone.addModificator<TreasurePlacer>();
+				zone.addModificator<WaterProxy>();
+				zone.addModificator<WaterRoutes>();
+				break;
+				
+			default:
+				zone.addModificator<TownPlacer>();
+				zone.addModificator<ObjectManager>();
+				zone.addModificator<ConnectionsPlacer>();
+				zone.addModificator<TreasurePlacer>();
+				zone.addModificator<RoadPlacer>();
+				break;
+		}
+		
+	}
+}
+
+rmg::Tileset collectDistantTiles(const Zone& zone, int distance)
+{
+	int distanceSq = distance * distance;
+	auto subarea = zone.getArea().getSubarea([&zone, distanceSq](const int3 & t)
+	{
+		return t.dist2dSQ(zone.getPos()) > distanceSq;
+	});
+	return subarea.getTiles();
+}
+
+void createBorder(RmgMap & gen, Zone & zone)
+{
+	rmg::Area borderArea(zone.getArea().getBorder());
+	rmg::Area borderOutsideArea(zone.getArea().getBorderOutside());
+	auto blockBorder = borderArea.getSubarea([&gen, &borderOutsideArea](const int3 & t)
+	{
+		auto tile = borderOutsideArea.nearest(t);
+		return gen.isOnMap(tile) && gen.getZones()[gen.getZoneID(tile)]->getType() != ETemplateZoneType::WATER;
+	});
+	
+	for(auto & tile : blockBorder.getTilesVector())
+	{
+		if(gen.isPossible(tile))
+		{
+			gen.setOccupied(tile, ETileType::BLOCKED);
+			zone.areaPossible().erase(tile);
+		}
+		
+		gen.foreachDirectNeighbour(tile, [&gen, &zone](int3 &nearbyPos)
+		{
+			if(gen.isPossible(nearbyPos) && gen.getZoneID(nearbyPos) == zone.getId())
+			{
+				gen.setOccupied(nearbyPos, ETileType::BLOCKED);
+				zone.areaPossible().erase(nearbyPos);
+			}
+		});
+	}
+}
+
+void paintZoneTerrain(const Zone & zone, CRandomGenerator & generator, RmgMap & map, const Terrain & terrainType)
+{
+	auto v = zone.getArea().getTilesVector();
+	map.getEditManager()->getTerrainSelection().setSelection(v);
+	map.getEditManager()->drawTerrain(terrainType, &generator);
+}
+
+int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, const Terrain & terrain)
+{
+	auto factories = VLC->objtypeh->knownSubObjects(ObjID);
+	vstd::erase_if(factories, [ObjID, &terrain](si32 f)
+	{
+		return VLC->objtypeh->getHandlerFor(ObjID, f)->getTemplates(terrain).empty();
+	});
+	
+	return *RandomGeneratorUtil::nextItem(factories, generator);
+}
+
+void initTerrainType(Zone & zone, CMapGenerator & gen)
+{
+	if(zone.getType()==ETemplateZoneType::WATER)
+	{
+		//collect all water terrain types
+		std::vector<Terrain> waterTerrains;
+		for(auto & terrain : Terrain::Manager::terrains())
+			if(terrain.isWater())
+				waterTerrains.push_back(terrain);
+		
+		zone.setTerrainType(*RandomGeneratorUtil::nextItem(waterTerrains, gen.rand));
+	}
+	else
+	{
+		if(zone.isMatchTerrainToTown() && zone.getTownType() != ETownType::NEUTRAL)
+		{
+			zone.setTerrainType((*VLC->townh)[zone.getTownType()]->nativeTerrain);
+		}
+		else
+		{
+			zone.setTerrainType(*RandomGeneratorUtil::nextItem(zone.getTerrainTypes(), gen.rand));
+		}
+		
+		//TODO: allow new types of terrain?
+		{
+			if(zone.isUnderground())
+			{
+				if(!vstd::contains(gen.getConfig().terrainUndergroundAllowed, zone.getTerrainType()))
+				{
+					//collect all underground terrain types
+					std::vector<Terrain> undegroundTerrains;
+					for(auto & terrain : Terrain::Manager::terrains())
+						if(terrain.isUnderground())
+							undegroundTerrains.push_back(terrain);
+					
+					zone.setTerrainType(*RandomGeneratorUtil::nextItem(undegroundTerrains, gen.rand));
+				}
+			}
+			else
+			{
+				if(vstd::contains(gen.getConfig().terrainGroundProhibit, zone.getTerrainType()) || zone.getTerrainType().isUnderground())
+					zone.setTerrainType(Terrain("dirt"));
+			}
+		}
+	}
+}
+
+void createObstaclesCommon2(RmgMap & map, CRandomGenerator & generator)
+{
+	if(map.map().twoLevel)
+	{
+		//finally mark rock tiles as occupied, spawn no obstacles there
+		for(int x = 0; x < map.map().width; x++)
+		{
+			for(int y = 0; y < map.map().height; y++)
+			{
+				int3 tile(x, y, 1);
+				if(!map.map().getTile(tile).terType.isPassable())
+				{
+					map.setOccupied(tile, ETileType::USED);
+				}
+			}
+		}
+	}
+	
+	//tighten obstacles to improve visuals
+	
+	/*for (int i = 0; i < 3; ++i)
+	{
+		int blockedTiles = 0;
+		int freeTiles = 0;
+		
+		for (int z = 0; z < (map.map().twoLevel ? 2 : 1); z++)
+		{
+			for (int x = 0; x < map.map().width; x++)
+			{
+				for (int y = 0; y < map.map().height; y++)
+				{
+					int3 tile(x, y, z);
+					if (!map.isPossible(tile)) //only possible tiles can change
+						continue;
+					
+					int blockedNeighbours = 0;
+					int freeNeighbours = 0;
+					map.foreach_neighbour(tile, [&map, &blockedNeighbours, &freeNeighbours](int3 &pos)
+					{
+						if (map.isBlocked(pos))
+							blockedNeighbours++;
+						if (map.isFree(pos))
+							freeNeighbours++;
+					});
+					if (blockedNeighbours > 4)
+					{
+						map.setOccupied(tile, ETileType::BLOCKED);
+						blockedTiles++;
+					}
+					else if (freeNeighbours > 4)
+					{
+						map.setOccupied(tile, ETileType::FREE);
+						freeTiles++;
+					}
+				}
+			}
+		}
+		logGlobal->trace("Set %d tiles to BLOCKED and %d tiles to FREE", blockedTiles, freeTiles);
+	}*/
+}

+ 48 - 0
lib/rmg/Functions.h

@@ -0,0 +1,48 @@
+/*
+ * Functions.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "Zone.h"
+#include <boost/heap/priority_queue.hpp> //A*
+
+class RmgMap;
+class ObjectManager;
+class ObjectTemplate;
+class CMapGenerator;
+
+class rmgException : public std::exception
+{
+	std::string msg;
+public:
+	explicit rmgException(const std::string& _Message) : msg(_Message)
+	{
+	}
+	
+	virtual ~rmgException() throw ()
+	{
+	};
+	
+	const char *what() const throw () override
+	{
+		return msg.c_str();
+	}
+};
+
+rmg::Tileset collectDistantTiles(const Zone & zone, int distance);
+
+void createBorder(RmgMap & gen, Zone & zone);
+
+void paintZoneTerrain(const Zone & zone, CRandomGenerator & generator, RmgMap & map, const Terrain & terrainType);
+
+void initTerrainType(Zone & zone, CMapGenerator & gen);
+
+int chooseRandomAppearance(CRandomGenerator & generator, si32 ObjID, const Terrain & terrain);
+

+ 417 - 0
lib/rmg/ObjectManager.cpp

@@ -0,0 +1,417 @@
+/*
+ * ObjectManager.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 "ObjectManager.h"
+#include "CMapGenerator.h"
+#include "TileInfo.h"
+#include "RmgMap.h"
+#include "RoadPlacer.h"
+#include "RiverPlacer.h"
+#include "WaterAdopter.h"
+#include "../CCreatureHandler.h"
+#include "../mapObjects/CommonConstructors.h"
+#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "Functions.h"
+#include "RmgObject.h"
+
+void ObjectManager::process()
+{
+	zone.fractalize();
+		createRequiredObjects();
+}
+
+void ObjectManager::init()
+{
+	DEPENDENCY(WaterAdopter);
+	POSTFUNCTION(RoadPlacer);
+}
+
+void ObjectManager::addRequiredObject(CGObjectInstance * obj, si32 strength)
+{
+	requiredObjects.push_back(std::make_pair(obj, strength));
+}
+
+void ObjectManager::addCloseObject(CGObjectInstance * obj, si32 strength)
+{
+	closeObjects.push_back(std::make_pair(obj, strength));
+}
+
+void ObjectManager::addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget)
+{
+	nearbyObjects.push_back(std::make_pair(obj, nearbyTarget));
+}
+
+void ObjectManager::updateDistances(const rmg::Object & obj)
+{
+	for (auto tile : zone.areaPossible().getTiles()) //don't need to mark distance for not possible tiles
+	{
+		ui32 d = obj.getArea().distanceSqr(tile); //optimization, only relative distance is interesting
+		map.setNearestObjectDistance(tile, std::min((float)d, map.getNearestObjectDistance(tile)));
+	}
+}
+
+const rmg::Area & ObjectManager::getVisitableArea() const
+{
+	return objectsVisitableArea;
+}
+
+int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, bool optimizer) const
+{
+	float bestWeight = 0.f;
+	int3 result(-1, -1, -1);
+	
+	for(const auto & tile : searchArea.getTiles())
+	{
+		obj.setPosition(tile);
+		
+		if(!searchArea.contains(obj.getArea()) || !searchArea.overlap(obj.getAccessibleArea()))
+			continue;
+		
+		float weight = weightFunction(tile);
+		if(weight > bestWeight)
+		{
+			bestWeight = weight;
+			result = tile;
+			if(!optimizer)
+				break;
+		}
+	}
+	if(result.valid())
+		obj.setPosition(result);
+	return result;
+}
+
+int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool optimizer) const
+{
+	return findPlaceForObject(searchArea, obj, [this, min_dist](const int3 & tile)
+	{
+		auto ti = map.getTile(tile);
+		float dist = ti.getNearestObjectDistance();
+		if(dist < min_dist)
+			return -1.f;
+		
+		return dist;
+	}, optimizer);
+}
+
+rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, bool optimizer) const
+{
+	return placeAndConnectObject(searchArea, obj, [this, min_dist](const int3 & tile)
+	{
+		auto ti = map.getTile(tile);
+		float dist = ti.getNearestObjectDistance();
+		if(dist < min_dist)
+			return -1.f;
+		
+		return dist;
+	}, isGuarded, onlyStraight, optimizer);
+}
+
+rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, bool isGuarded, bool onlyStraight, bool optimizer) const
+{
+	int3 pos;
+	auto possibleArea = searchArea;
+	while(true)
+	{
+		pos = findPlaceForObject(possibleArea, obj, weightFunction, optimizer);
+		if(!pos.valid())
+		{
+			return rmg::Path::invalid();
+		}
+		possibleArea.erase(pos); //do not place again at this point
+		auto accessibleArea = obj.getAccessibleArea(isGuarded) * (zone.areaPossible() + zone.freePaths());
+		//we should exclude tiles which will be covered
+		if(isGuarded)
+		{
+			auto & guardedArea = obj.instances().back()->getAccessibleArea();
+			accessibleArea.intersect(guardedArea);
+		}
+		
+		auto path = zone.searchPath(accessibleArea, onlyStraight, [&obj, isGuarded](const int3 & t)
+		{
+			if(isGuarded)
+			{
+				auto & guardedArea =  obj.instances().back()->getAccessibleArea();
+				auto & unguardedArea = obj.getAccessibleArea(isGuarded);
+				if(unguardedArea.contains(t) && !guardedArea.contains(t))
+					return false;
+			}
+			return !obj.getArea().contains(t);
+		});
+		
+		if(path.valid())
+		{
+			return path;
+		}
+	}
+}
+
+bool ObjectManager::createRequiredObjects()
+{
+	logGlobal->trace("Creating required objects");
+	
+	for(const auto & object : requiredObjects)
+	{
+		auto * obj = object.first;
+		int3 pos;
+		rmg::Object rmgObject(*obj);
+		rmgObject.setTemplate(zone.getTerrainType());
+		bool guarded = addGuard(rmgObject, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY));
+		auto path = placeAndConnectObject(zone.areaPossible(), rmgObject, 3, guarded, false, true);
+		
+		if(!path.valid())
+		{
+			logGlobal->error("Failed to fill zone %d due to lack of space", zone.getId());
+			return false;
+		}
+		
+		zone.connectPath(path);
+		placeObject(rmgObject, guarded, true);
+		
+		for(const auto & nearby : nearbyObjects)
+		{
+			if(nearby.second != obj)
+				continue;
+			
+			rmg::Object rmgNearObject(*nearby.first);
+			rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside());
+			possibleArea.intersect(zone.areaPossible());
+			if(possibleArea.empty())
+			{
+				rmgNearObject.clear();
+				continue;
+			}
+			
+			rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), generator.rand));
+			placeObject(rmgNearObject, false, false);
+		}
+	}
+	
+	for(const auto & object : closeObjects)
+	{
+		auto * obj = object.first;
+		int3 pos;
+		auto possibleArea = zone.areaPossible();
+		rmg::Object rmgObject(*obj);
+		rmgObject.setTemplate(zone.getTerrainType());
+		bool guarded = addGuard(rmgObject, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY));
+		auto path = placeAndConnectObject(zone.areaPossible(), rmgObject,
+										  [this, &rmgObject](const int3 & tile)
+		{
+			float dist = rmgObject.getArea().distanceSqr(zone.getPos());
+			dist *= (dist > 12.f * 12.f) ? 10.f : 1.f; //tiles closer 12 are preferrable
+			dist = 1000000.f - dist; //some big number
+			return dist + map.getNearestObjectDistance(tile);
+		}, guarded, false, true);
+		
+		if(!path.valid())
+		{
+			logGlobal->error("Failed to fill zone %d due to lack of space", zone.getId());
+			return false;
+		}
+		
+		zone.connectPath(path);
+		placeObject(rmgObject, guarded, true);
+		
+		for(const auto & nearby : nearbyObjects)
+		{
+			if(nearby.second != obj)
+				continue;
+			
+			rmg::Object rmgNearObject(*nearby.first);
+			rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside());
+			possibleArea.intersect(zone.areaPossible());
+			if(possibleArea.empty())
+			{
+				rmgNearObject.clear();
+				continue;
+			}
+			
+			rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), generator.rand));
+			placeObject(rmgNearObject, false, false);
+		}
+	}
+	
+	//create object on specific positions
+	//TODO: implement guards
+	for (const auto &obj : instantObjects)
+	{
+		rmg::Object rmgObject(*obj.first);
+		rmgObject.setPosition(obj.second);
+		placeObject(rmgObject, false, false);
+	}
+	
+	requiredObjects.clear();
+	closeObjects.clear();
+	nearbyObjects.clear();
+	instantObjects.clear();
+	
+	return true;
+}
+
+void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateDistance)
+{	
+	object.finalize(map);
+	zone.areaPossible().subtract(object.getArea());
+	bool keepVisitable = zone.freePaths().contains(object.getVisitablePosition());
+	zone.freePaths().subtract(object.getArea()); //just to avoid areas overlapping
+	if(keepVisitable)
+		zone.freePaths().add(object.getVisitablePosition());
+	zone.areaUsed().unite(object.getArea());
+	zone.areaUsed().erase(object.getVisitablePosition());
+	
+	if(guarded)
+	{
+		auto guardedArea = object.instances().back()->getAccessibleArea();
+		guardedArea.add(object.instances().back()->getVisitablePosition());
+		auto areaToBlock = object.getAccessibleArea(true);
+		areaToBlock.subtract(guardedArea);
+		zone.areaPossible().subtract(areaToBlock);
+		for(auto & i : areaToBlock.getTilesVector())
+			if(map.isOnMap(i) && map.isPossible(i))
+				map.setOccupied(i, ETileType::BLOCKED);
+	}
+	
+	if(updateDistance)
+		updateDistances(object);
+	
+	for(auto * instance : object.instances())
+	{
+		objectsVisitableArea.add(instance->getVisitablePosition());
+		objects.push_back(&instance->object());
+		if(auto * m = zone.getModificator<RoadPlacer>())
+		{
+			if(instance->object().appearance.isVisitableFromTop())
+				m->areaForRoads().add(instance->getVisitablePosition());
+			else
+			{
+				m->areaIsolated().add(instance->getVisitablePosition() + int3(0, -1, 0));
+			}
+		}
+	}
+	
+	switch(object.instances().front()->object().ID)
+	{
+		case Obj::TOWN:
+		case Obj::RANDOM_TOWN:
+		case Obj::MONOLITH_TWO_WAY:
+		case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+		case Obj::MONOLITH_ONE_WAY_EXIT:
+		case Obj::SUBTERRANEAN_GATE:
+		case Obj::SHIPYARD:
+			if(auto * m = zone.getModificator<RoadPlacer>())
+				m->addRoadNode(object.instances().front()->getVisitablePosition());
+			break;
+			
+		case Obj::WATER_WHEEL:
+			if(auto * m = zone.getModificator<RiverPlacer>())
+				m->addRiverNode(object.instances().front()->getVisitablePosition());
+			break;
+			
+		default:
+			break;
+	}
+}
+
+CGCreature * ObjectManager::chooseGuard(si32 strength, bool zoneGuard)
+{
+	//precalculate actual (randomized) monster strength based on this post
+	//http://forum.vcmi.eu/viewtopic.php?p=12426#12426
+	
+	int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength();
+	int monsterStrength = (zoneGuard ? 0 : zone.zoneMonsterStrength) + mapMonsterStrength - 1; //array index from 0 to 4
+	static const std::array<int, 5> value1{2500, 1500, 1000, 500, 0};
+	static const std::array<int, 5> value2{7500, 7500, 7500, 5000, 5000};
+	static const std::array<float, 5> multiplier1{0.5, 0.75, 1.0, 1.5, 1.5};
+	static const std::array<float, 5> multiplier2{0.5, 0.75, 1.0, 1.0, 1.5};
+	
+	int strength1 = static_cast<int>(std::max(0.f, (strength - value1.at(monsterStrength)) * multiplier1.at(monsterStrength)));
+	int strength2 = static_cast<int>(std::max(0.f, (strength - value2.at(monsterStrength)) * multiplier2.at(monsterStrength)));
+	
+	strength = strength1 + strength2;
+	if (strength < generator.getConfig().minGuardStrength)
+		return nullptr; //no guard at all
+	
+	CreatureID creId = CreatureID::NONE;
+	int amount = 0;
+	std::vector<CreatureID> possibleCreatures;
+	for(auto cre : VLC->creh->objects)
+	{
+		if(cre->special)
+			continue;
+		if(!cre->AIValue) //bug #2681
+			continue;
+		if(!vstd::contains(zone.getMonsterTypes(), cre->faction))
+			continue;
+		if(((si32)(cre->AIValue * (cre->ammMin + cre->ammMax) / 2) < strength) && (strength < (si32)cre->AIValue * 100)) //at least one full monster. size between average size of given stack and 100
+		{
+			possibleCreatures.push_back(cre->idNumber);
+		}
+	}
+	if(possibleCreatures.size())
+	{
+		creId = *RandomGeneratorUtil::nextItem(possibleCreatures, generator.rand);
+		amount = strength / VLC->creh->objects[creId]->AIValue;
+		if (amount >= 4)
+			amount = static_cast<int>(amount * generator.rand.nextDouble(0.75, 1.25));
+	}
+	else //just pick any available creature
+	{
+		creId = CreatureID(132); //Azure Dragon
+		amount = strength / VLC->creh->objects[creId]->AIValue;
+	}
+	
+	auto guardFactory = VLC->objtypeh->getHandlerFor(Obj::MONSTER, creId);
+	
+	auto guard = (CGCreature *) guardFactory->create(ObjectTemplate());
+	guard->character = CGCreature::HOSTILE;
+	auto  hlp = new CStackInstance(creId, amount);
+	//will be set during initialization
+	guard->putStack(SlotID(0), hlp);
+	return guard;
+}
+
+bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard)
+{
+	auto * guard = chooseGuard(strength, zoneGuard);
+	if(!guard)
+		return false;
+	
+	rmg::Area visitablePos({object.getVisitablePosition()});
+	visitablePos.unite(visitablePos.getBorderOutside());
+	
+	auto accessibleArea = object.getAccessibleArea();
+	accessibleArea.intersect(visitablePos);
+	if(accessibleArea.empty())
+	{
+		delete guard;
+		return false;
+	}
+	auto guardTiles = accessibleArea.getTilesVector();
+	auto guardPos = *std::min_element(guardTiles.begin(), guardTiles.end(), [&object](const int3 & l, const int3 & r)
+	{
+		auto p = object.getVisitablePosition();
+		if(l.y > r.y)
+			return true;
+		
+		if(l.y == r.y)
+			return abs(l.x - p.x) < abs(r.x - p.x);
+		
+		return false;
+	});
+	
+	auto & instance = object.addInstance(*guard);
+	instance.setPosition(guardPos - object.getPosition());
+		
+	return true;
+}

+ 56 - 0
lib/rmg/ObjectManager.h

@@ -0,0 +1,56 @@
+/*
+ * ObjectManager.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "Zone.h"
+#include "RmgObject.h"
+
+class CGObjectInstance;
+class ObjectTemplate;
+class CGCreature;
+
+class ObjectManager: public Modificator
+{
+public:
+	MODIFICATOR(ObjectManager);
+	
+	void process() override;
+	void init() override;
+	
+	void addRequiredObject(CGObjectInstance * obj, si32 guardStrength=0);
+	void addCloseObject(CGObjectInstance * obj, si32 guardStrength = 0);
+	void addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget);
+		
+	bool createRequiredObjects();
+	
+	int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool optimizer) const;
+	int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, bool optimizer) const;
+	
+	rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, bool isGuarded, bool onlyStraight, bool optimizer) const;
+	rmg::Path placeAndConnectObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, bool isGuarded, bool onlyStraight, bool optimizer) const;
+	
+	CGCreature * chooseGuard(si32 strength, bool zoneGuard = false);
+	bool addGuard(rmg::Object & object, si32 strength, bool zoneGuard = false);
+	void placeObject(rmg::Object & object, bool guarded, bool updateDistance);
+	
+	void updateDistances(const rmg::Object & obj);
+	
+	const rmg::Area & getVisitableArea() const;
+	
+protected:
+	//content info
+	std::vector<std::pair<CGObjectInstance*, ui32>> requiredObjects;
+	std::vector<std::pair<CGObjectInstance*, ui32>> closeObjects;
+	std::vector<std::pair<CGObjectInstance*, int3>> instantObjects;
+	std::vector<std::pair<CGObjectInstance*, CGObjectInstance*>> nearbyObjects;
+	std::vector<CGObjectInstance*> objects;
+	rmg::Area objectsVisitableArea;
+};

+ 188 - 0
lib/rmg/ObstaclePlacer.cpp

@@ -0,0 +1,188 @@
+/*
+ * ObstaclePlacer.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 "../mapObjects/CObjectClassesHandler.h"
+#include "ObstaclePlacer.h"
+#include "ObjectManager.h"
+#include "TreasurePlacer.h"
+#include "RockPlacer.h"
+#include "WaterRoutes.h"
+#include "WaterProxy.h"
+#include "RoadPlacer.h"
+#include "RiverPlacer.h"
+#include "RmgMap.h"
+#include "CMapGenerator.h"
+#include "../CRandomGenerator.h"
+#include "Functions.h"
+
+void ObstaclePlacer::process()
+{
+	auto * manager = zone.getModificator<ObjectManager>();
+	if(!manager)
+		return;
+	
+	auto * riverManager = zone.getModificator<RiverPlacer>();
+	
+	typedef std::vector<ObjectTemplate> ObstacleVector;
+	//obstacleVector possibleObstacles;
+	
+	std::map<int, ObstacleVector> obstaclesBySize;
+	typedef std::pair<int, ObstacleVector> ObstaclePair;
+	std::vector<ObstaclePair> possibleObstacles;
+	
+	//get all possible obstacles for this terrain
+	for(auto primaryID : VLC->objtypeh->knownObjects())
+	{
+		for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
+		{
+			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
+			if(handler->isStaticObject())
+			{
+				for(auto temp : handler->getTemplates())
+				{
+					if(temp.canBePlacedAt(zone.getTerrainType()) && temp.getBlockMapOffset().valid())
+						obstaclesBySize[temp.getBlockedOffsets().size()].push_back(temp);
+				}
+			}
+		}
+	}
+	for(auto o : obstaclesBySize)
+	{
+		possibleObstacles.push_back(o);
+	}
+	boost::sort(possibleObstacles, [](const ObstaclePair &p1, const ObstaclePair &p2) -> bool
+	{
+		return p1.first > p2.first; //bigger obstacles first
+	});
+	
+	auto blockedArea = zone.area().getSubarea([this](const int3 & t)
+	{
+		return map.shouldBeBlocked(t);
+	});
+	blockedArea.subtract(zone.areaUsed());
+	zone.areaPossible().subtract(blockedArea);
+	
+	
+	auto prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea();
+	
+	//reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left
+	auto blockedTiles = blockedArea.getTilesVector();
+	int tilePos = 0;
+	while(!blockedArea.empty() && tilePos < blockedArea.getTilesVector().size())
+	{
+		auto tile = blockedArea.getTilesVector()[tilePos];
+		
+		std::list<rmg::Object> allObjects;
+		std::vector<std::pair<rmg::Object*, int3>> weightedObjects; //obj + position
+		int maxWeight = std::numeric_limits<int>::min();
+		for(int i = 0; i < possibleObstacles.size(); ++i)
+		{
+			if(!possibleObstacles[i].first)
+				continue;
+			
+			for(auto & temp : possibleObstacles[i].second)
+			{
+				auto handler = VLC->objtypeh->getHandlerFor(temp.id, temp.subid);
+				auto obj = handler->create(temp);
+				allObjects.emplace_back(*obj);
+				rmg::Object * rmgObject = &allObjects.back();
+				for(auto & offset : obj->getBlockedOffsets())
+				{
+					rmgObject->setPosition(tile - offset);
+					if(!map.isOnMap(rmgObject->getPosition()))
+						continue;
+					
+					if(!rmgObject->getArea().getSubarea([this](const int3 & t)
+					{
+						return !map.isOnMap(t);
+					}).empty())
+						continue;
+					
+					if(prohibitedArea.overlap(rmgObject->getArea()))
+						continue;
+					
+					if(!zone.area().contains(rmgObject->getArea()))
+						continue;
+					
+					int coverageBlocked = 0;
+					int coveragePossible = 0;
+					//do not use area intersection in optimization purposes
+					for(auto & t : rmgObject->getArea().getTilesVector())
+					{
+						if(map.shouldBeBlocked(t))
+							++coverageBlocked;
+						if(zone.areaPossible().contains(t))
+							++coveragePossible;
+					}
+					
+					int coverageOverlap = possibleObstacles[i].first - coverageBlocked - coveragePossible;
+					int weight = possibleObstacles[i].first + coverageBlocked - coverageOverlap * possibleObstacles[i].first;
+					assert(coverageOverlap >= 0);
+					
+					if(weight > maxWeight)
+					{
+						weightedObjects.clear();
+						maxWeight = weight;
+						weightedObjects.emplace_back(rmgObject, rmgObject->getPosition());
+						if(weight > 0)
+							break;
+					}
+					else if(weight == maxWeight)
+						weightedObjects.emplace_back(rmgObject, rmgObject->getPosition());
+					
+				}
+			}
+			
+			if(maxWeight > 0)
+				break;
+		}
+		
+		if(weightedObjects.empty())
+		{
+			tilePos += 1;
+			continue;
+		}
+		
+		auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, generator.rand);
+		objIter->first->setPosition(objIter->second);
+		manager->placeObject(*objIter->first, false, false);
+		blockedArea.subtract(objIter->first->getArea());
+		tilePos = 0;
+		
+		//river processing
+		if(riverManager)
+		{
+			if(objIter->first->instances().front()->object().typeName == "mountain")
+				riverManager->riverSource().unite(objIter->first->getArea());
+			if(objIter->first->instances().front()->object().typeName == "lake")
+				riverManager->riverSink().unite(objIter->first->getArea());
+		}
+		
+		if(maxWeight < 0)
+			logGlobal->warn("Placed obstacle with negative weight at %s", objIter->second.toString());
+		
+		for(auto & o : allObjects)
+		{
+			if(&o != objIter->first)
+				o.clear();
+		}
+	}
+}
+
+void ObstaclePlacer::init()
+{
+	DEPENDENCY(ObjectManager);
+	DEPENDENCY(TreasurePlacer);
+	DEPENDENCY(WaterRoutes);
+	DEPENDENCY(WaterProxy);
+	DEPENDENCY(RoadPlacer);
+	DEPENDENCY_ALL(RockPlacer);
+}

+ 21 - 0
lib/rmg/ObstaclePlacer.h

@@ -0,0 +1,21 @@
+/*
+ * ObstaclePlacer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+
+class ObstaclePlacer: public Modificator
+{
+public:
+	MODIFICATOR(ObstaclePlacer);
+	
+	void process() override;
+	void init() override;
+};

+ 404 - 0
lib/rmg/RiverPlacer.cpp

@@ -0,0 +1,404 @@
+/*
+ * RiverPlacer.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 "RiverPlacer.h"
+#include "Functions.h"
+#include "CMapGenerator.h"
+#include "RmgMap.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "RmgPath.h"
+#include "ObjectManager.h"
+#include "ObstaclePlacer.h"
+#include "WaterProxy.h"
+#include "RoadPlacer.h"
+
+const int RIVER_DELTA_ID = 143;
+const int RIVER_DELTA_SUBTYPE = 0;
+const std::map<std::string, std::string> RIVER_DELTA_TEMPLATE_NAME
+{
+	{RIVER_NAMES[1], "clrdelt"},
+	{RIVER_NAMES[2], "icedelt"},
+	{RIVER_NAMES[3], "muddelt"},
+	{RIVER_NAMES[4], "lavdelt"}
+};
+
+const std::array<std::array<int, 25>, 4> deltaTemplates
+{
+	//0 - must be on ground
+	//1 - delta entry
+	//2 - must be on water
+	//3 - anything
+	//4 - prohibit river placement
+	//5 - must be on ground + position
+	//6 - must be on water + position
+	std::array<int, 25>{
+		3, 4, 3, 4, 3,
+		3, 4, 1, 4, 3,
+		3, 0, 0, 0, 3,
+		3, 0, 0, 0, 3,
+		3, 2, 2, 6, 3
+	},
+	std::array<int, 25>{
+		3, 2, 2, 2, 3,
+		3, 0, 0, 0, 3,
+		3, 0, 0, 5, 3,
+		3, 4, 1, 4, 3,
+		3, 4, 3, 4, 3
+	},
+	std::array<int, 25>{
+		3, 3, 3, 3, 3,
+		4, 4, 0, 0, 2,
+		3, 1, 0, 0, 2,
+		4, 4, 0, 0, 6,
+		3, 3, 3, 3, 3
+	},
+	std::array<int, 25>	{
+		3, 3, 3, 3, 3,
+		2, 0, 0, 4, 4,
+		2, 0, 0, 1, 3,
+		2, 0, 5, 4, 4,
+		3, 3, 3, 3, 3
+	}
+};
+
+void RiverPlacer::process()
+{
+	preprocess();
+	for(auto & t : riverNodes)
+		connectRiver(t);
+	
+	if(!rivers.empty())
+		drawRivers();
+}
+
+void RiverPlacer::init()
+{
+	DEPENDENCY_ALL(WaterProxy);
+	DEPENDENCY(ObjectManager);
+	DEPENDENCY(ObstaclePlacer);
+}
+
+void RiverPlacer::drawRivers()
+{
+	map.getEditManager()->getTerrainSelection().setSelection(rivers.getTilesVector());
+	map.getEditManager()->drawRiver(Terrain::Manager::getInfo(zone.getTerrainType()).river, &generator.rand);
+}
+
+char RiverPlacer::dump(const int3 & t)
+{
+	if(riverNodes.count(t))
+		return '@';
+	if(rivers.contains(t))
+		return '~';
+	if(sink.contains(t))
+		return '2';
+	if(source.contains(t))
+		return '1';
+	if(zone.area().contains(t))
+		return ' ';
+	return '?';
+}
+
+void RiverPlacer::addRiverNode(const int3 & node)
+{
+	assert(zone.area().contains(node));
+	riverNodes.insert(node);
+}
+
+rmg::Area & RiverPlacer::riverSource()
+{
+	return source;
+}
+
+rmg::Area & RiverPlacer::riverSink()
+{
+	return sink;
+}
+
+rmg::Area & RiverPlacer::riverProhibit()
+{
+	return prohibit;
+}
+
+void RiverPlacer::prepareHeightmap()
+{
+	rmg::Area roads;
+	if(auto * m = zone.getModificator<RoadPlacer>())
+	{
+		roads.unite(m->getRoads());
+	}
+	
+	for(auto & t : zone.area().getTilesVector())
+	{
+		heightMap[t] = generator.rand.nextInt(5);
+		
+		if(roads.contains(t))
+			heightMap[t] += 30.f;
+		
+		if(zone.areaUsed().contains(t))
+			heightMap[t] += 1000.f;
+	}
+	
+	//make grid
+	for(int j = 0; j < map.map().height; j += 2)
+	{
+		for(int i = 0; i < map.map().width; i += 2)
+		{
+			int3 t{i, j, zone.getPos().z};
+			if(zone.area().contains(t))
+				heightMap[t] += 10.f;
+		}
+	}
+}
+
+void RiverPlacer::preprocess()
+{
+	rmg::Area outOfMapTiles;
+	std::map<TRmgTemplateZoneId, rmg::Area> neighbourZonesTiles;
+	rmg::Area borderArea(zone.getArea().getBorder());
+	TRmgTemplateZoneId connectedToWaterZoneId = -1;
+	for(auto & t : zone.getArea().getBorderOutside())
+	{
+		if(!map.isOnMap(t))
+		{
+			outOfMapTiles.add(t);
+		}
+		else if(map.getZoneID(t) != zone.getId())
+		{
+			if(map.getZones()[map.getZoneID(t)]->getType() == ETemplateZoneType::WATER)
+				connectedToWaterZoneId = map.getZoneID(t);
+			neighbourZonesTiles[map.getZoneID(t)].add(t);
+		}
+	}
+	rmg::Area outOfMapInternal(outOfMapTiles.getBorderOutside());
+	outOfMapInternal.intersect(borderArea);
+	
+	//looking outside map
+	if(!outOfMapInternal.empty())
+	{
+		auto elem = *RandomGeneratorUtil::nextItem(outOfMapInternal.getTilesVector(), generator.rand);
+		source.add(elem);
+		outOfMapInternal.erase(elem);
+	}
+	if(!outOfMapInternal.empty())
+	{
+		auto elem = *RandomGeneratorUtil::nextItem(outOfMapInternal.getTilesVector(), generator.rand);
+		sink.add(elem);
+		outOfMapInternal.erase(elem);
+	}
+	
+	//calculate delta positions
+	if(connectedToWaterZoneId > -1)
+	{
+		auto river = Terrain::Manager::getInfo(zone.getTerrainType()).river;
+		auto & a = neighbourZonesTiles[connectedToWaterZoneId];
+		auto availableArea = zone.areaPossible() + zone.freePaths();
+		for(auto & tileToProcess : availableArea.getTilesVector())
+		{
+			int templateId = -1;
+			for(int tId = 0; tId < 4; ++tId)
+			{
+				templateId = tId;
+				for(int i = 0; i < 25; ++i)
+				{
+					if((deltaTemplates[tId][i] == 2 || deltaTemplates[tId][i] == 6) && !a.contains(tileToProcess + int3(i % 5 - 2, i / 5 - 2, 0)))
+					{
+						templateId = -1;
+						break;
+					}
+					if((deltaTemplates[tId][i] < 2 || deltaTemplates[tId][i] == 5) && !availableArea.contains(tileToProcess + int3(i % 5 - 2, i / 5 - 2, 0)))
+					{
+						templateId = -1;
+						break;
+					}
+				}
+				if(templateId > -1)
+					break;
+			}
+			
+			if(templateId > -1)
+			{
+				for(int i = 0; i < 25; ++i)
+				{
+					auto p = tileToProcess + int3(i % 5 - 2, i / 5 - 2, 0);
+					if(deltaTemplates[templateId][i] == 1)
+					{
+						sink.add(p);
+						deltaSink.add(p);
+						deltaOrientations[p] = templateId + 1;
+						
+						//specific case: deltas for ice rivers amd mud rivers are messed :(
+						if(river == RIVER_NAMES[2])
+						{
+							switch(deltaOrientations[p])
+							{
+								case 1:
+									deltaOrientations[p] = 2;
+									break;
+								case 2:
+									deltaOrientations[p] = 3;
+									break;
+								case 3:
+									deltaOrientations[p] = 4;
+									break;
+								case 4:
+									deltaOrientations[p] = 1;
+									break;
+							}
+						}
+						if(river == RIVER_NAMES[3])
+						{
+							switch(deltaOrientations[p])
+							{
+								case 1:
+									deltaOrientations[p] = 4;
+									break;
+								case 2:
+									deltaOrientations[p] = 3;
+									break;
+								case 3:
+									deltaOrientations[p] = 1;
+									break;
+								case 4:
+									deltaOrientations[p] = 2;
+									break;
+							}
+						}
+						
+						for(auto j = 0; j < 25; ++j)
+						{
+							if(deltaTemplates[templateId][j] >= 5)
+							{
+								deltaPositions[p] = tileToProcess + int3(j % 5 - 2, j / 5 - 2, 0);
+							}
+						}
+					}
+					if(deltaTemplates[templateId][i] == 0 || deltaTemplates[templateId][i] == 4 || deltaTemplates[templateId][i] == 5)
+					{
+						prohibit.add(p);
+					}
+				}
+			}
+		}
+	}
+	
+	prepareHeightmap();
+	
+	//decorative river
+	if(!sink.empty() && !source.empty() && riverNodes.empty() && !zone.areaPossible().empty())
+	{
+		addRiverNode(*RandomGeneratorUtil::nextItem(source.getTilesVector(), generator.rand));
+	}
+	
+	if(source.empty())
+	{
+		logGlobal->info("River source is empty!");
+		
+		//looking outside map
+		
+		for(auto & i : heightMap)
+		{
+			if(i.second > 0)
+				source.add(i.first);
+		}
+	}
+	
+	if(sink.empty())
+	{
+		logGlobal->error("River sink is empty!");
+		for(auto & i : heightMap)
+		{
+			if(i.second <= 0)
+				sink.add(i.first);
+		}
+	}
+}
+
+void RiverPlacer::connectRiver(const int3 & tile)
+{
+	auto river = Terrain::Manager::getInfo(zone.getTerrainType()).river;
+	if(river.empty() || river == RIVER_NAMES[0])
+		return;
+	
+	rmg::Area roads;
+	if(auto * m = zone.getModificator<RoadPlacer>())
+	{
+		roads.unite(m->getRoads());
+	}
+	
+	auto movementCost = [this, &roads](const int3 & s, const int3 & d)
+	{
+		float cost = heightMap[d];
+		if(roads.contains(s))
+			cost += 1000.f; //allow road intersection, but avoid long overlaps
+		return cost;
+	};
+	
+	auto availableArea = zone.area() - prohibit;
+	
+	rmg::Path pathToSource(availableArea);
+	pathToSource.connect(source);
+	pathToSource.connect(rivers);
+	pathToSource = pathToSource.search(tile, true, movementCost);
+	
+	availableArea.subtract(pathToSource.getPathArea());
+	
+	rmg::Path pathToSink(availableArea);
+	pathToSink.connect(sink);
+	pathToSource.connect(rivers);
+	pathToSink = pathToSink.search(tile, true, movementCost);
+	
+	if(pathToSource.getPathArea().empty() || pathToSink.getPathArea().empty())
+	{
+		logGlobal->error("Cannot build river");
+		return;
+	}
+	
+	//delta placement
+	auto deltaPos = pathToSink.getPathArea() * deltaSink;
+	if(!deltaPos.empty())
+	{
+		assert(deltaPos.getTilesVector().size() == 1);
+		
+		auto pos = deltaPos.getTilesVector().front();
+		auto handler = VLC->objtypeh->getHandlerFor(RIVER_DELTA_ID, RIVER_DELTA_SUBTYPE);
+		assert(handler->isStaticObject());
+		
+		std::vector<ObjectTemplate> tmplates;
+		for(auto & temp : handler->getTemplates())
+		{
+			if(temp.canBePlacedAt(zone.getTerrainType()))
+			   tmplates.push_back(temp);
+		}
+		
+		if(tmplates.size() > 3)
+		{
+			if(tmplates.size() % 4 != 0)
+				throw rmgException(boost::to_string(boost::format("River templates for (%d,%d) at terrain %s, river %s are incorrect") % RIVER_DELTA_ID % RIVER_DELTA_SUBTYPE % zone.getTerrainType() % river));
+			
+			std::string targetTemplateName = RIVER_DELTA_TEMPLATE_NAME.at(river) + std::to_string(deltaOrientations[pos]) + ".def";
+			for(auto & templ : tmplates)
+			{
+				if(templ.animationFile == targetTemplateName)
+				{
+					auto obj = handler->create(templ);
+					rmg::Object deltaObj(*obj, deltaPositions[pos]);
+					deltaObj.finalize(map);
+				}
+			}
+		}
+	}
+	
+	rivers.unite(pathToSource.getPathArea());
+	rivers.unite(pathToSink.getPathArea());
+}

+ 48 - 0
lib/rmg/RiverPlacer.h

@@ -0,0 +1,48 @@
+/*
+ * RiverPlacer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+
+class RiverPlacer: public Modificator
+{
+public:
+	MODIFICATOR(RiverPlacer);
+	
+	void process() override;
+	void init() override;
+	char dump(const int3 &) override;
+	
+	void addRiverNode(const int3 & node);
+	
+	rmg::Area & riverSource();
+	rmg::Area & riverSink();
+	rmg::Area & riverProhibit();
+	
+protected:
+	void drawRivers();
+	
+	void preprocess();
+	void connectRiver(const int3 & tile);
+	
+	void prepareHeightmap();
+	
+private:
+	rmg::Area rivers;
+	rmg::Area source;
+	rmg::Area sink;
+	rmg::Area prohibit;
+	rmg::Tileset riverNodes;
+	rmg::Area deltaSink;
+	std::map<int3, int3> deltaPositions;
+	std::map<int3, int> deltaOrientations;
+	
+	std::map<int3, int> heightMap;
+};

+ 399 - 0
lib/rmg/RmgArea.cpp

@@ -0,0 +1,399 @@
+/*
+ * RmgArea.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 "RmgArea.h"
+#include "CMapGenerator.h"
+
+namespace rmg
+{
+
+void toAbsolute(Tileset & tiles, const int3 & position)
+{
+	Tileset temp;
+	for(auto & tile : tiles)
+	{
+		temp.insert(tile + position);
+	}
+	tiles = std::move(temp);
+}
+
+void toRelative(Tileset & tiles, const int3 & position)
+{
+	toAbsolute(tiles, -position);
+}
+
+Area::Area(const Area & area): dTiles(area.dTiles), dTotalShiftCache(area.dTotalShiftCache)
+{
+}
+
+Area::Area(const Area && area): dTiles(std::move(area.dTiles)), dTotalShiftCache(std::move(area.dTotalShiftCache))
+{
+}
+
+
+Area & Area::operator=(const Area & area)
+{
+	clear();
+	dTiles = area.dTiles;
+	dTotalShiftCache = area.dTotalShiftCache;
+	return *this;
+}
+
+Area::Area(const Tileset & tiles): dTiles(tiles)
+{
+}
+
+Area::Area(const Tileset & relative, const int3 & position): dTiles(relative), dTotalShiftCache(position)
+{
+}
+
+void Area::invalidate()
+{
+	getTiles();
+	dTilesVectorCache.clear();
+	dBorderCache.clear();
+	dBorderOutsideCache.clear();
+}
+
+bool Area::connected() const
+{
+	std::list<int3> queue({*dTiles.begin()});
+	Tileset connected = dTiles; //use invalidated cache - ok
+	while(!queue.empty())
+	{
+		auto t = queue.front();
+		connected.erase(t);
+		queue.pop_front();
+		
+		for(auto & i : int3::getDirs())
+		{
+			if(connected.count(t + i))
+			{
+				queue.push_back(t + i);
+			}
+		}
+	}
+	
+	return connected.empty();
+}
+
+std::list<Area> connectedAreas(const Area & area)
+{
+	std::list<Area> result;
+	Tileset connected = area.getTiles();
+	while(!connected.empty())
+	{
+		result.emplace_back();
+		std::list<int3> queue({*connected.begin()});
+		std::set<int3> queueSet({*connected.begin()});
+		while(!queue.empty())
+		{
+			auto t = queue.front();
+			connected.erase(t);
+			result.back().add(t);
+			queue.pop_front();
+			
+			for(auto & i : int3::getDirs())
+			{
+				auto tile = t + i;
+				if(!queueSet.count(tile) && connected.count(tile) && !result.back().contains(tile))
+				{
+					queueSet.insert(tile);
+					queue.push_back(tile);
+				}
+			}
+		}
+	}
+	return result;
+}
+
+const Tileset & Area::getTiles() const
+{
+	if(dTotalShiftCache != int3())
+	{
+		toAbsolute(dTiles, dTotalShiftCache);
+		dTotalShiftCache = int3();
+	}
+	return dTiles;
+}
+
+const std::vector<int3> & Area::getTilesVector() const
+{
+	if(dTilesVectorCache.empty())
+	{
+		getTiles();
+		dTilesVectorCache.assign(dTiles.begin(), dTiles.end());
+	}
+	return dTilesVectorCache;
+}
+
+const Tileset & Area::getBorder() const
+{
+	if(!dBorderCache.empty())
+		return dBorderCache;
+	
+	//compute border cache
+	for(auto & t : dTiles)
+	{
+		for(auto & i : int3::getDirs())
+		{
+			if(!dTiles.count(t + i))
+			{
+				dBorderCache.insert(t + dTotalShiftCache);
+				break;
+			}
+		}
+	}
+	
+	return dBorderCache;
+}
+
+const Tileset & Area::getBorderOutside() const
+{
+	if(!dBorderOutsideCache.empty())
+		return dBorderOutsideCache;
+	
+	//compute outside border cache
+	for(auto & t : dTiles)
+	{
+		for(auto & i : int3::getDirs())
+		{
+			if(!dTiles.count(t + i))
+				dBorderOutsideCache.insert(t + i + dTotalShiftCache);
+		}
+	}
+	
+	return dBorderOutsideCache;
+}
+
+DistanceMap Area::computeDistanceMap(std::map<int, Tileset> & reverseDistanceMap) const
+{
+	reverseDistanceMap.clear();
+	DistanceMap result;
+	auto area = *this;
+	int distance = 0;
+	
+	while(!area.empty())
+	{
+		for(auto & tile : area.getBorder())
+			result[tile] = distance;
+		reverseDistanceMap[distance++] = area.getBorder();
+		area.subtract(area.getBorder());
+	}
+	return result;
+}
+
+bool Area::empty() const
+{
+	return dTiles.empty();
+}
+
+bool Area::contains(const int3 & tile) const
+{
+	return dTiles.count(tile - dTotalShiftCache);
+}
+
+bool Area::contains(const std::vector<int3> & tiles) const
+{
+	for(auto & t : tiles)
+	{
+		if(!contains(t))
+			return false;
+	}
+	return true;
+}
+
+bool Area::contains(const Area & area) const
+{
+	return contains(area.getTilesVector());
+}
+
+bool Area::overlap(const std::vector<int3> & tiles) const
+{
+	for(auto & t : tiles)
+	{
+		if(contains(t))
+			return true;
+	}
+	return false;
+}
+
+bool Area::overlap(const Area & area) const
+{
+	return overlap(area.getTilesVector());
+}
+
+int Area::distanceSqr(const int3 & tile) const
+{
+	return nearest(tile).dist2dSQ(tile);
+}
+
+int Area::distanceSqr(const Area & area) const
+{
+	int dist = std::numeric_limits<int>::max();
+	int3 nearTile = *getTilesVector().begin();
+	int3 otherNearTile = area.nearest(nearTile);
+	
+	while(dist != otherNearTile.dist2dSQ(nearTile))
+	{
+		dist = otherNearTile.dist2dSQ(nearTile);
+		nearTile = nearest(otherNearTile);
+		otherNearTile = area.nearest(nearTile);
+	}
+	
+	return dist;
+}
+
+int3 Area::nearest(const int3 & tile) const
+{
+	return findClosestTile(getTilesVector(), tile);
+}
+
+int3 Area::nearest(const Area & area) const
+{
+	int dist = std::numeric_limits<int>::max();
+	int3 nearTile = *getTilesVector().begin();
+	int3 otherNearTile = area.nearest(nearTile);
+	
+	while(dist != otherNearTile.dist2dSQ(nearTile))
+	{
+		dist = otherNearTile.dist2dSQ(nearTile);
+		nearTile = nearest(otherNearTile);
+		otherNearTile = area.nearest(nearTile);
+	}
+	
+	return nearTile;
+}
+
+Area Area::getSubarea(std::function<bool(const int3 &)> filter) const
+{
+	Area subset;
+	for(auto & t : getTilesVector())
+		if(filter(t))
+			subset.add(t);
+	return subset;
+}
+
+void Area::clear()
+{
+	dTiles.clear();
+	dTotalShiftCache = int3();
+	invalidate();
+}
+
+void Area::assign(const Tileset tiles)
+{
+	clear();
+	dTiles = tiles;
+}
+
+void Area::add(const int3 & tile)
+{
+	invalidate();
+	dTiles.insert(tile);
+}
+
+void Area::erase(const int3 & tile)
+{
+	invalidate();
+	dTiles.erase(tile);
+}
+void Area::unite(const Area & area)
+{
+	invalidate();
+	for(auto & t : area.getTilesVector())
+	{
+		dTiles.insert(t);
+	}
+}
+void Area::intersect(const Area & area)
+{
+	invalidate();
+	Tileset result;
+	for(auto & t : area.getTilesVector())
+	{
+		if(dTiles.count(t))
+			result.insert(t);
+	}
+	dTiles = result;
+}
+
+void Area::subtract(const Area & area)
+{
+	invalidate();
+	for(auto & t : area.getTilesVector())
+	{
+		dTiles.erase(t);
+	}
+}
+
+void Area::translate(const int3 & shift)
+{
+	dBorderCache.clear();
+	dBorderOutsideCache.clear();
+	
+	if(dTilesVectorCache.empty())
+	{
+		getTiles();
+		getTilesVector();
+	}
+	
+	//avoid recomputation within std::set, use vector instead
+	dTotalShiftCache += shift;
+	
+	for(auto & t : dTilesVectorCache)
+	{
+		t += shift;
+	}
+	//toAbsolute(dTiles, shift);
+}
+
+Area operator- (const Area & l, const int3 & r)
+{
+	Area result(l);
+	result.translate(-r);
+	return result;
+}
+
+Area operator+ (const Area & l, const int3 & r)
+{
+	Area result(l);
+	result.translate(r);
+	return result;
+}
+
+Area operator+ (const Area & l, const Area & r)
+{
+	Area result(l);
+	result.unite(r);
+	return result;
+}
+
+Area operator- (const Area & l, const Area & r)
+{
+	Area result(l);
+	result.subtract(r);
+	return result;
+}
+
+Area operator* (const Area & l, const Area & r)
+{
+	Area result(l);
+	result.intersect(r);
+	return result;
+}
+
+bool operator== (const Area & l, const Area & r)
+{
+	return l.getTiles() == r.getTiles();
+}
+
+}

+ 83 - 0
lib/rmg/RmgArea.h

@@ -0,0 +1,83 @@
+/*
+ * RmgArea.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../GameConstants.h"
+#include "../int3.h"
+
+namespace rmg
+{
+	using Tileset = std::set<int3>;
+	using DistanceMap = std::map<int3, int>;
+	void toAbsolute(Tileset & tiles, const int3 & position);
+	void toRelative(Tileset & tiles, const int3 & position);
+	
+	class Area
+	{
+	public:
+		Area() = default;
+		Area(const Area &);
+		Area(const Area &&);
+		Area(const Tileset & tiles);
+		Area(const Tileset & relative, const int3 & position); //create from relative positions
+		Area & operator= (const Area &);
+		
+		const Tileset & getTiles() const;
+		const std::vector<int3> & getTilesVector() const;
+		const Tileset & getBorder() const; //lazy cache invalidation
+		const Tileset & getBorderOutside() const; //lazy cache invalidation
+		
+		DistanceMap computeDistanceMap(std::map<int, Tileset> & reverseDistanceMap) const;
+		
+		Area getSubarea(std::function<bool(const int3 &)> filter) const;
+		
+		bool connected() const; //is connected
+		bool empty() const;
+		bool contains(const int3 & tile) const;
+		bool contains(const std::vector<int3> & tiles) const;
+		bool contains(const Area & area) const;
+		bool overlap(const Area & area) const;
+		bool overlap(const std::vector<int3> & tiles) const;
+		int distanceSqr(const int3 & tile) const;
+		int distanceSqr(const Area & area) const;
+		int3 nearest(const int3 & tile) const;
+		int3 nearest(const Area & area) const;
+		
+		void clear();
+		void assign(const Tileset tiles); //do not use reference to allow assigment of cached data
+		void add(const int3 & tile);
+		void erase(const int3 & tile);
+		void unite(const Area & area);
+		void intersect(const Area & area);
+		void subtract(const Area & area);
+		void translate(const int3 & shift);
+		
+		friend Area operator+ (const Area & l, const int3 & r); //translation
+		friend Area operator- (const Area & l, const int3 & r); //translation
+		friend Area operator+ (const Area & l, const Area & r); //union
+		friend Area operator* (const Area & l, const Area & r); //intersection
+		friend Area operator- (const Area & l, const Area & r); //AreaL reduced by tiles from AreaR
+		friend bool operator== (const Area & l, const Area & r);
+		friend std::list<Area> connectedAreas(const Area & area);
+		
+	private:
+		
+		void invalidate();
+		void computeBorderCache();
+		void computeBorderOutsideCache();
+		
+		mutable Tileset dTiles;
+		mutable std::vector<int3> dTilesVectorCache;
+		mutable Tileset dBorderCache;
+		mutable Tileset dBorderOutsideCache;
+		mutable int3 dTotalShiftCache;
+	};
+}

+ 343 - 0
lib/rmg/RmgMap.cpp

@@ -0,0 +1,343 @@
+/*
+ * RmgMap.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 "RmgMap.h"
+#include "TileInfo.h"
+#include "CMapGenOptions.h"
+#include "Zone.h"
+#include "../mapping/CMapEditManager.h"
+#include "../CTownHandler.h"
+#include "ObjectManager.h"
+#include "RoadPlacer.h"
+#include "TreasurePlacer.h"
+#include "ConnectionsPlacer.h"
+#include "TownPlacer.h"
+#include "WaterAdopter.h"
+#include "WaterProxy.h"
+#include "WaterRoutes.h"
+#include "RockPlacer.h"
+#include "ObstaclePlacer.h"
+#include "RiverPlacer.h"
+#include "TerrainPainter.h"
+#include "Functions.h"
+#include "CMapGenerator.h"
+
+static const int3 dirs4[] = {int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0)};
+static const int3 dirsDiagonal[] = { int3(1,1,0),int3(1,-1,0),int3(-1,1,0),int3(-1,-1,0) };
+
+RmgMap::RmgMap(const CMapGenOptions& mapGenOptions) :
+	mapGenOptions(mapGenOptions), zonesTotal(0)
+{
+	mapInstance = std::make_unique<CMap>();
+	getEditManager()->getUndoManager().setUndoRedoLimit(0);
+}
+
+void RmgMap::foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo)
+{
+	for(const int3 &dir : int3::getDirs())
+	{
+		int3 n = pos + dir;
+		/*important notice: perform any translation before this function is called,
+		 so the actual mapInstance->position is checked*/
+		if(mapInstance->isInTheMap(n))
+			foo(n);
+	}
+}
+
+void RmgMap::foreachDirectNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
+{
+	for(const int3 &dir : dirs4)
+	{
+		int3 n = pos + dir;
+		if(mapInstance->isInTheMap(n))
+			foo(n);
+	}
+}
+
+void RmgMap::foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
+{
+	for (const int3 &dir : dirsDiagonal)
+	{
+		int3 n = pos + dir;
+		if (mapInstance->isInTheMap(n))
+			foo(n);
+	}
+}
+
+void RmgMap::initTiles(CMapGenerator & generator)
+{
+	mapInstance->initTerrain();
+	
+	tiles.resize(boost::extents[mapInstance->width][mapInstance->height][mapInstance->twoLevel ? 2 : 1]);
+	zoneColouring.resize(boost::extents[mapInstance->width][mapInstance->height][mapInstance->twoLevel ? 2 : 1]);
+	
+	//init native town count with 0
+	for (auto faction : VLC->townh->getAllowedFactions())
+		zonesPerFaction[faction] = 0;
+	
+	getEditManager()->clearTerrain(&generator.rand);
+	getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight()));
+	getEditManager()->drawTerrain(Terrain("grass"), &generator.rand);
+	
+	auto tmpl = mapGenOptions.getMapTemplate();
+	zones.clear();
+	for(const auto & option : tmpl->getZones())
+	{
+		auto zone = std::make_shared<Zone>(*this, generator);
+		zone->setOptions(*option.second.get());
+		zones[zone->getId()] = zone;
+	}
+	
+	switch(mapGenOptions.getWaterContent())
+	{
+		case EWaterContent::NORMAL:
+		case EWaterContent::ISLANDS:
+			TRmgTemplateZoneId waterId = zones.size() + 1;
+			rmg::ZoneOptions options;
+			options.setId(waterId);
+			options.setType(ETemplateZoneType::WATER);
+			auto zone = std::make_shared<Zone>(*this, generator);
+			zone->setOptions(options);
+			zones[zone->getId()] = zone;
+			break;
+	}
+}
+
+void RmgMap::addModificators()
+{
+	for(auto & z : getZones())
+	{
+		auto zone = z.second;
+		
+		zone->addModificator<ObjectManager>();
+		zone->addModificator<TreasurePlacer>();
+		zone->addModificator<ObstaclePlacer>();
+		zone->addModificator<TerrainPainter>();
+		
+		if(zone->getType() == ETemplateZoneType::WATER)
+		{
+			for(auto & z1 : getZones())
+			{
+				z1.second->addModificator<WaterAdopter>();
+				z1.second->getModificator<WaterAdopter>()->setWaterZone(zone->getId());
+			}
+			zone->addModificator<WaterProxy>();
+			zone->addModificator<WaterRoutes>();
+		}
+		else
+		{
+			zone->addModificator<TownPlacer>();
+			zone->addModificator<ConnectionsPlacer>();
+			zone->addModificator<RoadPlacer>();
+			zone->addModificator<RiverPlacer>();
+		}
+		
+		if(zone->isUnderground())
+		{
+			zone->addModificator<RockPlacer>();
+		}
+		
+	}
+}
+
+RmgMap::~RmgMap()
+{
+}
+
+CMap & RmgMap::map() const
+{
+	return *mapInstance;
+}
+
+CMapEditManager* RmgMap::getEditManager() const
+{
+	return mapInstance->getEditManager();
+}
+
+bool RmgMap::isOnMap(const int3 & tile) const
+{
+	return mapInstance->isInTheMap(tile);
+}
+
+const CMapGenOptions& RmgMap::getMapGenOptions() const
+{
+	return mapGenOptions;
+}
+
+void RmgMap::assertOnMap(const int3& tile) const
+{
+	if (!mapInstance->isInTheMap(tile))
+		throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile.toString()));
+}
+
+RmgMap::Zones & RmgMap::getZones()
+{
+	return zones;
+}
+
+bool RmgMap::isBlocked(const int3 &tile) const
+{
+	assertOnMap(tile);
+	
+	return tiles[tile.x][tile.y][tile.z].isBlocked();
+}
+
+bool RmgMap::shouldBeBlocked(const int3 &tile) const
+{
+	assertOnMap(tile);
+	
+	return tiles[tile.x][tile.y][tile.z].shouldBeBlocked();
+}
+
+bool RmgMap::isPossible(const int3 &tile) const
+{
+	assertOnMap(tile);
+	
+	return tiles[tile.x][tile.y][tile.z].isPossible();
+}
+
+bool RmgMap::isFree(const int3 &tile) const
+{
+	assertOnMap(tile);
+	
+	return tiles[tile.x][tile.y][tile.z].isFree();
+}
+
+bool RmgMap::isUsed(const int3 &tile) const
+{
+	assertOnMap(tile);
+	
+	return tiles[tile.x][tile.y][tile.z].isUsed();
+}
+
+bool RmgMap::isRoad(const int3& tile) const
+{
+	assertOnMap(tile);
+	
+	return tiles[tile.x][tile.y][tile.z].isRoad();
+}
+
+void RmgMap::setOccupied(const int3 &tile, ETileType::ETileType state)
+{
+	assertOnMap(tile);
+	
+	tiles[tile.x][tile.y][tile.z].setOccupied(state);
+}
+
+void RmgMap::setRoad(const int3& tile, const std::string & roadType)
+{
+	assertOnMap(tile);
+	
+	tiles[tile.x][tile.y][tile.z].setRoadType(roadType);
+}
+
+TileInfo RmgMap::getTile(const int3& tile) const
+{
+	assertOnMap(tile);
+	
+	return tiles[tile.x][tile.y][tile.z];
+}
+
+TRmgTemplateZoneId RmgMap::getZoneID(const int3& tile) const
+{
+	assertOnMap(tile);
+	
+	return zoneColouring[tile.x][tile.y][tile.z];
+}
+
+void RmgMap::setZoneID(const int3& tile, TRmgTemplateZoneId zid)
+{
+	assertOnMap(tile);
+	
+	zoneColouring[tile.x][tile.y][tile.z] = zid;
+}
+
+void RmgMap::setNearestObjectDistance(int3 &tile, float value)
+{
+	assertOnMap(tile);
+	
+	tiles[tile.x][tile.y][tile.z].setNearestObjectDistance(value);
+}
+
+float RmgMap::getNearestObjectDistance(const int3 &tile) const
+{
+	assertOnMap(tile);
+	
+	return tiles[tile.x][tile.y][tile.z].getNearestObjectDistance();
+}
+
+void RmgMap::registerZone(TFaction faction)
+{
+	zonesPerFaction[faction]++;
+	zonesTotal++;
+}
+
+ui32 RmgMap::getZoneCount(TFaction faction)
+{
+	return zonesPerFaction[faction];
+}
+
+ui32 RmgMap::getTotalZoneCount() const
+{
+	return zonesTotal;
+}
+
+bool RmgMap::isAllowedSpell(SpellID sid) const
+{
+	assert(sid >= 0);
+	if (sid < mapInstance->allowedSpell.size())
+	{
+		return mapInstance->allowedSpell[sid];
+	}
+	else
+		return false;
+}
+
+void RmgMap::dump(bool zoneId) const
+{
+	static int id = 0;
+	std::ofstream out(boost::to_string(boost::format("zone_%d.txt") % id++));
+	int levels = mapInstance->twoLevel ? 2 : 1;
+	int width =  mapInstance->width;
+	int height = mapInstance->height;
+	for (int k = 0; k < levels; k++)
+	{
+		for(int j = 0; j < height; j++)
+		{
+			for (int i = 0; i < width; i++)
+			{
+				if(zoneId)
+				{
+					out << getZoneID(int3(i, j, k));
+				}
+				else
+				{
+					char t = '?';
+					switch (getTile(int3(i, j, k)).getTileType())
+					{
+						case ETileType::FREE:
+							t = ' '; break;
+						case ETileType::BLOCKED:
+							t = '#'; break;
+						case ETileType::POSSIBLE:
+							t = '-'; break;
+						case ETileType::USED:
+							t = 'O'; break;
+					}
+					out << t;
+				}
+			}
+			out << std::endl;
+		}
+		out << std::endl;
+	}
+	out << std::endl;
+}

+ 81 - 0
lib/rmg/RmgMap.h

@@ -0,0 +1,81 @@
+/*
+ * RmgMap.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
+ *
+ */
+
+#pragma once
+#include "../int3.h"
+#include "../GameConstants.h"
+#include "../mapping/CMap.h"
+
+class CMapEditManager;
+class TileInfo;
+class CMapGenOptions;
+class Zone;
+class CMapGenerator;
+
+class RmgMap
+{
+public:
+	mutable std::unique_ptr<CMap> mapInstance;
+	CMap & map() const;
+	
+	RmgMap(const CMapGenOptions& mapGenOptions);
+	~RmgMap();
+	
+	CMapEditManager* getEditManager() const;
+	const CMapGenOptions& getMapGenOptions() const;
+	
+	void foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo);
+	void foreachDirectNeighbour(const int3 &pos, std::function<void(int3& pos)> foo);
+	void foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo);
+	
+	bool isBlocked(const int3 &tile) const;
+	bool shouldBeBlocked(const int3 &tile) const;
+	bool isPossible(const int3 &tile) const;
+	bool isFree(const int3 &tile) const;
+	bool isUsed(const int3 &tile) const;
+	bool isRoad(const int3 &tile) const;
+	bool isOnMap(const int3 & tile) const;
+	
+	void setOccupied(const int3 &tile, ETileType::ETileType state);
+	void setRoad(const int3 &tile, const std::string & roadType);
+	
+	TileInfo getTile(const int3 & tile) const;
+		
+	float getNearestObjectDistance(const int3 &tile) const;
+	void setNearestObjectDistance(int3 &tile, float value);
+	
+	TRmgTemplateZoneId getZoneID(const int3& tile) const;
+	void setZoneID(const int3& tile, TRmgTemplateZoneId zid);
+	
+	using Zones = std::map<TRmgTemplateZoneId, std::shared_ptr<Zone>>;
+	
+	Zones & getZones();
+	
+	void registerZone(TFaction faction);
+	ui32 getZoneCount(TFaction faction);
+	ui32 getTotalZoneCount() const;
+	void initTiles(CMapGenerator & generator);
+	void addModificators();
+	
+	bool isAllowedSpell(SpellID sid) const;
+	
+	void dump(bool zoneId) const;
+	
+private:
+	void assertOnMap(const int3 &tile) const; //throws
+	
+private:
+	Zones zones;
+	std::map<TFaction, ui32> zonesPerFaction;
+	ui32 zonesTotal; //zones that have their main town only
+	const CMapGenOptions& mapGenOptions;
+	boost::multi_array<TileInfo, 3> tiles; //[x][y][z]
+	boost::multi_array<TRmgTemplateZoneId, 3> zoneColouring; //[x][y][z]
+};

+ 327 - 0
lib/rmg/RmgObject.cpp

@@ -0,0 +1,327 @@
+/*
+ * RmgObject.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 "RmgObject.h"
+#include "RmgMap.h"
+#include "../mapObjects/CObjectHandler.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapping/CMap.h"
+#include "../VCMI_Lib.h"
+#include "../mapObjects/CommonConstructors.h"
+#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
+#include "Functions.h"
+
+using namespace rmg;
+
+Object::Instance::Instance(const Object& parent, CGObjectInstance & object): dParent(parent), dObject(object)
+{
+	setPosition(dPosition);
+}
+
+Object::Instance::Instance(const Object& parent, CGObjectInstance & object, const int3 & position): Instance(parent, object)
+{
+	setPosition(position);
+}
+
+const Area & Object::Instance::getBlockedArea() const
+{
+	if(dBlockedAreaCache.empty())
+	{
+		dBlockedAreaCache.assign(dObject.getBlockedPos());
+		if(dObject.isVisitable() || dBlockedAreaCache.empty())
+			dBlockedAreaCache.add(dObject.visitablePos());
+	}
+	return dBlockedAreaCache;
+}
+
+int3 Object::Instance::getPosition(bool isAbsolute) const
+{
+	if(isAbsolute)
+		return dPosition + dParent.getPosition();
+	else
+		return dPosition;
+}
+
+int3 Object::Instance::getVisitablePosition() const
+{
+	return dObject.visitablePos();
+}
+
+const rmg::Area & Object::Instance::getAccessibleArea() const
+{
+	if(dAccessibleAreaCache.empty())
+	{
+		auto neighbours = rmg::Area({getVisitablePosition()}).getBorderOutside();
+		rmg::Area visitable = rmg::Area(neighbours) - getBlockedArea();
+		for(auto & from : visitable.getTiles())
+		{
+			if(isVisitableFrom(from))
+				dAccessibleAreaCache.add(from);
+		}
+	}
+	return dAccessibleAreaCache;
+}
+
+void Object::Instance::setPosition(const int3 & position)
+{
+	dPosition = position;
+	dObject.pos = dPosition + dParent.getPosition();
+	
+	dBlockedAreaCache.clear();
+	dAccessibleAreaCache.clear();
+	dParent.dAccessibleAreaCache.clear();
+	dParent.dAccessibleAreaFullCache.clear();
+	dParent.dFullAreaCache.clear();
+}
+
+void Object::Instance::setPositionRaw(const int3 & position)
+{
+	if(!dObject.pos.valid())
+	{
+		dObject.pos = dPosition + dParent.getPosition();
+		dBlockedAreaCache.clear();
+		dAccessibleAreaCache.clear();
+		dParent.dAccessibleAreaCache.clear();
+		dParent.dAccessibleAreaFullCache.clear();
+		dParent.dFullAreaCache.clear();
+	}
+		
+	auto shift = position + dParent.getPosition() - dObject.pos;
+	
+	dAccessibleAreaCache.translate(shift);
+	dBlockedAreaCache.translate(shift);
+	
+	dPosition = position;
+	dObject.pos = dPosition + dParent.getPosition();
+}
+
+void Object::Instance::setTemplate(const Terrain & terrain)
+{
+	if(dObject.appearance.id == Obj::NO_OBJ)
+	{
+		auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain);
+		if(templates.empty())
+			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % static_cast<std::string>(terrain)));
+		
+		dObject.appearance = templates.front();
+	}
+	dAccessibleAreaCache.clear();
+	setPosition(getPosition(false));
+}
+
+void Object::Instance::clear()
+{
+	delete &dObject;
+	dBlockedAreaCache.clear();
+	dAccessibleAreaCache.clear();
+	dParent.dAccessibleAreaCache.clear();
+	dParent.dAccessibleAreaFullCache.clear();
+	dParent.dFullAreaCache.clear();
+}
+
+bool Object::Instance::isVisitableFrom(const int3 & position) const
+{
+	auto relPosition = position - getPosition(true);
+	return dObject.appearance.isVisitableFrom(relPosition.x, relPosition.y);
+}
+
+CGObjectInstance & Object::Instance::object()
+{
+	return dObject;
+}
+
+const CGObjectInstance & Object::Instance::object() const
+{
+	return dObject;
+}
+
+Object::Object(CGObjectInstance & object, const int3 & position)
+{
+	addInstance(object, position);
+}
+
+Object::Object(CGObjectInstance & object)
+{
+	addInstance(object);
+}
+
+Object::Object(const Object & object)
+{
+	dStrenght = object.dStrenght;
+	for(auto & i : object.dInstances)
+		addInstance(const_cast<CGObjectInstance &>(i.object()), i.getPosition());
+	setPosition(object.getPosition());
+}
+
+std::list<Object::Instance*> Object::instances()
+{
+	std::list<Object::Instance*> result;
+	for(auto & i : dInstances)
+		result.push_back(&i);
+	return result;
+}
+
+std::list<const Object::Instance*> Object::instances() const
+{
+	std::list<const Object::Instance*> result;
+	for(const auto & i : dInstances)
+		result.push_back(&i);
+	return result;
+}
+
+void Object::addInstance(Instance & object)
+{
+	//assert(object.dParent == *this);
+	dInstances.push_back(object);
+	dFullAreaCache.clear();
+	dAccessibleAreaCache.clear();
+	dAccessibleAreaFullCache.clear();
+}
+
+Object::Instance & Object::addInstance(CGObjectInstance & object)
+{
+	dInstances.emplace_back(*this, object);
+	dFullAreaCache.clear();
+	dAccessibleAreaCache.clear();
+	dAccessibleAreaFullCache.clear();
+	return dInstances.back();
+}
+
+Object::Instance & Object::addInstance(CGObjectInstance & object, const int3 & position)
+{
+	dInstances.emplace_back(*this, object, position);
+	dFullAreaCache.clear();
+	dAccessibleAreaCache.clear();
+	dAccessibleAreaFullCache.clear();
+	return dInstances.back();
+}
+
+const int3 & Object::getPosition() const
+{
+	return dPosition;
+}
+
+int3 Object::getVisitablePosition() const
+{
+	assert(!dInstances.empty());
+	for(auto & instance : dInstances)
+		if(!getArea().contains(instance.getVisitablePosition()))
+			return instance.getVisitablePosition();
+	
+	return dInstances.back().getVisitablePosition(); //fallback - return position of last object
+}
+
+const rmg::Area & Object::getAccessibleArea(bool exceptLast) const
+{
+	if(dInstances.empty())
+		return dAccessibleAreaFullCache;
+	if(exceptLast && !dAccessibleAreaCache.empty())
+		return dAccessibleAreaCache;
+	if(!exceptLast && !dAccessibleAreaFullCache.empty())
+		return dAccessibleAreaFullCache;
+	
+	for(auto i = dInstances.begin(); i != std::prev(dInstances.end()); ++i)
+		dAccessibleAreaCache.unite(i->getAccessibleArea());
+	
+	dAccessibleAreaFullCache = dAccessibleAreaCache;
+	dAccessibleAreaFullCache.unite(dInstances.back().getAccessibleArea());
+	dAccessibleAreaCache.subtract(getArea());
+	dAccessibleAreaFullCache.subtract(getArea());
+	
+	if(exceptLast)
+		return dAccessibleAreaCache;
+	else
+		return dAccessibleAreaFullCache;
+}
+
+void Object::setPosition(const int3 & position)
+{
+	dAccessibleAreaCache.translate(position - dPosition);
+	dAccessibleAreaFullCache.translate(position - dPosition);
+	dFullAreaCache.translate(position - dPosition);
+	
+	dPosition = position;
+	for(auto& i : dInstances)
+		i.setPositionRaw(i.getPosition());
+}
+
+void Object::setTemplate(const Terrain & terrain)
+{
+	for(auto& i : dInstances)
+		i.setTemplate(terrain);
+}
+
+const Area & Object::getArea() const
+{
+	if(!dFullAreaCache.empty() || dInstances.empty())
+		return dFullAreaCache;
+	
+	for(const auto & instance : dInstances)
+	{
+		dFullAreaCache.unite(instance.getBlockedArea());
+	}
+	
+	return dFullAreaCache;
+}
+
+void Object::Instance::finalize(RmgMap & map)
+{
+	if(!map.isOnMap(getPosition(true)))
+		throw rmgException(boost::to_string(boost::format("Position of object %d at %s is outside the map") % dObject.id % getPosition(true).toString()));
+	
+	if (dObject.isVisitable() && !map.isOnMap(dObject.visitablePos()))
+		throw rmgException(boost::to_string(boost::format("Visitable tile %s of object %d at %s is outside the map") % dObject.visitablePos().toString() % dObject.id % dObject.pos.toString()));
+	
+	for (auto & tile : dObject.getBlockedPos())
+	{
+		if(!map.isOnMap(tile))
+			throw rmgException(boost::to_string(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % dObject.id % dObject.pos.toString()));
+	}
+	
+	if (dObject.appearance.id == Obj::NO_OBJ)
+	{
+		auto terrainType = map.map().getTile(getPosition(true)).terType;
+		auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType);
+		if (templates.empty())
+			throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % dObject.ID % dObject.subID % getPosition(true).toString() % terrainType));
+		
+		setTemplate(terrainType);
+	}
+	
+	for(auto & tile : getBlockedArea().getTilesVector())
+	{
+		map.setOccupied(tile, ETileType::ETileType::USED);
+	}
+	
+	map.getEditManager()->insertObject(&dObject);
+}
+
+void Object::finalize(RmgMap & map)
+{
+	if(dInstances.empty())
+		throw rmgException("Cannot finalize object without instances");
+	
+	for(auto iter = dInstances.begin(); iter != dInstances.end(); ++iter)
+	{
+		iter->finalize(map);
+	}
+}
+
+void Object::clear()
+{
+	for(auto & instance : dInstances)
+		instance.clear();
+	dInstances.clear();
+	dFullAreaCache.clear();
+	dAccessibleAreaCache.clear();
+	dAccessibleAreaFullCache.clear();
+}
+ 

+ 87 - 0
lib/rmg/RmgObject.h

@@ -0,0 +1,87 @@
+/*
+ * RmgObject.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../GameConstants.h"
+#include "../int3.h"
+#include "RmgArea.h"
+
+class CGObjectInstance;
+class RmgMap;
+class Terrain;
+
+namespace rmg {
+class Object
+{
+public:
+	
+	class Instance
+	{
+	public:
+		Instance(const Object& parent, CGObjectInstance & object);
+		Instance(const Object& parent, CGObjectInstance & object, const int3 & position);
+		
+		const Area & getBlockedArea() const;
+		
+		int3 getVisitablePosition() const;
+		bool isVisitableFrom(const int3 & tile) const;
+		const Area & getAccessibleArea() const;
+		void setTemplate(const Terrain & terrain); //cache invalidation
+		
+		int3 getPosition(bool isAbsolute = false) const;
+		void setPosition(const int3 & position); //cache invalidation
+		void setPositionRaw(const int3 & position); //no cache invalidation
+		const CGObjectInstance & object() const;
+		CGObjectInstance & object();
+		
+		void finalize(RmgMap & map); //cache invalidation
+		void clear();
+		
+	private:
+		mutable Area dBlockedAreaCache;
+		int3 dPosition;
+		mutable Area dAccessibleAreaCache;
+		CGObjectInstance & dObject;
+		const Object & dParent;
+	};
+	
+	Object() = default;
+	Object(const Object & object);
+	Object(CGObjectInstance & object);
+	Object(CGObjectInstance & object, const int3 & position);
+	
+	void addInstance(Instance & object);
+	Instance & addInstance(CGObjectInstance & object);
+	Instance & addInstance(CGObjectInstance & object, const int3 & position);
+	
+	std::list<Instance*> instances();
+	std::list<const Instance*> instances() const;
+	
+	int3 getVisitablePosition() const;
+	const Area & getAccessibleArea(bool exceptLast = false) const;
+	
+	const int3 & getPosition() const;
+	void setPosition(const int3 & position);
+	void setTemplate(const Terrain & terrain);
+	
+	const Area & getArea() const;  //lazy cache invalidation
+	
+	void finalize(RmgMap & map);
+	void clear();
+	
+private:
+	std::list<Instance> dInstances;
+	mutable Area dFullAreaCache;
+	mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache;
+	int3 dPosition;
+	ui32 dStrenght;
+};
+}

+ 193 - 0
lib/rmg/RmgPath.cpp

@@ -0,0 +1,193 @@
+/*
+ * RmgPath.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 "RmgPath.h"
+#include <boost/heap/priority_queue.hpp> //A*
+
+using namespace rmg;
+
+const std::function<float(const int3 &, const int3 &)> Path::DEFAULT_MOVEMENT_FUNCTION =
+[](const int3 & src, const int3 & dst)
+{	
+	return 1.f;
+};
+
+//A* priority queue
+typedef std::pair<int3, float> TDistance;
+struct NodeComparer
+{
+	bool operator()(const TDistance & lhs, const TDistance & rhs) const
+	{
+		return (rhs.second < lhs.second);
+	}
+};
+boost::heap::priority_queue<TDistance, boost::heap::compare<NodeComparer>> createPriorityQueue()
+{
+	return boost::heap::priority_queue<TDistance, boost::heap::compare<NodeComparer>>();
+}
+
+Path::Path(const Area & area): dArea(&area)
+{
+}
+
+Path::Path(const Area & area, const int3 & src): dArea(&area)
+{
+	dPath.add(src);
+}
+
+Path::Path(const Path & path): dArea(path.dArea), dPath(path.dPath)
+{
+	
+}
+
+Path & Path::operator= (const Path & path)
+{
+	//do not modify area
+	dPath = path.dPath;
+	return *this;
+}
+
+bool Path::valid() const
+{
+	return !dPath.empty();
+}
+
+Path Path::invalid()
+{
+	return Path({});
+}
+
+Path Path::search(const Tileset & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction) const
+{
+	//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
+	if(!dArea)
+		return Path::invalid();
+	
+	auto resultArea = *dArea + dst;
+	Path result(resultArea);
+	if(dst.empty())
+		return result;
+	
+	int3 src = rmg::Area(dst).nearest(dPath);
+	result.connect(src);
+	
+	Tileset closed;    // The set of nodes already evaluated.
+	auto open = createPriorityQueue(); // The set of tentative nodes to be evaluated, initially containing the start node
+	std::map<int3, int3> cameFrom;  // The map of navigated nodes.
+	std::map<int3, float> distances;
+	
+	cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition
+	distances[src] = 0;
+	open.push(std::make_pair(src, 0.f));
+	// Cost from start along best known path.
+	
+	while(!open.empty())
+	{
+		auto node = open.top();
+		open.pop();
+		int3 currentNode = node.first;
+		
+		closed.insert(currentNode);
+		
+		if(dPath.contains(currentNode)) //we reached connection, stop
+		{
+			// Trace the path using the saved parent information and return path
+			int3 backTracking = currentNode;
+			while (cameFrom[backTracking].valid())
+			{
+				result.dPath.add(backTracking);
+				backTracking = cameFrom[backTracking];
+			}
+			return result;
+		}
+		else
+		{
+			auto computeTileScore = [&open, &closed, &cameFrom, &currentNode, &distances, &moveCostFunction, &result](const int3& pos) -> void
+			{
+				if(closed.count(pos))
+					return;
+				
+				if(!result.dArea->contains(pos))
+					return;
+				
+				float movementCost = moveCostFunction(currentNode, pos) + currentNode.dist2d(pos);
+				
+				float distance = distances[currentNode] + movementCost; //we prefer to use already free paths
+				int bestDistanceSoFar = std::numeric_limits<int>::max();
+				auto it = distances.find(pos);
+				if(it != distances.end())
+					bestDistanceSoFar = static_cast<int>(it->second);
+				
+				if(distance < bestDistanceSoFar)
+				{
+					cameFrom[pos] = currentNode;
+					open.push(std::make_pair(pos, distance));
+					distances[pos] = distance;
+				}
+			};
+			
+			auto dirs = int3::getDirs();
+			std::vector<int3> neighbors(dirs.begin(), dirs.end());
+			if(straight)
+				neighbors = { { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0) } };
+			for(auto & i : neighbors)
+			{
+				computeTileScore(currentNode + i);
+			}
+		}
+		
+	}
+	
+	result.dPath.clear();
+	return result;
+}
+
+Path Path::search(const int3 & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction) const
+{
+	return search(Tileset{dst}, straight, moveCostFunction);
+}
+
+Path Path::search(const Area & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction) const
+{
+	return search(dst.getTiles(), straight, moveCostFunction);
+}
+
+Path Path::search(const Path & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction) const
+{
+	assert(dst.dArea == dArea);
+	return search(dst.dPath, straight, moveCostFunction);
+}
+
+void Path::connect(const int3 & path)
+{
+	dPath.add(path);
+}
+
+void Path::connect(const Tileset & path)
+{
+	Area a(path);
+	dPath.unite(a);
+}
+
+void Path::connect(const Area & path)
+{
+	dPath.unite(path);
+}
+
+void Path::connect(const Path & path)
+{
+	dPath.unite(path.dPath);
+}
+
+const Area & Path::getPathArea() const
+{
+	return dPath;
+}

+ 49 - 0
lib/rmg/RmgPath.h

@@ -0,0 +1,49 @@
+/*
+ * RmgPath.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../GameConstants.h"
+#include "../int3.h"
+#include "RmgArea.h"
+
+namespace rmg
+{
+class Path
+{
+public:
+	const static std::function<float(const int3 &, const int3 &)> DEFAULT_MOVEMENT_FUNCTION;
+	
+	Path(const Area & area);
+	Path(const Area & area, const int3 & src);
+	Path(const Path & path);
+	Path & operator= (const Path & path);
+	bool valid() const;
+	
+	Path search(const Tileset & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const;
+	Path search(const int3 & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const;
+	Path search(const Area & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const;
+	Path search(const Path & dst, bool straight, std::function<float(const int3 &, const int3 &)> moveCostFunction = DEFAULT_MOVEMENT_FUNCTION) const;
+	
+	void connect(const Path & path);
+	void connect(const int3 & path); //TODO: force connection?
+	void connect(const Area & path); //TODO: force connection?
+	void connect(const Tileset & path); //TODO: force connection?
+	
+	const Area & getPathArea() const;
+	
+	static Path invalid();
+	
+private:
+	
+	const Area * dArea = nullptr;
+	Area dPath;
+};
+}

+ 105 - 0
lib/rmg/RoadPlacer.cpp

@@ -0,0 +1,105 @@
+/*
+ * RoadPlacer.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 "RoadPlacer.h"
+#include "Functions.h"
+#include "CMapGenerator.h"
+#include "RmgMap.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "RmgPath.h"
+
+void RoadPlacer::process()
+{
+	connectRoads();
+}
+
+rmg::Area & RoadPlacer::areaForRoads()
+{
+	return areaRoads;
+}
+
+rmg::Area & RoadPlacer::areaIsolated()
+{
+	return isolated;
+}
+
+const rmg::Area & RoadPlacer::getRoads() const
+{
+	return roads;
+}
+
+bool RoadPlacer::createRoad(const int3 & dst)
+{
+	auto searchArea = zone.areaPossible() + areaRoads + zone.freePaths() - isolated + roads;
+	
+	rmg::Path path(searchArea);
+	path.connect(roads);
+	
+	auto res = path.search(dst, true);
+	if(!res.valid())
+	{
+		res = path.search(dst, false, [](const int3 & src, const int3 & dst)
+		{
+			float weight = dst.dist2dSQ(src);
+			return weight * weight;
+		});
+		if(!res.valid())
+		{
+			logGlobal->warn("Failed to create road");
+			return false;
+		}
+	}
+	roads.unite(res.getPathArea());
+	return true;
+	
+}
+
+void RoadPlacer::drawRoads()
+{
+	zone.areaPossible().subtract(roads);
+	zone.freePaths().unite(roads);
+	map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector());
+	map.getEditManager()->drawRoad(generator.getConfig().defaultRoadType, &generator.rand);
+}
+
+void RoadPlacer::addRoadNode(const int3& node)
+{
+	roadNodes.insert(node);
+}
+
+void RoadPlacer::connectRoads()
+{
+	if(roadNodes.empty())
+		return;
+	
+	//take any tile from road nodes as destination zone for all other road nodes
+	if(roads.empty())
+		roads.add(*roadNodes.begin());
+	
+	for(auto & node : roadNodes)
+	{
+		createRoad(node);
+	}
+	
+	drawRoads();
+}
+
+char RoadPlacer::dump(const int3 & t)
+{
+	if(roadNodes.count(t))
+		return '@';
+	if(roads.contains(t))
+		return '+';
+	if(isolated.contains(t))
+		return 'i';
+	return Modificator::dump(t);
+}

+ 37 - 0
lib/rmg/RoadPlacer.h

@@ -0,0 +1,37 @@
+/*
+ * RoadPlacer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+
+class RoadPlacer: public Modificator
+{
+public:
+	MODIFICATOR(RoadPlacer);
+	
+	void process() override;
+	char dump(const int3 &) override;
+	
+	void addRoadNode(const int3 & node);
+	void connectRoads(); //fills "roads" according to "roadNodes"
+	
+	rmg::Area & areaForRoads();
+	rmg::Area & areaIsolated();
+	const rmg::Area & getRoads() const;
+	
+protected:
+	bool createRoad(const int3 & dst);
+	void drawRoads(); //actually updates tiles
+	
+protected:
+	rmg::Tileset roadNodes; //tiles to be connected with roads
+	rmg::Area roads; //all tiles with roads
+	rmg::Area areaRoads, isolated;
+};

+ 103 - 0
lib/rmg/RockPlacer.cpp

@@ -0,0 +1,103 @@
+/*
+ * RockPlacer.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 "RockPlacer.h"
+#include "TreasurePlacer.h"
+#include "ObjectManager.h"
+#include "RoadPlacer.h"
+#include "RiverPlacer.h"
+#include "RmgMap.h"
+#include "CMapGenerator.h"
+#include "Functions.h"
+#include "../CRandomGenerator.h"
+#include "../mapping/CMapEditManager.h"
+
+void RockPlacer::process()
+{
+	rockTerrain = Terrain::Manager::getInfo(zone.getTerrainType()).rockTerrain;
+	assert(!rockTerrain.isPassable());
+	
+	accessibleArea = zone.freePaths() + zone.areaUsed();
+	if(auto * m = zone.getModificator<ObjectManager>())
+		accessibleArea.unite(m->getVisitableArea());
+	
+	//negative approach - create rock tiles first, then make sure all accessible tiles have no rock
+	rockArea = zone.area().getSubarea([this](const int3 & t)
+	{
+		return map.shouldBeBlocked(t);
+	});
+	
+	for(auto & z : map.getZones())
+	{
+		if(auto * m = z.second->getModificator<RockPlacer>())
+		{
+			if(m != this && !m->isFinished())
+				return;
+		}
+	}
+	
+	processMap();
+}
+
+void RockPlacer::processMap()
+{
+	//merge all areas
+	for(auto & z : map.getZones())
+	{
+		if(auto * m = z.second->getModificator<RockPlacer>())
+		{
+			map.getEditManager()->getTerrainSelection().setSelection(m->rockArea.getTilesVector());
+			map.getEditManager()->drawTerrain(m->rockTerrain, &generator.rand);
+		}
+	}
+	
+	for(auto & z : map.getZones())
+	{
+		if(auto * m = z.second->getModificator<RockPlacer>())
+		{
+			//now make sure all accessible tiles have no additional rock on them
+			map.getEditManager()->getTerrainSelection().setSelection(m->accessibleArea.getTilesVector());
+			map.getEditManager()->drawTerrain(z.second->getTerrainType(), &generator.rand);
+			m->postProcess();
+		}
+	}
+}
+
+void RockPlacer::postProcess()
+{
+	//finally mark rock tiles as occupied, spawn no obstacles there
+	rockArea = zone.area().getSubarea([this](const int3 & t)
+	{
+		return !map.map().getTile(t).terType.isPassable();
+	});
+	
+	zone.areaUsed().unite(rockArea);
+	zone.areaPossible().subtract(rockArea);
+	if(auto * m = zone.getModificator<RiverPlacer>())
+		m->riverProhibit().unite(rockArea);
+	if(auto * m = zone.getModificator<RoadPlacer>())
+		m->areaIsolated().unite(rockArea);
+}
+
+void RockPlacer::init()
+{
+	POSTFUNCTION_ALL(RoadPlacer);
+	DEPENDENCY(TreasurePlacer);
+}
+
+char RockPlacer::dump(const int3 & t)
+{
+	if(!map.map().getTile(t).terType.isPassable())
+	{
+		return zone.area().contains(t) ? 'R' : 'E';
+	}
+	return Modificator::dump(t);
+}

+ 30 - 0
lib/rmg/RockPlacer.h

@@ -0,0 +1,30 @@
+/*
+ * RockPlacer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+
+class RockPlacer: public Modificator
+{
+public:
+	MODIFICATOR(RockPlacer);
+	
+	void process() override;
+	void init() override;
+	char dump(const int3 &) override;
+	
+	void processMap();
+	void postProcess();
+	
+protected:
+	
+	rmg::Area rockArea, accessibleArea;
+	Terrain rockTerrain;
+};

+ 35 - 0
lib/rmg/TerrainPainter.cpp

@@ -0,0 +1,35 @@
+/*
+ * TerrainPainter.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 "TerrainPainter.h"
+#include "TownPlacer.h"
+#include "WaterAdopter.h"
+#include "WaterProxy.h"
+#include "ConnectionsPlacer.h"
+#include "ObjectManager.h"
+#include "Functions.h"
+#include "CMapGenerator.h"
+#include "RmgMap.h"
+
+void TerrainPainter::process()
+{
+	initTerrainType(zone, generator);
+	paintZoneTerrain(zone, generator.rand, map, zone.getTerrainType());
+}
+
+void TerrainPainter::init()
+{
+	DEPENDENCY(TownPlacer);
+	DEPENDENCY_ALL(WaterAdopter);
+	POSTFUNCTION_ALL(WaterProxy);
+	POSTFUNCTION_ALL(ConnectionsPlacer);
+	POSTFUNCTION(ObjectManager);
+}

+ 21 - 0
lib/rmg/TerrainPainter.h

@@ -0,0 +1,21 @@
+/*
+ * TerrainPainter.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+
+class TerrainPainter: public Modificator
+{
+public:
+	MODIFICATOR(TerrainPainter);
+	
+	void process() override;
+	void init() override;
+};

+ 78 - 0
lib/rmg/TileInfo.cpp

@@ -0,0 +1,78 @@
+/*
+ * TileInfo.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 "TileInfo.h"
+
+TileInfo::TileInfo():nearestObjectDistance(float(INT_MAX)), terrain()
+{
+	occupied = ETileType::POSSIBLE; //all tiles are initially possible to place objects or passages
+}
+
+float TileInfo::getNearestObjectDistance() const
+{
+	return nearestObjectDistance;
+}
+
+void TileInfo::setNearestObjectDistance(float value)
+{
+	nearestObjectDistance = std::max<float>(0, value); //never negative (or unitialized)
+}
+bool TileInfo::shouldBeBlocked() const
+{
+	return occupied == ETileType::BLOCKED;
+}
+bool TileInfo::isBlocked() const
+{
+	return occupied == ETileType::BLOCKED || occupied == ETileType::USED;
+}
+bool TileInfo::isPossible() const
+{
+	return occupied == ETileType::POSSIBLE;
+}
+bool TileInfo::isFree() const
+{
+	return occupied == ETileType::FREE;
+}
+
+bool TileInfo::isRoad() const
+{
+	return roadType != ROAD_NAMES[0];
+}
+
+bool TileInfo::isUsed() const
+{
+	return occupied == ETileType::USED;
+}
+void TileInfo::setOccupied(ETileType::ETileType value)
+{
+	occupied = value;
+}
+
+ETileType::ETileType TileInfo::getTileType() const
+{
+	return occupied;
+}
+
+Terrain TileInfo::getTerrainType() const
+{
+	return terrain;
+}
+
+void TileInfo::setTerrainType(Terrain value)
+{
+	terrain = value;
+}
+
+void TileInfo::setRoadType(const std::string & value)
+{
+	roadType = value;
+	//	setOccupied(ETileType::FREE);
+}

+ 41 - 0
lib/rmg/TileInfo.h

@@ -0,0 +1,41 @@
+/*
+ * TileInfo.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../GameConstants.h"
+#include "../Terrain.h"
+
+class TileInfo
+{
+public:
+	
+	TileInfo();
+	
+	float getNearestObjectDistance() const;
+	void setNearestObjectDistance(float value);
+	bool isBlocked() const;
+	bool shouldBeBlocked() const;
+	bool isPossible() const;
+	bool isFree() const;
+	bool isUsed() const;
+	bool isRoad() const;
+	void setOccupied(ETileType::ETileType value);
+	Terrain getTerrainType() const;
+	ETileType::ETileType getTileType() const;
+	void setTerrainType(Terrain value);
+	
+	void setRoadType(const std::string & value);
+private:
+	float nearestObjectDistance;
+	ETileType::ETileType occupied;
+	Terrain terrain;
+	std::string roadType;
+};

+ 279 - 0
lib/rmg/TownPlacer.cpp

@@ -0,0 +1,279 @@
+/*
+ * TownPlacer.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 "TownPlacer.h"
+#include "CMapGenerator.h"
+#include "RmgMap.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "../spells/CSpellHandler.h" //for choosing random spells
+#include "RmgPath.h"
+#include "RmgObject.h"
+#include "ObjectManager.h"
+#include "Functions.h"
+#include "RoadPlacer.h"
+#include "WaterAdopter.h"
+#include "TileInfo.h"
+
+void TownPlacer::process()
+{
+	auto * manager = zone.getModificator<ObjectManager>();
+	if(!manager)
+	{
+		logGlobal->error("ObjectManager doesn't exist for zone %d, skip modificator %s", zone.getId(), getName());
+		return;
+	}
+	
+	
+	placeTowns(*manager);
+	placeMines(*manager);
+}
+
+void TownPlacer::init()
+{
+	POSTFUNCTION(ObjectManager);
+	POSTFUNCTION(RoadPlacer);
+} 
+
+void TownPlacer::placeTowns(ObjectManager & manager)
+{
+	if((zone.getType() == ETemplateZoneType::CPU_START) || (zone.getType() == ETemplateZoneType::PLAYER_START))
+	{
+		//set zone types to player faction, generate main town
+		logGlobal->info("Preparing playing zone");
+		int player_id = *zone.getOwner() - 1;
+		auto & playerInfo = map.map().players[player_id];
+		PlayerColor player(player_id);
+		if(playerInfo.canAnyonePlay())
+		{
+			player = PlayerColor(player_id);
+			zone.setTownType(map.getMapGenOptions().getPlayersSettings().find(player)->second.getStartingTown());
+			
+			if(zone.getTownType() == CMapGenOptions::CPlayerSettings::RANDOM_TOWN)
+				zone.setTownType(getRandomTownType(true));
+		}
+		else //no player - randomize town
+		{
+			player = PlayerColor::NEUTRAL;
+			zone.setTownType(getRandomTownType());
+		}
+		
+		auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, zone.getTownType());
+		
+		CGTownInstance * town = (CGTownInstance *) townFactory->create(ObjectTemplate());
+		town->tempOwner = player;
+		town->builtBuildings.insert(BuildingID::FORT);
+		town->builtBuildings.insert(BuildingID::DEFAULT);
+		
+		for(auto spell : VLC->spellh->objects) //add all regular spells to town
+		{
+			if(!spell->isSpecial() && !spell->isCreatureAbility())
+				town->possibleSpells.push_back(spell->id);
+		}
+		
+		auto position = placeMainTown(manager, *town);
+		
+		totalTowns++;
+		//register MAIN town of zone only
+		map.registerZone(town->subID);
+		
+		if(playerInfo.canAnyonePlay()) //configure info for owning player
+		{
+			logGlobal->trace("Fill player info %d", player_id);
+			
+			// Update player info
+			playerInfo.allowedFactions.clear();
+			playerInfo.allowedFactions.insert(zone.getTownType());
+			playerInfo.hasMainTown = true;
+			playerInfo.posOfMainTown = position;
+			playerInfo.generateHeroAtMainTown = true;
+			
+			//now create actual towns
+			addNewTowns(zone.getPlayerTowns().getCastleCount() - 1, true, player, manager);
+			addNewTowns(zone.getPlayerTowns().getTownCount(), false, player, manager);
+		}
+		else
+		{
+			addNewTowns(zone.getPlayerTowns().getCastleCount() - 1, true, PlayerColor::NEUTRAL, manager);
+			addNewTowns(zone.getPlayerTowns().getTownCount(), false, PlayerColor::NEUTRAL, manager);
+		}
+	}
+	else //randomize town types for any other zones as well
+	{
+		zone.setTownType(getRandomTownType());
+	}
+	
+	addNewTowns(zone.getNeutralTowns().getCastleCount(), true, PlayerColor::NEUTRAL, manager);
+	addNewTowns(zone.getNeutralTowns().getTownCount(), false, PlayerColor::NEUTRAL, manager);
+	
+	if(!totalTowns) //if there's no town present, get random faction for dwellings and pandoras
+	{
+		//25% chance for neutral
+		if (generator.rand.nextInt(1, 100) <= 25)
+		{
+			zone.setTownType(ETownType::NEUTRAL);
+		}
+		else
+		{
+			if(zone.getTownTypes().size())
+				zone.setTownType(*RandomGeneratorUtil::nextItem(zone.getTownTypes(), generator.rand));
+			else if(zone.getMonsterTypes().size())
+				zone.setTownType(*RandomGeneratorUtil::nextItem(zone.getMonsterTypes(), generator.rand)); //this happens in Clash of Dragons in treasure zones, where all towns are banned
+			else //just in any case
+				zone.setTownType(getRandomTownType());
+		}
+	}
+}
+
+int3 TownPlacer::placeMainTown(ObjectManager & manager, CGTownInstance & town)
+{
+	//towns are big objects and should be centered around visitable position
+	rmg::Object rmgObject(town);
+	auto position = manager.findPlaceForObject(zone.areaPossible(), rmgObject, [this](const int3 & t)
+	{
+		float distance = zone.getPos().dist2dSQ(t);
+		return 100000.f - distance; //some big number
+	}, true);
+	rmgObject.setPosition(position);
+	manager.placeObject(rmgObject, false, true);
+	cleanupBoundaries(rmgObject);
+	zone.setPos(rmgObject.getVisitablePosition()); //roads lead to main town
+	return position;
+}
+
+bool TownPlacer::placeMines(ObjectManager & manager)
+{
+	using namespace Res;
+	std::vector<CGMine*> createdMines;
+	
+	for(const auto & mineInfo : zone.getMinesInfo())
+	{
+		ERes res = (ERes)mineInfo.first;
+		for(int i = 0; i < mineInfo.second; ++i)
+		{
+			auto mineHandler = VLC->objtypeh->getHandlerFor(Obj::MINE, res);
+			auto & rmginfo = mineHandler->getRMGInfo();
+			auto mine = (CGMine*)mineHandler->create(ObjectTemplate());
+			mine->producedResource = res;
+			mine->tempOwner = PlayerColor::NEUTRAL;
+			mine->producedQuantity = mine->defaultResProduction();
+			createdMines.push_back(mine);
+			
+			
+			if(!i && (res == ERes::WOOD || res == ERes::ORE))
+				manager.addCloseObject(mine, rmginfo.value); //only first wood&ore mines are close
+			else
+				manager.addRequiredObject(mine, rmginfo.value);
+		}
+	}
+	
+	//create extra resources
+	if(int extraRes = generator.getConfig().mineExtraResources)
+	{
+		for(auto * mine : createdMines)
+		{
+			for(int rc = generator.rand.nextInt(1, extraRes); rc > 0; --rc)
+			{
+				auto resourse = (CGResource*) VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create(ObjectTemplate());
+				resourse->amount = CGResource::RANDOM_AMOUNT;
+				manager.addNearbyObject(resourse, mine);
+			}
+		}
+	}
+	
+	return true;
+}
+
+void TownPlacer::cleanupBoundaries(const rmg::Object & rmgObject)
+{
+	for(auto & t : rmgObject.getArea().getBorderOutside())
+	{
+		if(map.isOnMap(t))
+		{
+			map.setOccupied(t, ETileType::FREE);
+			zone.areaPossible().erase(t);
+			zone.freePaths().add(t);
+		}
+	}
+}
+
+void TownPlacer::addNewTowns(int count, bool hasFort, PlayerColor player, ObjectManager & manager)
+{
+	for(int i = 0; i < count; i++)
+	{
+		si32 subType = zone.getTownType();
+		
+		if(totalTowns>0)
+		{
+			if(!zone.areTownsSameType())
+			{
+				if (zone.getTownTypes().size())
+					subType = *RandomGeneratorUtil::nextItem(zone.getTownTypes(), generator.rand);
+				else
+					subType = *RandomGeneratorUtil::nextItem(zone.getDefaultTownTypes(), generator.rand); //it is possible to have zone with no towns allowed
+			}
+		}
+		
+		auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, subType);
+		auto town = (CGTownInstance *) townFactory->create(ObjectTemplate());
+		town->ID = Obj::TOWN;
+		
+		town->tempOwner = player;
+		if (hasFort)
+			town->builtBuildings.insert(BuildingID::FORT);
+		town->builtBuildings.insert(BuildingID::DEFAULT);
+		
+		for(auto spell : VLC->spellh->objects) //add all regular spells to town
+		{
+			if(!spell->isSpecial() && !spell->isCreatureAbility())
+				town->possibleSpells.push_back(spell->id);
+		}
+		
+		if(totalTowns <= 0)
+		{
+			//FIXME: discovered bug with small zones - getPos is close to map boarder and we have outOfMap exception
+			//register MAIN town of zone
+			map.registerZone(town->subID);
+			//first town in zone goes in the middle
+			placeMainTown(manager, *town);
+		}
+		else
+			manager.addRequiredObject(town);
+		totalTowns++;
+	}
+}
+
+si32 TownPlacer::getRandomTownType(bool matchUndergroundType)
+{
+	auto townTypesAllowed = (zone.getTownTypes().size() ? zone.getTownTypes() : zone.getDefaultTownTypes());
+	if(matchUndergroundType)
+	{
+		std::set<TFaction> townTypesVerify;
+		for(TFaction factionIdx : townTypesAllowed)
+		{
+			bool preferUnderground = (*VLC->townh)[factionIdx]->preferUndergroundPlacement;
+			if(zone.isUnderground() ? preferUnderground : !preferUnderground)
+			{
+				townTypesVerify.insert(factionIdx);
+			}
+		}
+		if(!townTypesVerify.empty())
+			townTypesAllowed = townTypesVerify;
+	}
+	
+	return *RandomGeneratorUtil::nextItem(townTypesAllowed, generator.rand);
+}
+
+int TownPlacer::getTotalTowns() const
+{
+	return totalTowns;
+}

+ 37 - 0
lib/rmg/TownPlacer.h

@@ -0,0 +1,37 @@
+/*
+ * TownPlacer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+#include "Zone.h"
+
+class ObjectManager;
+class CGTownInstance;
+
+class TownPlacer: public Modificator
+{
+public:
+	MODIFICATOR(TownPlacer);
+	
+	void process() override;
+	void init() override;
+	
+	int getTotalTowns() const;
+	
+protected:
+	void cleanupBoundaries(const rmg::Object & rmgObject);
+	void addNewTowns(int count, bool hasFort, PlayerColor player, ObjectManager & manager);
+	si32 getRandomTownType(bool matchUndergroundType = false);
+	void placeTowns(ObjectManager & manager);
+	bool placeMines(ObjectManager & manager);
+	int3 placeMainTown(ObjectManager & manager, CGTownInstance & town);
+	
+protected:
+	
+	int totalTowns = 0;
+};

+ 788 - 0
lib/rmg/TreasurePlacer.cpp

@@ -0,0 +1,788 @@
+/*
+ * TreasurePlacer.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 "TreasurePlacer.h"
+#include "CMapGenerator.h"
+#include "Functions.h"
+#include "ObjectManager.h"
+#include "RoadPlacer.h"
+#include "ConnectionsPlacer.h"
+#include "RmgMap.h"
+#include "TileInfo.h"
+#include "../mapObjects/CommonConstructors.h"
+#include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
+#include "../CCreatureHandler.h"
+#include "../spells/CSpellHandler.h" //for choosing random spells
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+
+void TreasurePlacer::process()
+{
+	addAllPossibleObjects();
+	auto * m = zone.getModificator<ObjectManager>();
+	if(m)
+		createTreasures(*m);
+}
+
+void TreasurePlacer::init()
+{
+	DEPENDENCY(ObjectManager);
+	DEPENDENCY(ConnectionsPlacer);
+	POSTFUNCTION(RoadPlacer);
+}
+
+void TreasurePlacer::setQuestArtZone(Zone * otherZone)
+{
+	questArtZone = otherZone;
+}
+
+void TreasurePlacer::addAllPossibleObjects()
+{
+	ObjectInfo oi;
+	
+	int numZones = static_cast<int>(map.getZones().size());
+	
+	for(auto primaryID : VLC->objtypeh->knownObjects())
+	{
+		for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
+		{
+			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
+			if(!handler->isStaticObject() && handler->getRMGInfo().value)
+			{
+				for(auto temp : handler->getTemplates())
+				{
+					if(temp.canBePlacedAt(zone.getTerrainType()))
+					{
+						oi.generateObject = [temp]() -> CGObjectInstance *
+						{
+							return VLC->objtypeh->getHandlerFor(temp.id, temp.subid)->create(temp);
+						};
+						auto rmgInfo = handler->getRMGInfo();
+						oi.value = rmgInfo.value;
+						oi.probability = rmgInfo.rarity;
+						oi.templ = temp;
+						oi.maxPerZone = rmgInfo.zoneLimit;
+						vstd::amin(oi.maxPerZone, rmgInfo.mapLimit / numZones); //simple, but should distribute objects evenly on large maps
+						possibleObjects.push_back(oi);
+					}
+				}
+			}
+		}
+	}
+	
+	if(zone.getType() == ETemplateZoneType::WATER)
+		return;
+	
+	//prisons
+	//levels 1, 5, 10, 20, 30
+	static int prisonsLevels = std::min(generator.getConfig().prisonExperience.size(), generator.getConfig().prisonValues.size());
+	for(int i = 0; i < prisonsLevels; i++)
+	{
+		oi.generateObject = [i, this]() -> CGObjectInstance *
+		{
+			std::vector<ui32> possibleHeroes;
+			for(int j = 0; j < map.map().allowedHeroes.size(); j++)
+			{
+				if(map.map().allowedHeroes[j])
+					possibleHeroes.push_back(j);
+			}
+			
+			auto hid = *RandomGeneratorUtil::nextItem(possibleHeroes, generator.rand);
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0);
+			auto obj = (CGHeroInstance *) factory->create(ObjectTemplate());
+			
+			
+			obj->subID = hid; //will be initialized later
+			obj->exp = generator.getConfig().prisonExperience[i];
+			obj->setOwner(PlayerColor::NEUTRAL);
+			map.map().allowedHeroes[hid] = false; //ban this hero
+			generator.decreasePrisonsRemaining();
+			obj->appearance = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType()).front(); //can't init template with hero subID
+			
+			return obj;
+		};
+		oi.setTemplate(Obj::PRISON, 0, zone.getTerrainType());
+		oi.value = generator.getConfig().prisonValues[i];
+		oi.probability = 30;
+		oi.maxPerZone = generator.getPrisonsRemaning() / 5; //probably not perfect, but we can't generate more prisons than hereos.
+		possibleObjects.push_back(oi);
+	}
+	
+	//all following objects are unlimited
+	oi.maxPerZone = std::numeric_limits<ui32>().max();
+	
+	std::vector<CCreature *> creatures; //native creatures for this zone
+	for(auto cre : VLC->creh->objects)
+	{
+		if(!cre->special && cre->faction == zone.getTownType())
+		{
+			creatures.push_back(cre);
+		}
+	}
+	
+	//dwellings
+	auto dwellingTypes = {Obj::CREATURE_GENERATOR1, Obj::CREATURE_GENERATOR4};
+	
+	for(auto dwellingType : dwellingTypes)
+	{
+		auto subObjects = VLC->objtypeh->knownSubObjects(dwellingType);
+		
+		if(dwellingType == Obj::CREATURE_GENERATOR1)
+		{
+			//don't spawn original "neutral" dwellings that got replaced by Conflux dwellings in AB
+			static int elementalConfluxROE[] = {7, 13, 16, 47};
+			for(int i = 0; i < 4; i++)
+				vstd::erase_if_present(subObjects, elementalConfluxROE[i]);
+		}
+		
+		for(auto secondaryID : subObjects)
+		{
+			auto dwellingHandler = dynamic_cast<const CDwellingInstanceConstructor *>(VLC->objtypeh->getHandlerFor(dwellingType, secondaryID).get());
+			auto creatures = dwellingHandler->getProducedCreatures();
+			if(creatures.empty())
+				continue;
+			
+			auto cre = creatures.front();
+			if(cre->faction == zone.getTownType())
+			{
+				float nativeZonesCount = static_cast<float>(map.getZoneCount(cre->faction));
+				oi.value = static_cast<ui32>(cre->AIValue * cre->growth * (1 + (nativeZonesCount / map.getTotalZoneCount()) + (nativeZonesCount / 2)));
+				oi.probability = 40;
+				
+				for(auto tmplate : dwellingHandler->getTemplates())
+				{
+					if(tmplate.canBePlacedAt(zone.getTerrainType()))
+					{
+						oi.generateObject = [tmplate, secondaryID, dwellingType]() -> CGObjectInstance *
+						{
+							auto obj = VLC->objtypeh->getHandlerFor(dwellingType, secondaryID)->create(tmplate);
+							obj->tempOwner = PlayerColor::NEUTRAL;
+							return obj;
+						};
+						
+						oi.templ = tmplate;
+						possibleObjects.push_back(oi);
+					}
+				}
+			}
+		}
+	}
+	
+	for(int i = 0; i < generator.getConfig().scrollValues.size(); i++)
+	{
+		oi.generateObject = [i, this]() -> CGObjectInstance *
+		{
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::SPELL_SCROLL, 0);
+			auto obj = (CGArtifact *) factory->create(ObjectTemplate());
+			std::vector<SpellID> out;
+			
+			for(auto spell : VLC->spellh->objects) //spellh size appears to be greater (?)
+			{
+				if(map.isAllowedSpell(spell->id) && spell->level == i + 1)
+				{
+					out.push_back(spell->id);
+				}
+			}
+			auto a = CArtifactInstance::createScroll(*RandomGeneratorUtil::nextItem(out, generator.rand));
+			obj->storedArtifact = a;
+			return obj;
+		};
+		oi.setTemplate(Obj::SPELL_SCROLL, 0, zone.getTerrainType());
+		oi.value = generator.getConfig().scrollValues[i];
+		oi.probability = 30;
+		possibleObjects.push_back(oi);
+	}
+	
+	//pandora box with gold
+	for(int i = 1; i < 5; i++)
+	{
+		oi.generateObject = [i]() -> CGObjectInstance *
+		{
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
+			obj->resources[Res::GOLD] = i * 5000;
+			return obj;
+		};
+		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
+		oi.value = i * generator.getConfig().pandoraMultiplierGold;
+		oi.probability = 5;
+		possibleObjects.push_back(oi);
+	}
+	
+	//pandora box with experience
+	for(int i = 1; i < 5; i++)
+	{
+		oi.generateObject = [i]() -> CGObjectInstance *
+		{
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
+			obj->gainedExp = i * 5000;
+			return obj;
+		};
+		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
+		oi.value = i * generator.getConfig().pandoraMultiplierExperience;
+		oi.probability = 20;
+		possibleObjects.push_back(oi);
+	}
+	
+	//pandora box with creatures
+	const std::vector<int> & tierValues = generator.getConfig().pandoraCreatureValues;
+	
+	auto creatureToCount = [tierValues](CCreature * creature) -> int
+	{
+		if(!creature->AIValue || tierValues.empty()) //bug #2681
+			return 0; //this box won't be generated
+		
+		int actualTier = creature->level > tierValues.size() ?
+		tierValues.size() - 1 :
+		creature->level - 1;
+		float creaturesAmount = ((float)tierValues[actualTier]) / creature->AIValue;
+		if(creaturesAmount <= 5)
+		{
+			creaturesAmount = boost::math::round(creaturesAmount); //allow single monsters
+			if(creaturesAmount < 1)
+				return 0;
+		}
+		else if(creaturesAmount <= 12)
+		{
+			(creaturesAmount /= 2) *= 2;
+		}
+		else if(creaturesAmount <= 50)
+		{
+			creaturesAmount = boost::math::round(creaturesAmount / 5) * 5;
+		}
+		else
+		{
+			creaturesAmount = boost::math::round(creaturesAmount / 10) * 10;
+		}
+		return static_cast<int>(creaturesAmount);
+	};
+	
+	for(auto creature : creatures)
+	{
+		int creaturesAmount = creatureToCount(creature);
+		if(!creaturesAmount)
+			continue;
+		
+		oi.generateObject = [creature, creaturesAmount]() -> CGObjectInstance *
+		{
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
+			auto stack = new CStackInstance(creature, creaturesAmount);
+			obj->creatures.putStack(SlotID(0), stack);
+			return obj;
+		};
+		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
+		oi.value = static_cast<ui32>((2 * (creature->AIValue) * creaturesAmount * (1 + (float)(map.getZoneCount(creature->faction)) / map.getTotalZoneCount())) / 3);
+		oi.probability = 3;
+		possibleObjects.push_back(oi);
+	}
+	
+	//Pandora with 12 spells of certain level
+	for(int i = 1; i <= GameConstants::SPELL_LEVELS; i++)
+	{
+		oi.generateObject = [i, this]() -> CGObjectInstance *
+		{
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
+			
+			std::vector <CSpell *> spells;
+			for(auto spell : VLC->spellh->objects)
+			{
+				if(map.isAllowedSpell(spell->id) && spell->level == i)
+					spells.push_back(spell);
+			}
+			
+			RandomGeneratorUtil::randomShuffle(spells, generator.rand);
+			for(int j = 0; j < std::min(12, (int)spells.size()); j++)
+			{
+				obj->spells.push_back(spells[j]->id);
+			}
+			
+			return obj;
+		};
+		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
+		oi.value = (i + 1) * generator.getConfig().pandoraMultiplierSpells; //5000 - 15000
+		oi.probability = 2;
+		possibleObjects.push_back(oi);
+	}
+	
+	//Pandora with 15 spells of certain school
+	for(int i = 0; i < 4; i++)
+	{
+		oi.generateObject = [i, this]() -> CGObjectInstance *
+		{
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
+			
+			std::vector <CSpell *> spells;
+			for(auto spell : VLC->spellh->objects)
+			{
+				if(map.isAllowedSpell(spell->id) && spell->school[(ESpellSchool)i])
+					spells.push_back(spell);
+			}
+			
+			RandomGeneratorUtil::randomShuffle(spells, generator.rand);
+			for(int j = 0; j < std::min(15, (int)spells.size()); j++)
+			{
+				obj->spells.push_back(spells[j]->id);
+			}
+			
+			return obj;
+		};
+		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
+		oi.value = generator.getConfig().pandoraSpellSchool;
+		oi.probability = 2;
+		possibleObjects.push_back(oi);
+	}
+	
+	// Pandora box with 60 random spells
+	
+	oi.generateObject = [this]() -> CGObjectInstance *
+	{
+		auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+		auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
+		
+		std::vector <CSpell *> spells;
+		for(auto spell : VLC->spellh->objects)
+		{
+			if(map.isAllowedSpell(spell->id))
+				spells.push_back(spell);
+		}
+		
+		RandomGeneratorUtil::randomShuffle(spells, generator.rand);
+		for(int j = 0; j < std::min(60, (int)spells.size()); j++)
+		{
+			obj->spells.push_back(spells[j]->id);
+		}
+		
+		return obj;
+	};
+	oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
+	oi.value = generator.getConfig().pandoraSpell60;
+	oi.probability = 2;
+	possibleObjects.push_back(oi);
+	
+	//seer huts with creatures or generic rewards
+	
+	if(questArtZone) //we won't be placing seer huts if there is no zone left to place arties
+	{
+		static const int genericSeerHuts = 8;
+		int seerHutsPerType = 0;
+		const int questArtsRemaining = static_cast<int>(generator.getQuestArtsRemaning().size());
+		
+		//general issue is that not many artifact types are available for quests
+		
+		if(questArtsRemaining >= genericSeerHuts + (int)creatures.size())
+		{
+			seerHutsPerType = questArtsRemaining / (genericSeerHuts + (int)creatures.size());
+		}
+		else if(questArtsRemaining >= genericSeerHuts)
+		{
+			seerHutsPerType = 1;
+		}
+		oi.maxPerZone = seerHutsPerType;
+		
+		RandomGeneratorUtil::randomShuffle(creatures, generator.rand);
+		
+		auto generateArtInfo = [this](ArtifactID id) -> ObjectInfo
+		{
+			ObjectInfo artInfo;
+			artInfo.probability = std::numeric_limits<ui16>::max(); //99,9% to spawn that art in first treasure pile
+			artInfo.maxPerZone = 1;
+			artInfo.value = 2000; //treasure art
+			artInfo.setTemplate(Obj::ARTIFACT, id, this->zone.getTerrainType());
+			artInfo.generateObject = [id]() -> CGObjectInstance *
+			{
+				auto handler = VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, id);
+				return handler->create(handler->getTemplates().front());
+			};
+			return artInfo;
+		};
+		
+		for(int i = 0; i < std::min((int)creatures.size(), questArtsRemaining - genericSeerHuts); i++)
+		{
+			auto creature = creatures[i];
+			int creaturesAmount = creatureToCount(creature);
+			
+			if(!creaturesAmount)
+				continue;
+			
+			int randomAppearance = chooseRandomAppearance(generator.rand, Obj::SEER_HUT, zone.getTerrainType());
+			
+			oi.generateObject = [creature, creaturesAmount, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
+			{
+				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
+				auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
+				obj->rewardType = CGSeerHut::CREATURE;
+				obj->rID = creature->idNumber;
+				obj->rVal = creaturesAmount;
+				
+				obj->quest->missionType = CQuest::MISSION_ART;
+				ArtifactID artid = *RandomGeneratorUtil::nextItem(generator.getQuestArtsRemaning(), generator.rand);
+				obj->quest->m5arts.push_back(artid);
+				obj->quest->lastDay = -1;
+				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
+				
+				generator.banQuestArt(artid);
+				
+				
+				this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
+				
+				return obj;
+			};
+			oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
+			oi.value = static_cast<ui32>(((2 * (creature->AIValue) * creaturesAmount * (1 + (float)(map.getZoneCount(creature->faction)) / map.getTotalZoneCount())) - 4000) / 3);
+			oi.probability = 3;
+			possibleObjects.push_back(oi);
+		}
+		
+		static int seerLevels = std::min(generator.getConfig().questValues.size(), generator.getConfig().questRewardValues.size());
+		for(int i = 0; i < seerLevels; i++) //seems that code for exp and gold reward is similiar
+		{
+			int randomAppearance = chooseRandomAppearance(generator.rand, Obj::SEER_HUT, zone.getTerrainType());
+			
+			oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
+			oi.value = generator.getConfig().questValues[i];
+			oi.probability = 10;
+			
+			oi.generateObject = [i, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
+			{
+				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
+				auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
+				
+				obj->rewardType = CGSeerHut::EXPERIENCE;
+				obj->rID = 0; //unitialized?
+				obj->rVal = generator.getConfig().questRewardValues[i];
+				
+				obj->quest->missionType = CQuest::MISSION_ART;
+				ArtifactID artid = *RandomGeneratorUtil::nextItem(generator.getQuestArtsRemaning(), generator.rand);
+				obj->quest->m5arts.push_back(artid);
+				obj->quest->lastDay = -1;
+				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
+				
+				generator.banQuestArt(artid);
+				
+				this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
+				
+				return obj;
+			};
+			
+			possibleObjects.push_back(oi);
+			
+			oi.generateObject = [i, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
+			{
+				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
+				auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
+				obj->rewardType = CGSeerHut::RESOURCES;
+				obj->rID = Res::GOLD;
+				obj->rVal = generator.getConfig().questRewardValues[i];
+				
+				obj->quest->missionType = CQuest::MISSION_ART;
+				ArtifactID artid = *RandomGeneratorUtil::nextItem(generator.getQuestArtsRemaning(), generator.rand);
+				obj->quest->m5arts.push_back(artid);
+				obj->quest->lastDay = -1;
+				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
+				
+				generator.banQuestArt(artid);
+				
+				this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
+				
+				return obj;
+			};
+			
+			possibleObjects.push_back(oi);
+		}
+	}
+}
+
+bool TreasurePlacer::isGuardNeededForTreasure(int value)
+{
+	return zone.getType() != ETemplateZoneType::WATER && value > minGuardedValue;
+}
+
+std::vector<ObjectInfo*> TreasurePlacer::prepareTreasurePile(const CTreasureInfo& treasureInfo)
+{
+	std::vector<ObjectInfo*> objectInfos;
+	int maxValue = treasureInfo.max;
+	int minValue = treasureInfo.min;
+	
+	const ui32 desiredValue = generator.rand.nextInt(minValue, maxValue);
+	
+	int currentValue = 0;
+	bool hasLargeObject = false;
+	while(currentValue <= (int)desiredValue - 100) //no objects with value below 100 are available
+	{
+		auto * oi = getRandomObject(desiredValue, currentValue, maxValue, !hasLargeObject);
+		if(!oi) //fail
+			break;
+		
+		if(oi->templ.isVisitableFromTop())
+		{
+			objectInfos.push_back(oi);
+		}
+		else
+		{
+			objectInfos.insert(objectInfos.begin(), oi); //large object shall at first place
+			hasLargeObject = true;
+		}
+		
+		//remove from possible objects
+		assert(oi->maxPerZone);
+		oi->maxPerZone--;
+		
+		currentValue += oi->value;
+	}
+	
+	return objectInfos;
+}
+
+rmg::Object TreasurePlacer::constuctTreasurePile(const std::vector<ObjectInfo*> & treasureInfos)
+{
+	rmg::Object rmgObject;
+	for(auto & oi : treasureInfos)
+	{
+		auto blockedArea = rmgObject.getArea();
+		auto accessibleArea = rmgObject.getAccessibleArea();
+		if(rmgObject.instances().empty())
+			accessibleArea.add(int3());
+		
+		auto * object = oi->generateObject();
+		object->appearance = oi->templ;
+		auto & instance = rmgObject.addInstance(*object);
+
+		do
+		{
+			if(accessibleArea.empty())
+			{
+				//fail - fallback
+				rmgObject.clear();
+				return rmgObject;
+			}
+			
+			int3 nextPos = *RandomGeneratorUtil::nextItem(accessibleArea.getTiles(), generator.rand);
+			instance.setPosition(nextPos - rmgObject.getPosition());
+			
+			auto instanceAccessibleArea = instance.getAccessibleArea();
+			if(instance.getBlockedArea().getTilesVector().size() == 1)
+			{
+				if(instance.object().appearance.isVisitableFromTop() && instance.object().ID != Obj::CORPSE)
+					instanceAccessibleArea.add(instance.getVisitablePosition());
+			}
+			
+			//first object is good
+			if(rmgObject.instances().size() == 1)
+				break;
+			
+			//condition for good position
+			if(!blockedArea.overlap(instance.getBlockedArea()) && accessibleArea.overlap(instanceAccessibleArea))
+				break;
+			
+			//fail - new position
+			accessibleArea.erase(nextPos);
+		} while(true);
+	}
+	return rmgObject;
+}
+
+ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects)
+{
+	std::vector<std::pair<ui32, ObjectInfo*>> thresholds; //handle complex object via pointer
+	ui32 total = 0;
+	
+	//calculate actual treasure value range based on remaining value
+	ui32 maxVal = maxValue - currentValue;
+	ui32 minValue = static_cast<ui32>(0.25f * (desiredValue - currentValue));
+	
+	for(ObjectInfo & oi : possibleObjects) //copy constructor turned out to be costly
+	{
+		if(oi.value > maxVal)
+			break; //this assumes values are sorted in ascending order
+		
+		if(!oi.templ.isVisitableFromTop() && !allowLargeObjects)
+			continue;
+		
+		if(oi.value >= minValue && oi.maxPerZone > 0)
+		{
+			total += oi.probability;
+			thresholds.push_back(std::make_pair(total, &oi));
+		}
+	}
+	
+	if(thresholds.empty())
+	{
+		return nullptr;
+	}
+	else
+	{
+		int r = generator.rand.nextInt(1, total);
+		
+		//binary search = fastest
+		auto it = std::lower_bound(thresholds.begin(), thresholds.end(), r,
+								   [](const std::pair<ui32, ObjectInfo*> &rhs, const int lhs)->bool
+								   {
+			return (int)rhs.first < lhs;
+		});
+		return it->second;
+	}
+}
+
+void TreasurePlacer::createTreasures(ObjectManager & manager)
+{
+	int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength();
+	int monsterStrength = zone.zoneMonsterStrength + mapMonsterStrength - 1; //array index from 0 to 4
+	
+	static int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 };
+	minGuardedValue = minGuardedValues[monsterStrength];
+	
+	auto valueComparator = [](const CTreasureInfo & lhs, const CTreasureInfo & rhs) -> bool
+	{
+		return lhs.max > rhs.max;
+	};
+	
+	auto restoreZoneLimits = [](const std::vector<ObjectInfo*> & treasurePile)
+	{
+		for(auto * oi : treasurePile)
+		{
+			oi->maxPerZone++;
+		}
+	};
+	
+	//place biggest treasures first at large distance, place smaller ones inbetween
+	auto treasureInfo = zone.getTreasureInfo();
+	boost::sort(treasureInfo, valueComparator);
+	
+	//sort treasures by ascending value so we can stop checking treasures with too high value
+	boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool
+	{
+		return oi1.value < oi2.value;
+	});
+	
+	int totalDensity = 0;
+	for (auto t : treasureInfo)
+	{
+		//discard objects with too high value to be ever placed
+		vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool
+		{
+			return oi.value > t.max;
+		});
+		
+		totalDensity += t.density;
+		
+		//treasure density is inversely proportional to zone size but must be scaled back to map size
+		//also, normalize it to zone count - higher count means relatively smaller zones
+		
+		//this is squared distance for optimization purposes
+		const float minDistance = std::max<float>((125.f / totalDensity), 2.0f);
+		//distance lower than 2 causes objects to overlap and crash
+		
+		while(true)
+		{
+			auto treasurePileInfos = prepareTreasurePile(t);
+			if(treasurePileInfos.empty())
+				break;
+			
+			int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo * oi){return v + oi->value;});
+			
+			auto rmgObject = constuctTreasurePile(treasurePileInfos);
+			if(rmgObject.instances().empty()) //handle incorrect placement
+			{
+				restoreZoneLimits(treasurePileInfos);
+				continue;
+			}
+			
+			//guard treasure pile
+			bool guarded = isGuardNeededForTreasure(value);
+			if(guarded)
+				guarded = manager.addGuard(rmgObject, value);
+			
+			int3 pos;
+			auto possibleArea = zone.areaPossible();
+			
+			auto path = rmg::Path::invalid();
+			if(guarded)
+			{
+				path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3 & tile)
+				{
+					auto ti = map.getTile(tile);
+					if(ti.getNearestObjectDistance() < minDistance)
+						return -1.f;
+					
+					auto guardedArea = rmgObject.instances().back()->getAccessibleArea();
+					auto areaToBlock = rmgObject.getAccessibleArea(true);
+					areaToBlock.subtract(guardedArea);
+					if(areaToBlock.overlap(zone.freePaths()) || areaToBlock.overlap(manager.getVisitableArea()))
+						return -1.f;
+					
+					return 1.f;
+				}, guarded, false, false);
+			}
+			else
+			{
+				path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, false);
+			}
+			
+			if(path.valid())
+			{
+				//debug purposes
+				treasureArea.unite(rmgObject.getArea());
+				if(guarded)
+				{
+					guards.unite(rmgObject.instances().back()->getBlockedArea());
+					auto guardedArea = rmgObject.instances().back()->getAccessibleArea();
+					auto areaToBlock = rmgObject.getAccessibleArea(true);
+					areaToBlock.subtract(guardedArea);
+					treasureBlockArea.unite(areaToBlock);
+				}
+				zone.connectPath(path);
+				manager.placeObject(rmgObject, guarded, true);
+			}
+			else
+			{
+				restoreZoneLimits(treasurePileInfos);
+				rmgObject.clear();
+				break;
+			}
+		}
+	}
+}
+
+char TreasurePlacer::dump(const int3 & t)
+{
+	if(guards.contains(t))
+		return '!';
+	if(treasureArea.contains(t))
+		return '$';
+	if(treasureBlockArea.contains(t))
+		return '*';
+	
+	return Modificator::dump(t);
+}
+
+
+ObjectInfo::ObjectInfo()
+: templ(), value(0), probability(0), maxPerZone(1)
+{
+	
+}
+
+void ObjectInfo::setTemplate(si32 type, si32 subtype, Terrain terrainType)
+{
+	auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
+	if(!templHandler)
+		return;
+	
+	auto templates = templHandler->getTemplates(terrainType);
+	if(templates.empty())
+		return;
+	
+	templ = templates.front();
+}

+ 67 - 0
lib/rmg/TreasurePlacer.h

@@ -0,0 +1,67 @@
+/*
+ * TreasurePlacer.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
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+#include "../mapObjects/ObjectTemplate.h"
+
+class CGObjectInstance;
+class ObjectManager;
+class RmgMap;
+class CMapGenerator;
+
+struct ObjectInfo
+{
+	ObjectTemplate templ;
+	ui32 value = 0;
+	ui16 probability = 0;
+	ui32 maxPerZone = -1;
+	//ui32 maxPerMap; //unused
+	std::function<CGObjectInstance *()> generateObject;
+	
+	void setTemplate(si32 type, si32 subtype, Terrain terrain);
+	
+	ObjectInfo();
+	
+	bool operator==(const ObjectInfo& oi) const { return (templ == oi.templ); }
+};
+
+class TreasurePlacer: public Modificator
+{
+public:
+	MODIFICATOR(TreasurePlacer);
+	
+	void process() override;
+	void init() override;
+	char dump(const int3 &) override;
+	
+	void createTreasures(ObjectManager & manager);
+	
+	void setQuestArtZone(Zone * otherZone);
+	void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects
+	
+protected:
+	bool isGuardNeededForTreasure(int value);
+	
+	ObjectInfo * getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects);
+	std::vector<ObjectInfo*> prepareTreasurePile(const CTreasureInfo & treasureInfo);
+	rmg::Object constuctTreasurePile(const std::vector<ObjectInfo*> & treasureInfos);
+	
+	
+protected:	
+	std::vector<ObjectInfo> possibleObjects;
+	int minGuardedValue = 0;
+	
+	rmg::Area treasureArea;
+	rmg::Area treasureBlockArea;
+	rmg::Area guards;
+	
+	Zone * questArtZone = nullptr; //artifacts required for Seer Huts will be placed here - or not if null
+};

+ 261 - 0
lib/rmg/WaterAdopter.cpp

@@ -0,0 +1,261 @@
+/*
+ * WaterAdopter.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 "WaterAdopter.h"
+#include "CMapGenerator.h"
+#include "RmgMap.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "RmgPath.h"
+#include "RmgObject.h"
+#include "ObjectManager.h"
+#include "Functions.h"
+#include "RoadPlacer.h"
+#include "TreasurePlacer.h"
+#include "TownPlacer.h"
+#include "ConnectionsPlacer.h"
+#include "TileInfo.h"
+
+void WaterAdopter::process()
+{
+	createWater(map.getMapGenOptions().getWaterContent());
+}
+
+void WaterAdopter::init()
+{
+	//make dependencies
+	DEPENDENCY_ALL(WaterAdopter);
+	DEPENDENCY(TownPlacer);
+	POSTFUNCTION(ConnectionsPlacer);
+	POSTFUNCTION(TreasurePlacer);
+}
+
+void WaterAdopter::createWater(EWaterContent::EWaterContent waterContent)
+{
+	if(waterContent == EWaterContent::NONE || zone.isUnderground() || zone.getType() == ETemplateZoneType::WATER)
+		return; //do nothing
+	
+	distanceMap = zone.area().computeDistanceMap(reverseDistanceMap);
+	
+	//add border tiles as water for ISLANDS
+	if(waterContent == EWaterContent::ISLANDS)
+	{
+		waterArea.unite(collectDistantTiles(zone, zone.getSize() + 1));
+		waterArea.unite(zone.area().getBorder());
+	}
+	
+	//protect some parts from water for NORMAL
+	if(waterContent == EWaterContent::NORMAL)
+	{
+		waterArea.unite(collectDistantTiles(zone, zone.getSize() - 1));
+		auto sliceStart = RandomGeneratorUtil::nextItem(reverseDistanceMap[0], generator.rand);
+		auto sliceEnd = RandomGeneratorUtil::nextItem(reverseDistanceMap[0], generator.rand);
+		
+		//at least 25% without water
+		bool endPassed = false;
+		for(int counter = 0; counter < reverseDistanceMap[0].size() / 4 || !endPassed; ++sliceStart, ++counter)
+		{
+			if(sliceStart == reverseDistanceMap[0].end())
+				sliceStart = reverseDistanceMap[0].begin();
+			
+			if(sliceStart == sliceEnd)
+				endPassed = true;
+			
+			noWaterArea.add(*sliceStart);
+		}
+		
+		rmg::Area noWaterSlice;
+		for(int i = 1; i < reverseDistanceMap.size(); ++i)
+		{
+			for(auto & t : reverseDistanceMap[i])
+			{
+				if(noWaterArea.distanceSqr(t) < 3)
+					noWaterSlice.add(t);
+			}
+			noWaterArea.unite(noWaterSlice);
+		}
+	}
+	
+	//generating some irregularity of coast
+	int coastIdMax = sqrt(reverseDistanceMap.size()); //size of coastTilesMap shows the most distant tile from water
+	assert(coastIdMax > 0);
+	std::list<int3> tilesQueue;
+	rmg::Tileset tilesChecked;
+	for(int coastId = coastIdMax; coastId >= 0; --coastId)
+	{
+		//amount of iterations shall be proportion of coast perimeter
+		const int coastLength = reverseDistanceMap[coastId].size() / (coastId + 3);
+		for(int coastIter = 0; coastIter < coastLength; ++coastIter)
+		{
+			int3 tile = *RandomGeneratorUtil::nextItem(reverseDistanceMap[coastId], generator.rand);
+			if(tilesChecked.find(tile) != tilesChecked.end())
+				continue;
+			
+			if(map.isUsed(tile) || map.isFree(tile)) //prevent placing water nearby town
+				continue;
+			
+			tilesQueue.push_back(tile);
+			tilesChecked.insert(tile);
+		}
+	}
+	
+	//if tile is marked as water - connect it with "big" water
+	while(!tilesQueue.empty())
+	{
+		int3 src = tilesQueue.front();
+		tilesQueue.pop_front();
+		
+		if(waterArea.contains(src))
+			continue;
+		
+		waterArea.add(src);
+				
+		map.foreach_neighbour(src, [&src, this, &tilesChecked, &tilesQueue](const int3 & dst)
+		{
+			if(tilesChecked.count(dst))
+				return;
+			
+			if(distanceMap[dst] >= 0 && distanceMap[src] - distanceMap[dst] == 1)
+			{
+				tilesQueue.push_back(dst);
+				tilesChecked.insert(dst);
+			}
+		});
+	}
+	
+	waterArea.subtract(noWaterArea);
+	
+	//start filtering of narrow places and coast atrifacts
+	rmg::Area waterAdd;
+	for(int coastId = 1; coastId <= coastIdMax; ++coastId)
+	{
+		for(auto& tile : reverseDistanceMap[coastId])
+		{
+			//collect neighbout water tiles
+			auto collectionLambda = [this](const int3 & t, std::set<int3> & outCollection)
+			{
+				if(waterArea.contains(t))
+				{
+					reverseDistanceMap[0].insert(t);
+					outCollection.insert(t);
+				}
+			};
+			std::set<int3> waterCoastDirect, waterCoastDiag;
+			map.foreachDirectNeighbour(tile, std::bind(collectionLambda, std::placeholders::_1, std::ref(waterCoastDirect)));
+			map.foreachDiagonalNeighbour(tile, std::bind(collectionLambda, std::placeholders::_1, std::ref(waterCoastDiag)));
+			int waterCoastDirectNum = waterCoastDirect.size();
+			int waterCoastDiagNum = waterCoastDiag.size();
+			
+			//remove tiles which are mostly covered by water
+			if(waterCoastDirectNum >= 3)
+			{
+				waterAdd.add(tile);
+				continue;
+			}
+			if(waterCoastDiagNum == 4 && waterCoastDirectNum == 2)
+			{
+				waterAdd.add(tile);
+				continue;
+			}
+			if(waterCoastDirectNum == 2 && waterCoastDiagNum >= 2)
+			{
+				int3 diagSum, dirSum;
+				for(auto & i : waterCoastDiag)
+					diagSum += i - tile;
+				for(auto & i : waterCoastDirect)
+					dirSum += i - tile;
+				if(diagSum == int3() || dirSum == int3())
+				{
+					waterAdd.add(tile);
+					continue;
+				}
+				if(waterCoastDiagNum == 3 && diagSum != dirSum)
+				{
+					waterAdd.add(tile);
+					continue;
+				}
+			}
+		}
+	}
+	
+	waterArea.unite(waterAdd);
+	
+	//filtering tiny "lakes"
+	for(auto& tile : reverseDistanceMap[0]) //now it's only coast-water tiles
+	{
+		if(!waterArea.contains(tile)) //for ground tiles
+			continue;
+		
+		std::vector<int3> groundCoast;
+		map.foreachDirectNeighbour(tile, [this, &groundCoast](const int3 & t)
+		{
+			if(!waterArea.contains(t) && zone.area().contains(t)) //for ground tiles of same zone
+			{
+				groundCoast.push_back(t);
+			}
+		});
+		
+		if(groundCoast.size() >= 3)
+		{
+			waterArea.erase(tile);
+		}
+		else
+		{
+			if(groundCoast.size() == 2)
+			{
+				if(groundCoast[0] + groundCoast[1] == int3())
+				{
+					waterArea.erase(tile);
+				}
+			}
+		}
+	}
+	
+	map.getZones()[waterZoneId]->area().unite(waterArea);
+	zone.area().subtract(waterArea);
+	zone.areaPossible().subtract(waterArea);
+	distanceMap = zone.area().computeDistanceMap(reverseDistanceMap);
+}
+
+void WaterAdopter::setWaterZone(TRmgTemplateZoneId water)
+{
+	waterZoneId = water;
+}
+
+rmg::Area WaterAdopter::getCoastTiles() const
+{
+	if(reverseDistanceMap.empty())
+		return rmg::Area();
+	
+	return rmg::Area(reverseDistanceMap.at(0));
+}
+
+char WaterAdopter::dump(const int3 & t)
+{
+	if(noWaterArea.contains(t))
+		return 'X';
+	if(waterArea.contains(t))
+		return '~';
+	
+	auto distanceMapIter = distanceMap.find(t);
+	if(distanceMapIter != distanceMap.end())
+	{
+		if(distanceMapIter->second > 9)
+			return '%';
+		
+		auto distStr = std::to_string(distanceMapIter->second);
+		if(distStr.length() > 0)
+			return distStr[0];
+	}
+	
+	return Modificator::dump(t);
+}

+ 36 - 0
lib/rmg/WaterAdopter.h

@@ -0,0 +1,36 @@
+/*
+ * WaterAdopter.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+
+class WaterAdopter: public Modificator
+{
+public:
+	MODIFICATOR(WaterAdopter);
+	
+	void process() override;
+	void init() override;
+	char dump(const int3 &) override;
+	
+	
+	void setWaterZone(TRmgTemplateZoneId water);
+	
+	rmg::Area getCoastTiles() const;
+	
+protected:
+	void createWater(EWaterContent::EWaterContent waterContent);
+	
+protected:
+	rmg::Area noWaterArea, waterArea;
+	TRmgTemplateZoneId waterZoneId;
+	std::map<int3, int> distanceMap;
+	std::map<int, rmg::Tileset> reverseDistanceMap;
+};

+ 359 - 0
lib/rmg/WaterProxy.cpp

@@ -0,0 +1,359 @@
+/*
+ * WaterProxy.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 "WaterProxy.h"
+#include "CMapGenerator.h"
+#include "RmgMap.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "RmgPath.h"
+#include "RmgObject.h"
+#include "ObjectManager.h"
+#include "Functions.h"
+#include "RoadPlacer.h"
+#include "TreasurePlacer.h"
+#include "TownPlacer.h"
+#include "ConnectionsPlacer.h"
+#include "TileInfo.h"
+#include "WaterAdopter.h"
+#include "RmgArea.h"
+
+void WaterProxy::process()
+{
+	for(auto & t : zone.area().getTilesVector())
+	{
+		map.setZoneID(t, zone.getId());
+		map.setOccupied(t, ETileType::POSSIBLE);
+	}
+	
+	paintZoneTerrain(zone, generator.rand, map, zone.getTerrainType());
+	
+	//check terrain type
+	for(auto & t : zone.area().getTilesVector())
+	{
+		assert(map.isOnMap(t));
+		assert(map.map().getTile(t).terType == zone.getTerrainType());
+	}
+	
+	for(auto z : map.getZones())
+	{
+		if(z.second->getId() == zone.getId())
+			continue;
+		
+		for(auto & t : z.second->area().getTilesVector())
+		{
+			if(map.map().getTile(t).terType == zone.getTerrainType())
+			{
+				z.second->areaPossible().erase(t);
+				z.second->area().erase(t);
+				zone.area().add(t);
+				zone.areaPossible().add(t);
+				map.setZoneID(t, zone.getId());
+				map.setOccupied(t, ETileType::POSSIBLE);
+			}
+		}
+	}
+	
+	if(!zone.area().contains(zone.getPos()))
+	{
+		zone.setPos(zone.area().getTilesVector().front());
+	}
+	
+	zone.initFreeTiles();
+	
+	collectLakes();
+}
+
+void WaterProxy::init()
+{
+	for(auto & z : map.getZones())
+	{
+		dependency(z.second->getModificator<TownPlacer>());
+		dependency(z.second->getModificator<WaterAdopter>());
+		postfunction(z.second->getModificator<ConnectionsPlacer>());
+		postfunction(z.second->getModificator<ObjectManager>());
+	}
+	POSTFUNCTION(TreasurePlacer);
+}
+
+const std::vector<WaterProxy::Lake> & WaterProxy::getLakes() const
+{
+	return lakes;
+}
+
+void WaterProxy::collectLakes()
+{
+	int lakeId = 0;
+	for(auto lake : connectedAreas(zone.getArea()))
+	{
+		lakes.push_back(Lake{});
+		lakes.back().area = lake;
+		lakes.back().distanceMap = lake.computeDistanceMap(lakes.back().reverseDistanceMap);
+		for(auto & t : lake.getBorderOutside())
+			if(map.isOnMap(t))
+				lakes.back().neighbourZones[map.getZoneID(t)].add(t);
+		for(auto & t : lake.getTiles())
+			lakeMap[t] = lakeId;
+		
+		//each lake must have at least one free tile
+		if(!lake.overlap(zone.freePaths()))
+			zone.freePaths().add(*lakes.back().reverseDistanceMap[lakes.back().reverseDistanceMap.size() - 1].begin());
+		
+		++lakeId;
+	}
+}
+
+RouteInfo WaterProxy::waterRoute(Zone & dst)
+{
+	RouteInfo result;
+	
+	auto * adopter = dst.getModificator<WaterAdopter>();
+	if(!adopter)
+		return result;
+	
+	if(adopter->getCoastTiles().empty())
+		return result;
+	
+	//block zones are not connected by template
+	for(auto& lake : lakes)
+	{
+		if(lake.neighbourZones.count(dst.getId()))
+		{
+			if(!lake.keepConnections.count(dst.getId()))
+			{
+				for(auto & ct : lake.neighbourZones[dst.getId()].getTiles())
+				{
+					if(map.isPossible(ct))
+						map.setOccupied(ct, ETileType::BLOCKED);
+				}
+				dst.areaPossible().subtract(lake.neighbourZones[dst.getId()]);
+				continue;
+			}
+						
+			int zoneTowns = 0;
+			if(auto * m = dst.getModificator<TownPlacer>())
+				zoneTowns = m->getTotalTowns();
+			
+			if(dst.getType() == ETemplateZoneType::PLAYER_START || dst.getType() == ETemplateZoneType::CPU_START || zoneTowns)
+			{
+				if(placeShipyard(dst, lake, generator.getConfig().shipyardGuard, result))
+				{
+					logGlobal->info("Shipyard successfully placed at zone %d", dst.getId());
+				}
+				else
+				{
+					logGlobal->warn("Shipyard placement failed, trying boat at zone %d", dst.getId());
+					if(placeBoat(dst, lake, result))
+					{
+						logGlobal->warn("Boat successfully placed at zone %d", dst.getId());
+					}
+					else
+					{
+						logGlobal->error("Boat placement failed at zone %d", dst.getId());
+					}
+				}
+			}
+			else
+			{
+				if(placeBoat(dst, lake, result))
+				{
+					logGlobal->info("Boat successfully placed at zone %d", dst.getId());
+				}
+				else
+				{
+					logGlobal->error("Boat placement failed at zone %d", dst.getId());
+				}
+			}
+		}
+	}
+	
+	return result;
+}
+
+bool WaterProxy::waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB)
+{
+	for(auto & lake : lakes)
+	{
+		if(lake.neighbourZones.count(zoneA) && lake.neighbourZones.count(zoneB))
+		{
+			lake.keepConnections.insert(zoneA);
+			lake.keepConnections.insert(zoneB);
+			return true;
+		}
+	}
+	return false;
+}
+
+bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
+{
+	auto * manager = zone.getModificator<ObjectManager>();
+	if(!manager)
+		return false;
+	
+	auto subObjects = VLC->objtypeh->knownSubObjects(Obj::BOAT);
+	auto* boat = (CGBoat*)VLC->objtypeh->getHandlerFor(Obj::BOAT, *RandomGeneratorUtil::nextItem(subObjects, generator.rand))->create(ObjectTemplate());
+	
+	rmg::Object rmgObject(*boat);
+	rmgObject.setTemplate(zone.getTerrainType());
+	
+	auto waterAvailable = zone.areaPossible() + zone.freePaths();
+	rmg::Area coast = lake.neighbourZones.at(land.getId()); //having land tiles
+	coast.intersect(land.areaPossible() + land.freePaths()); //having only available land tiles
+	auto boardingPositions = coast.getSubarea([&waterAvailable](const int3 & tile) //tiles where boarding is possible
+	{
+		rmg::Area a({tile});
+		a = a.getBorderOutside();
+		a.intersect(waterAvailable);
+		return !a.empty();
+	});
+	
+	while(!boardingPositions.empty())
+	{
+		auto boardingPosition = *boardingPositions.getTiles().begin();
+		rmg::Area shipPositions({boardingPosition});
+		auto boutside = shipPositions.getBorderOutside();
+		shipPositions.assign(boutside);
+		shipPositions.intersect(waterAvailable);
+		if(shipPositions.empty())
+		{
+			boardingPositions.erase(boardingPosition);
+			continue;
+		}
+		
+		//try to place boat at water, create paths on water and land
+		auto path = manager->placeAndConnectObject(shipPositions, rmgObject, 2, false, true, false);
+		auto landPath = land.searchPath(boardingPosition, false);
+		if(!path.valid() || !landPath.valid())
+		{
+			boardingPositions.erase(boardingPosition);
+			continue;
+		}
+		
+		info.blocked = rmgObject.getArea();
+		info.visitable = rmgObject.getVisitablePosition();
+		info.boarding = boardingPosition;
+		info.water = shipPositions;
+		
+		zone.connectPath(path);
+		land.connectPath(landPath);
+		manager->placeObject(rmgObject, false, true);
+		break;
+	}
+	
+	return !boardingPositions.empty();
+}
+
+bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, RouteInfo & info)
+{
+	auto * manager = land.getModificator<ObjectManager>();
+	if(!manager)
+		return false;
+	
+	int subtype = chooseRandomAppearance(generator.rand, Obj::SHIPYARD, land.getTerrainType());
+	auto shipyard = (CGShipyard*) VLC->objtypeh->getHandlerFor(Obj::SHIPYARD, subtype)->create(ObjectTemplate());
+	shipyard->tempOwner = PlayerColor::NEUTRAL;
+	
+	rmg::Object rmgObject(*shipyard);
+	rmgObject.setTemplate(land.getTerrainType());
+	bool guarded = manager->addGuard(rmgObject, guard);
+	
+	auto waterAvailable = zone.areaPossible() + zone.freePaths();
+	rmg::Area coast = lake.neighbourZones.at(land.getId()); //having land tiles
+	coast.intersect(land.areaPossible() + land.freePaths()); //having only available land tiles
+	auto boardingPositions = coast.getSubarea([&waterAvailable](const int3 & tile) //tiles where boarding is possible
+	{
+		rmg::Area a({tile});
+		a = a.getBorderOutside();
+		a.intersect(waterAvailable);
+		return !a.empty();
+	});
+	
+	while(!boardingPositions.empty())
+	{
+		auto boardingPosition = *boardingPositions.getTiles().begin();
+		rmg::Area shipPositions({boardingPosition});
+		auto boutside = shipPositions.getBorderOutside();
+		shipPositions.assign(boutside);
+		shipPositions.intersect(waterAvailable);
+		if(shipPositions.empty())
+		{
+			boardingPositions.erase(boardingPosition);
+			continue;
+		}
+		
+		//try to place shipyard close to boarding position and appropriate water access
+		auto path = manager->placeAndConnectObject(land.areaPossible(), rmgObject, [&rmgObject, &shipPositions, &boardingPosition](const int3 & tile)
+		{
+			rmg::Area shipyardOut(rmgObject.getArea().getBorderOutside());
+			if(!shipyardOut.contains(boardingPosition) || (shipyardOut * shipPositions).empty())
+				return -1.f;
+			
+			return 1.0f;
+		}, guarded, true, false);
+		
+		//search path to boarding position
+		auto searchArea = land.areaPossible() - rmgObject.getArea();
+		rmg::Path pathToBoarding(searchArea);
+		pathToBoarding.connect(land.freePaths());
+		pathToBoarding.connect(path);
+		pathToBoarding = pathToBoarding.search(boardingPosition, false);
+		
+		//make sure shipyard places ship at position we defined
+		rmg::Area shipyardOutToBlock(rmgObject.getArea().getBorderOutside());
+		shipyardOutToBlock.intersect(waterAvailable);
+		shipyardOutToBlock.subtract(shipPositions);
+		shipPositions.subtract(shipyardOutToBlock);
+		auto pathToBoat = zone.searchPath(shipPositions, true);
+		
+		if(!path.valid() || !pathToBoarding.valid() || !pathToBoat.valid())
+		{
+			boardingPositions.erase(boardingPosition);
+			continue;
+		}
+		
+		land.connectPath(path);
+		land.connectPath(pathToBoarding);
+		zone.connectPath(pathToBoat);
+		
+		info.blocked = rmgObject.getArea();
+		info.visitable = rmgObject.getVisitablePosition();
+		info.boarding = boardingPosition;
+		info.water = shipPositions;
+		
+		manager->placeObject(rmgObject, guarded, true);
+		
+		zone.areaPossible().subtract(shipyardOutToBlock);
+		for(auto & i : shipyardOutToBlock.getTilesVector())
+			if(map.isOnMap(i) && map.isPossible(i))
+				map.setOccupied(i, ETileType::BLOCKED);
+		
+		break;
+	}
+	
+	return !boardingPositions.empty();
+}
+
+char WaterProxy::dump(const int3 & t)
+{
+	auto lakeIter = lakeMap.find(t);
+	if(lakeIter == lakeMap.end())
+		return '?';
+	
+	Lake & lake = lakes[lakeMap.at(t)];
+	for(auto i : lake.neighbourZones)
+	{
+		if(i.second.contains(t))
+			return lake.keepConnections.count(i.first) ? std::to_string(i.first)[0] : '=';
+	}
+	
+	return '~';
+}

+ 55 - 0
lib/rmg/WaterProxy.h

@@ -0,0 +1,55 @@
+/*
+ * WaterProxy.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+
+struct RouteInfo
+{
+	rmg::Area blocked;
+	int3 visitable;
+	int3 boarding;
+	rmg::Area water;
+};
+
+class WaterProxy: public Modificator
+{
+public:
+	MODIFICATOR(WaterProxy);
+	
+	//subclass to store disconnected parts of water zone
+	struct Lake
+	{
+		rmg::Area area; //water tiles
+		std::map<int3, int> distanceMap; //distance map for lake
+		std::map<int, rmg::Tileset> reverseDistanceMap;
+		std::map<TRmgTemplateZoneId, rmg::Area> neighbourZones; //zones boardered. Area - part of land
+		std::set<TRmgTemplateZoneId> keepConnections;
+	};
+		
+	bool waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB);
+	RouteInfo waterRoute(Zone & dst);
+	
+	void process() override;
+	void init() override;
+	char dump(const int3 &) override;
+	const std::vector<Lake> & getLakes() const;
+	
+protected:
+	void collectLakes();
+	
+	bool placeShipyard(Zone & land, const Lake & lake, si32 guard, RouteInfo & info);
+	bool placeBoat(Zone & land, const Lake & lake, RouteInfo & info);
+		
+protected:	
+	std::vector<Lake> lakes; //disconnected parts of zone. Used to work with water zones
+	std::map<int3, int> lakeMap; //map tile on lakeId which is position of lake in lakes array +1
+};
+

+ 116 - 0
lib/rmg/WaterRoutes.cpp

@@ -0,0 +1,116 @@
+/*
+ * WaterRoutes.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 "WaterRoutes.h"
+#include "WaterProxy.h"
+#include "CMapGenerator.h"
+#include "RmgMap.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "RmgPath.h"
+#include "RmgObject.h"
+#include "ObjectManager.h"
+#include "Functions.h"
+#include "RoadPlacer.h"
+#include "TreasurePlacer.h"
+#include "TownPlacer.h"
+#include "ConnectionsPlacer.h"
+#include "TileInfo.h"
+#include "WaterAdopter.h"
+#include "RmgArea.h"
+
+void WaterRoutes::process()
+{
+	auto * wproxy = zone.getModificator<WaterProxy>();
+	if(!wproxy)
+		return;
+	
+	for(auto & z : map.getZones())
+	{
+		if(z.first != zone.getId())
+			result.push_back(wproxy->waterRoute(*z.second));
+	}
+	
+	//prohibit to place objects on sealed off lakes
+	for(auto & lake : wproxy->getLakes())
+	{
+		if((lake.area * zone.freePaths()).getTilesVector().size() == 1)
+		{
+			zone.freePaths().subtract(lake.area);
+			zone.areaPossible().subtract(lake.area);
+		}
+	}
+	
+	//prohibit to place objects on the borders
+	for(auto & t : zone.area().getBorder())
+	{
+		if(zone.areaPossible().contains(t))
+		{
+			std::vector<int3> landTiles;
+			map.foreachDirectNeighbour(t, [this, &landTiles, &t](const int3 & c)
+			{
+				if(map.isOnMap(c) && map.getZoneID(c) != zone.getId())
+				{
+					landTiles.push_back(c - t);
+				}
+			});
+			
+			if(landTiles.size() == 2)
+			{
+				int3 o = landTiles[0] + landTiles[1];
+				if(o.x * o.x * o.y * o.y == 1) 
+				{
+					zone.areaPossible().erase(t);
+					zone.areaUsed().add(t);
+				}
+			}
+		}
+	}
+}
+
+void WaterRoutes::init()
+{
+	for(auto & z : map.getZones())
+	{
+		dependency(z.second->getModificator<ConnectionsPlacer>());
+		postfunction(z.second->getModificator<ObjectManager>());
+		postfunction(z.second->getModificator<TreasurePlacer>());
+	}
+	dependency(zone.getModificator<WaterProxy>());
+	postfunction(zone.getModificator<TreasurePlacer>());
+}
+
+char WaterRoutes::dump(const int3 & t)
+{
+	for(auto & i : result)
+	{
+		if(t == i.boarding)
+			return 'B';
+		if(t == i.visitable)
+			return '@';
+		if(i.blocked.contains(t))
+			return '#';
+		if(i.water.contains(t))
+		{
+			if(zone.freePaths().contains(t))
+				return '+';
+			else
+				return '-';
+		}
+	}
+	if(zone.freePaths().contains(t))
+		return '.';
+	if(zone.area().contains(t))
+		return '~';
+	return ' ';
+}
+

+ 27 - 0
lib/rmg/WaterRoutes.h

@@ -0,0 +1,27 @@
+/*
+ * WaterRoutes.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+#include "Zone.h"
+
+struct RouteInfo;
+
+class WaterRoutes: public Modificator
+{
+public:
+	MODIFICATOR(WaterRoutes);
+	
+	void process() override;
+	void init() override;
+	char dump(const int3 &) override;
+	
+private:
+	std::vector<RouteInfo> result;
+};

+ 410 - 0
lib/rmg/Zone.cpp

@@ -0,0 +1,410 @@
+/*
+ * Zone.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 "Zone.h"
+#include "RmgMap.h"
+#include "Functions.h"
+#include "TileInfo.h"
+#include "../mapping/CMap.h"
+#include "../CStopWatch.h"
+#include "CMapGenerator.h"
+#include "RmgPath.h"
+
+std::function<bool(const int3 &)> AREA_NO_FILTER = [](const int3 & t)
+{
+	return true;
+};
+
+Zone::Zone(RmgMap & map, CMapGenerator & generator)
+					: ZoneOptions(),
+					townType(ETownType::NEUTRAL),
+					terrainType(Terrain("grass")),
+					map(map),
+					generator(generator)
+{
+	
+}
+
+bool Zone::isUnderground() const
+{
+	return getPos().z;
+}
+
+void Zone::setOptions(const ZoneOptions& options)
+{
+	ZoneOptions::operator=(options);
+}
+
+float3 Zone::getCenter() const
+{
+	return center;
+}
+
+void Zone::setCenter(const float3 &f)
+{
+	//limit boundaries to (0,1) square
+	
+	//alternate solution - wrap zone around unitary square. If it doesn't fit on one side, will come out on the opposite side
+	center = f;
+	
+	center.x = static_cast<float>(std::fmod(center.x, 1));
+	center.y = static_cast<float>(std::fmod(center.y, 1));
+	
+	if(center.x < 0) //fmod seems to work only for positive numbers? we want to stay positive
+		center.x = 1 - std::abs(center.x);
+	if(center.y < 0)
+		center.y = 1 - std::abs(center.y);
+}
+
+int3 Zone::getPos() const
+{
+	return pos;
+}
+
+void Zone::setPos(const int3 &Pos)
+{
+	pos = Pos;
+}
+
+const rmg::Area & Zone::getArea() const
+{
+	return dArea;
+}
+
+rmg::Area & Zone::area()
+{
+	return dArea;
+}
+
+rmg::Area & Zone::areaPossible()
+{
+	return dAreaPossible;
+}
+
+rmg::Area & Zone::areaUsed()
+{
+	return dAreaUsed;
+}
+
+void Zone::clearTiles()
+{
+	dArea.clear();
+	dAreaPossible.clear();
+	dAreaFree.clear();
+}
+
+void Zone::initFreeTiles()
+{
+	rmg::Tileset possibleTiles;
+	vstd::copy_if(dArea.getTiles(), vstd::set_inserter(possibleTiles), [this](const int3 &tile) -> bool
+	{
+		return map.isPossible(tile);
+	});
+	dAreaPossible.assign(possibleTiles);
+	
+	if(dAreaFree.empty())
+	{
+		dAreaPossible.erase(pos);
+		dAreaFree.add(pos); //zone must have at least one free tile where other paths go - for instance in the center
+	}
+}
+
+rmg::Area & Zone::freePaths()
+{
+	return dAreaFree;
+}
+
+si32 Zone::getTownType() const
+{
+	return townType;
+}
+
+void Zone::setTownType(si32 town)
+{
+	townType = town;
+}
+
+const Terrain & Zone::getTerrainType() const
+{
+	return terrainType;
+}
+
+void Zone::setTerrainType(const Terrain & terrain)
+{
+	terrainType = terrain;
+}
+
+rmg::Path Zone::searchPath(const rmg::Area & src, bool onlyStraight, std::function<bool(const int3 &)> areafilter) const
+///connect current tile to any other free tile within zone
+{
+	auto movementCost = [this](const int3 & s, const int3 & d)
+	{
+		if(map.isFree(d))
+			return 1;
+		else if (map.isPossible(d))
+			return 2;
+		return 3;
+	};
+	
+	auto area = (dAreaPossible + dAreaFree).getSubarea(areafilter);
+	rmg::Path freePath(area);
+	freePath.connect(dAreaFree);
+	
+	//connect to all pieces
+	auto goals = connectedAreas(src);
+	for(auto & goal : goals)
+	{
+		auto path = freePath.search(goal, onlyStraight, movementCost);
+		if(path.getPathArea().empty())
+			return rmg::Path::invalid();
+		
+		freePath.connect(path.getPathArea());
+	}
+	
+	return freePath;
+}
+
+rmg::Path Zone::searchPath(const int3 & src, bool onlyStraight, std::function<bool(const int3 &)> areafilter) const
+///connect current tile to any other free tile within zone
+{
+	return searchPath(rmg::Area({src}), onlyStraight, areafilter);
+}
+
+void Zone::connectPath(const rmg::Path & path)
+///connect current tile to any other free tile within zone
+{
+	dAreaPossible.subtract(path.getPathArea());
+	dAreaFree.unite(path.getPathArea());
+	for(auto & t : path.getPathArea().getTilesVector())
+		map.setOccupied(t, ETileType::FREE);
+}
+
+void Zone::fractalize()
+{
+	rmg::Area clearedTiles(dAreaFree);
+	rmg::Area possibleTiles(dAreaPossible);
+	rmg::Area tilesToIgnore; //will be erased in this iteration
+	
+	//the more treasure density, the greater distance between paths. Scaling is experimental.
+	int totalDensity = 0;
+	for(auto ti : treasureInfo)
+		totalDensity += ti.density;
+	const float minDistance = 10 * 10; //squared
+	
+	if(type != ETemplateZoneType::JUNCTION)
+	{
+		//junction is not fractalized, has only one straight path
+		//everything else remains blocked
+		while(!possibleTiles.empty())
+		{
+			//link tiles in random order
+			std::vector<int3> tilesToMakePath = possibleTiles.getTilesVector();
+			RandomGeneratorUtil::randomShuffle(tilesToMakePath, generator.rand);
+			
+			int3 nodeFound(-1, -1, -1);
+			
+			for(auto tileToMakePath : tilesToMakePath)
+			{
+				//find closest free tile
+				int3 closestTile = clearedTiles.nearest(tileToMakePath);
+				if(closestTile.dist2dSQ(tileToMakePath) <= minDistance)
+					tilesToIgnore.add(tileToMakePath);
+				else
+				{
+					//if tiles are not close enough, make path to it
+					nodeFound = tileToMakePath;
+					clearedTiles.add(nodeFound); //from now on nearby tiles will be considered handled
+					break; //next iteration - use already cleared tiles
+				}
+			}
+			
+			possibleTiles.subtract(tilesToIgnore);
+			if(!nodeFound.valid()) //nothing else can be done (?)
+				break;
+			tilesToIgnore.clear();
+		}
+	}
+	
+	//cut straight paths towards the center. A* is too slow for that.
+	auto areas = connectedAreas(clearedTiles);
+	for(auto & area : areas)
+	{
+		if(dAreaFree.overlap(area))
+			continue; //already found
+			
+		auto availableArea = dAreaPossible + dAreaFree;
+		rmg::Path path(availableArea);
+		path.connect(dAreaFree);
+		auto res = path.search(area, false);
+		if(res.getPathArea().empty())
+		{
+			dAreaPossible.subtract(area);
+			dAreaFree.subtract(area);
+			for(auto & t : area.getTiles())
+				map.setOccupied(t, ETileType::BLOCKED);
+		}
+		else
+		{
+			dAreaPossible.subtract(res.getPathArea());
+			dAreaFree.unite(res.getPathArea());
+			for(auto & t : res.getPathArea().getTiles())
+				map.setOccupied(t, ETileType::FREE);
+		}
+	}
+	
+	//now block most distant tiles away from passages
+	float blockDistance = minDistance * 0.25f;
+	auto areaToBlock = dArea.getSubarea([this, blockDistance](const int3 & t)
+	{
+		float distance = static_cast<float>(dAreaFree.distanceSqr(t));
+		return distance > blockDistance;
+	});
+	dAreaPossible.subtract(areaToBlock);
+	dAreaFree.subtract(areaToBlock);
+	for(auto & t : areaToBlock.getTiles())
+		map.setOccupied(t, ETileType::BLOCKED);
+}
+
+void Zone::initModificators()
+{
+	for(auto & modificator : modificators)
+	{
+		modificator->init();
+	}
+	logGlobal->info("Zone %d modificators initialized", getId());
+}
+
+void Zone::processModificators()
+{
+	for(auto & modificator : modificators)
+	{
+		try
+		{
+			modificator->run();
+		}
+		catch (const rmgException & e)
+		{
+			logGlobal->info("Zone %d, modificator %s - FAILED: %s", getId(), e.what());
+			throw e;
+		}
+	}
+	logGlobal->info("Zone %d filled successfully", getId());
+}
+
+Modificator::Modificator(Zone & zone, RmgMap & map, CMapGenerator & generator) : zone(zone), map(map), generator(generator)
+{
+}
+
+void Modificator::setName(const std::string & n)
+{
+	name = n;
+}
+
+const std::string & Modificator::getName() const
+{
+	return name;
+}
+
+bool Modificator::isFinished() const
+{
+	return finished;
+}
+
+void Modificator::run()
+{
+	started = true;
+	if(!finished)
+	{
+		for(auto * modificator : preceeders)
+		{
+			if(!modificator->started)
+				modificator->run();
+		}
+		logGlobal->info("Modificator zone %d - %s - started", zone.getId(), getName());
+		CStopWatch processTime;
+		try
+		{
+			process();
+		}
+		catch(rmgException &e)
+		{
+			logGlobal->error("Modificator %s, exception: %s", getName(), e.what());
+		}
+#ifdef RMG_DUMP
+		dump();
+#endif
+		finished = true;
+		logGlobal->info("Modificator zone %d - %s - done (%d ms)", zone.getId(), getName(), processTime.getDiff());
+	}
+}
+
+void Modificator::dependency(Modificator * modificator)
+{
+	if(modificator && modificator != this)
+	{
+		if(std::find(preceeders.begin(), preceeders.end(), modificator) == preceeders.end())
+			preceeders.push_back(modificator);
+	}
+}
+
+void Modificator::postfunction(Modificator * modificator)
+{
+	if(modificator && modificator != this)
+	{
+		if(std::find(modificator->preceeders.begin(), modificator->preceeders.end(), this) == modificator->preceeders.end())
+			modificator->preceeders.push_back(this);
+	}
+}
+
+void Modificator::dump()
+{
+	std::ofstream out(boost::to_string(boost::format("seed_%d_modzone_%d_%s.txt") % generator.getRandomSeed() % zone.getId() % getName()));
+	auto & mapInstance = map.map();
+	int levels = mapInstance.twoLevel ? 2 : 1;
+	int width =  mapInstance.width;
+	int height = mapInstance.height;
+	for (int k = 0; k < levels; k++)
+	{
+		for(int j=0; j<height; j++)
+		{
+			for (int i=0; i<width; i++)
+			{
+				out << dump(int3(i, j, k));
+			}
+			out << std::endl;
+		}
+		out << std::endl;
+	}
+	out << std::endl;
+}
+
+char Modificator::dump(const int3 & t)
+{
+	if(zone.freePaths().contains(t))
+		return '.'; //free path
+	if(zone.areaPossible().contains(t))
+		return ' '; //possible
+	if(zone.areaUsed().contains(t))
+		return 'U'; //used
+	if(zone.area().contains(t))
+	{
+		if(map.shouldBeBlocked(t))
+			return '#'; //obstacle
+		else
+			return '^'; //visitable points?
+	}
+	return '?';
+}
+
+Modificator::~Modificator()
+{
+	
+}

+ 143 - 0
lib/rmg/Zone.h

@@ -0,0 +1,143 @@
+/*
+ * Zone.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../GameConstants.h"
+#include "float3.h"
+#include "../int3.h"
+#include "CRmgTemplate.h"
+#include "RmgArea.h"
+#include "RmgPath.h"
+#include "RmgObject.h"
+
+//uncomment to generate dumps afger every step of map generation
+//#define RMG_DUMP
+
+#define MODIFICATOR(x) x(Zone & z, RmgMap & m, CMapGenerator & g): Modificator(z, m, g) {setName(#x);}
+#define DEPENDENCY(x) 		dependency(zone.getModificator<x>());
+#define POSTFUNCTION(x)		postfunction(zone.getModificator<x>());
+#define DEPENDENCY_ALL(x) 	for(auto & z : map.getZones()) \
+							{ \
+								dependency(z.second->getModificator<x>()); \
+							}
+#define POSTFUNCTION_ALL(x) for(auto & z : map.getZones()) \
+							{ \
+								postfunction(z.second->getModificator<x>()); \
+							}
+
+class RmgMap;
+class CMapGenerator;
+class Zone;
+
+class Modificator
+{
+public:
+	Modificator() = delete;
+	Modificator(Zone & zone, RmgMap & map, CMapGenerator & generator);
+	
+	virtual void process() = 0;
+	virtual void init() {/*override to add dependencies*/}
+	virtual char dump(const int3 &);
+	virtual ~Modificator();
+	
+	void setName(const std::string & n);
+	const std::string & getName() const;
+	
+	void run();
+	void dependency(Modificator * modificator);
+	void postfunction(Modificator * modificator);
+
+protected:
+	RmgMap & map;
+	CMapGenerator & generator;
+	Zone & zone;
+	
+	bool isFinished() const;
+	
+private:
+	std::string name;
+	bool started = false;
+	bool finished = false;
+	std::list<Modificator*> preceeders; //must be ordered container
+	void dump();
+};
+
+extern std::function<bool(const int3 &)> AREA_NO_FILTER;
+
+class Zone : public rmg::ZoneOptions
+{
+public:
+	Zone(RmgMap & map, CMapGenerator & generator);
+	Zone(const Zone &) = delete;
+	
+	void setOptions(const rmg::ZoneOptions & options);
+	bool isUnderground() const;
+	
+	float3 getCenter() const;
+	void setCenter(const float3 &f);
+	int3 getPos() const;
+	void setPos(const int3 &pos);
+	
+	const rmg::Area & getArea() const;
+	rmg::Area & area();
+	rmg::Area & areaPossible();
+	rmg::Area & freePaths();
+	rmg::Area & areaUsed();
+	
+	void initFreeTiles();
+	void clearTiles();
+	void fractalize();
+	
+	si32 getTownType() const;
+	void setTownType(si32 town);
+	const Terrain & getTerrainType() const;
+	void setTerrainType(const Terrain & terrain);
+		
+	void connectPath(const rmg::Path & path);
+	rmg::Path searchPath(const rmg::Area & src, bool onlyStraight, std::function<bool(const int3 &)> areafilter = AREA_NO_FILTER) const;
+	rmg::Path searchPath(const int3 & src, bool onlyStraight, std::function<bool(const int3 &)> areafilter = AREA_NO_FILTER) const;
+	
+	template<class T>
+	T* getModificator()
+	{
+		for(auto & m : modificators)
+			if(auto * mm = dynamic_cast<T*>(m.get()))
+				return mm;
+		return nullptr;
+	}
+	
+	template<class T>
+	void addModificator()
+	{
+		modificators.emplace_back(new T(*this, map, generator));
+	}
+	
+	void initModificators();
+	void processModificators();
+	
+protected:
+	CMapGenerator & generator;
+	RmgMap & map;
+	std::list<std::unique_ptr<Modificator>> modificators;
+	
+	//placement info
+	int3 pos;
+	float3 center;
+	rmg::Area dArea; //irregular area assined to zone
+	rmg::Area dAreaPossible;
+	rmg::Area dAreaFree; //core paths of free tiles that all other objects will be linked to
+	rmg::Area dAreaUsed;
+	
+	//template info
+	si32 townType;
+	Terrain terrainType;
+	
+};