Browse Source

Merge pull request #2218 from vcmi/pregenerate_treasures

Okay, merging this for PvP feedback.
DjWarmonger 2 years ago
parent
commit
3be7720868

+ 10 - 18
Mods/vcmi/config/vcmi/rmg/hdmod/longRun.JSON

@@ -18,8 +18,7 @@
 				"treasure" :
 				[
 					{ "min" : 300, "max" : 3000, "density" : 12 },
-					{ "min" : 5000, "max" : 9000, "density" : 6 },
-					{ "min" : 0, "max" : 0, "density" : 1 }
+					{ "min" : 5000, "max" : 9000, "density" : 6 }
 				]
 			},
 			"2" :
@@ -34,8 +33,8 @@
 				"mines" : { "wood" : 1, "mercury" : 1, "ore" : 1, "sulfur" : 1, "crystal" : 1, "gems" : 1, "gold" : 1 },
 				"treasure" :
 				[
-					{ "min" : 5000, "max" : 7000, "density" : 30 },
 					{ "min" : 10000, "max" : 15000, "density" : 1 },
+					{ "min" : 5000, "max" : 7000, "density" : 30 },
 					{ "min" : 300, "max" : 3000, "density" : 5 }
 				]
 			},
@@ -51,8 +50,8 @@
 				"mines" : { "wood" : 1, "mercury" : 1, "ore" : 1, "sulfur" : 1, "crystal" : 1, "gems" : 1, "gold" : 0 },
 				"treasure" :
 				[
-					{ "min" : 12000, "max" : 16000, "density" : 5 },
 					{ "min" : 20000, "max" : 21000, "density" : 6 },
+					{ "min" : 12000, "max" : 16000, "density" : 5 },
 					{ "min" : 300, "max" : 3000, "density" : 5 }
 				]
 			},
@@ -69,8 +68,7 @@
 				"treasure" :
 				[
 					{ "min" : 25000, "max" : 30000, "density" : 10 },
-					{ "min" : 300, "max" : 3000, "density" : 10 },
-					{ "min" : 0, "max" : 0, "density" : 1 }
+					{ "min" : 300, "max" : 3000, "density" : 10 }
 				]
 			},
 			"5" :
@@ -85,9 +83,7 @@
 				"mines" : { "wood" : 0, "mercury" : 0, "ore" : 0, "sulfur" : 0, "crystal" : 0, "gems" : 0, "gold" : 4 },
 				"treasure" :
 				[
-					{ "min" : 30000, "max" : 90000, "density" : 25 },
-					{ "min" : 0, "max" : 0, "density" : 1 },
-					{ "min" : 0, "max" : 0, "density" : 1 }
+					{ "min" : 30000, "max" : 90000, "density" : 25 }
 				]
 			},
 			"6" :
@@ -167,8 +163,7 @@
 				"treasure" :
 				[
 					{ "min" : 300, "max" : 3000, "density" : 12 },
-					{ "min" : 5000, "max" : 9000, "density" : 6 },
-					{ "min" : 0, "max" : 0, "density" : 1 }
+					{ "min" : 5000, "max" : 9000, "density" : 6 }
 				]
 			},
 			"2" :
@@ -183,8 +178,8 @@
 				"mines" : { "wood" : 1, "mercury" : 1, "ore" : 1, "sulfur" : 1, "crystal" : 1, "gems" : 1, "gold" : 1 },
 				"treasure" :
 				[
-					{ "min" : 5000, "max" : 7000, "density" : 10 },
 					{ "min" : 10000, "max" : 15000, "density" : 1 },
+					{ "min" : 5000, "max" : 7000, "density" : 10 },
 					{ "min" : 300, "max" : 3000, "density" : 5 }
 				]
 			},
@@ -200,8 +195,8 @@
 				"mines" : { "wood" : 1, "mercury" : 1, "ore" : 1, "sulfur" : 1, "crystal" : 1, "gems" : 1, "gold" : 0 },
 				"treasure" :
 				[
-					{ "min" : 12000, "max" : 16000, "density" : 5 },
 					{ "min" : 20000, "max" : 21000, "density" : 6 },
+					{ "min" : 12000, "max" : 16000, "density" : 5 },
 					{ "min" : 300, "max" : 3000, "density" : 5 }
 				]
 			},
@@ -218,8 +213,7 @@
 				"treasure" :
 				[
 					{ "min" : 25000, "max" : 30000, "density" : 10 },
-					{ "min" : 300, "max" : 3000, "density" : 10 },
-					{ "min" : 0, "max" : 0, "density" : 1 }
+					{ "min" : 300, "max" : 3000, "density" : 10 }
 				]
 			},
 			"5" :
@@ -233,9 +227,7 @@
 				"mines" : { "wood" : 0, "mercury" : 0, "ore" : 0, "sulfur" : 0, "crystal" : 0, "gems" : 0, "gold" : 10 },
 				"treasure" :
 				[
-					{ "min" : 30000, "max" : 90000, "density" : 25 },
-					{ "min" : 0, "max" : 0, "density" : 1 },
-					{ "min" : 0, "max" : 0, "density" : 1 }
+					{ "min" : 30000, "max" : 90000, "density" : 25 }
 				]
 			},
 			"6" :

+ 1 - 0
config/objects/generic.json

@@ -511,6 +511,7 @@
 				"index" : 0,
 				"aiValue" : 100,
 				"rmg" : {
+					"zoneLimit"	: 1,
 					"mapLimit"	: 32,
 					"value"		: 100,
 					"rarity"	: 20

+ 28 - 0
config/objects/moddables.json

@@ -50,6 +50,34 @@
 		}
 	},
 
+	"randomResource":
+	{
+		"index" :76,
+		"handler": "resource",
+		"base" : {
+			"base" : {
+				"visitableFrom" : [ "+++", "+-+", "+++" ],
+				"mask" : [ "VA" ]
+			}
+		},
+		"types" : {
+			"randomResource" : {
+				"index" : 0,
+				"rmg" : {
+					"value"		: 1500,
+					"rarity"	: 2000
+				},
+				"templates" :
+				{
+					"res" :
+					{
+						"animation" : "AVTrndm0.def"
+					}
+				}
+			}
+		}
+	},
+
 	// subtype: resource ID
 	"resource" : {
 		"index" :79,

+ 2 - 0
config/objects/rewardableBonusing.json

@@ -15,6 +15,7 @@
 				"index" : 0,
 				"aiValue" : 100,
 				"rmg" : {
+					"zoneLimit"	: 1,
 					"value"		: 100,
 					"rarity"	: 100
 				},
@@ -253,6 +254,7 @@
 				"index" : 0,
 				"aiValue" : 100,
 				"rmg" : {
+					"zoneLimit"	: 1,
 					"value"		: 100,
 					"rarity"	: 20
 				},

+ 18 - 4
lib/rmg/RmgArea.cpp

@@ -64,21 +64,35 @@ void Area::invalidate()
 	dBorderOutsideCache.clear();
 }
 
-bool Area::connected() const
+bool Area::connected(bool noDiagonals) 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 (noDiagonals)
+		{
+			for (auto& i : dirs4)
+			{
+				if (connected.count(t + i))
+				{
+					queue.push_back(t + i);
+				}
+			}
+		}
+		else
 		{
-			if(connected.count(t + i))
+			for (auto& i : int3::getDirs())
 			{
-				queue.push_back(t + i);
+				if (connected.count(t + i))
+				{
+					queue.push_back(t + i);
+				}
 			}
 		}
 	}

+ 1 - 1
lib/rmg/RmgArea.h

@@ -44,7 +44,7 @@ namespace rmg
 
 		Area getSubarea(const std::function<bool(const int3 &)> & filter) const;
 
-		bool connected() const; //is connected
+		bool connected(bool noDiagonals = false) const; //is connected
 		bool empty() const;
 		bool contains(const int3 & tile) const;
 		bool contains(const std::vector<int3> & tiles) const;

+ 26 - 3
lib/rmg/RmgObject.cpp

@@ -161,17 +161,21 @@ const CGObjectInstance & Object::Instance::object() const
 	return dObject;
 }
 
-Object::Object(CGObjectInstance & object, const int3 & position)
+Object::Object(CGObjectInstance & object, const int3 & position):
+	guarded(false)
 {
 	addInstance(object, position);
 }
 
-Object::Object(CGObjectInstance & object)
+Object::Object(CGObjectInstance & object):
+	guarded(false)
 {
 	addInstance(object);
 }
 
-Object::Object(const Object & object): dStrength(object.dStrength)
+Object::Object(const Object & object):
+	dStrength(object.dStrength),
+	guarded(false)
 {
 	for(const auto & i : object.dInstances)
 		addInstance(const_cast<CGObjectInstance &>(i.object()), i.getPosition());
@@ -197,7 +201,9 @@ std::list<const Object::Instance*> Object::instances() const
 void Object::addInstance(Instance & object)
 {
 	//assert(object.dParent == *this);
+	setGuardedIfMonster(object);
 	dInstances.push_back(object);
+
 	dFullAreaCache.clear();
 	dAccessibleAreaCache.clear();
 	dAccessibleAreaFullCache.clear();
@@ -206,6 +212,8 @@ void Object::addInstance(Instance & object)
 Object::Instance & Object::addInstance(CGObjectInstance & object)
 {
 	dInstances.emplace_back(*this, object);
+	setGuardedIfMonster(dInstances.back());
+
 	dFullAreaCache.clear();
 	dAccessibleAreaCache.clear();
 	dAccessibleAreaFullCache.clear();
@@ -215,6 +223,8 @@ Object::Instance & Object::addInstance(CGObjectInstance & object)
 Object::Instance & Object::addInstance(CGObjectInstance & object, const int3 & position)
 {
 	dInstances.emplace_back(*this, object, position);
+	setGuardedIfMonster(dInstances.back());
+
 	dFullAreaCache.clear();
 	dAccessibleAreaCache.clear();
 	dAccessibleAreaFullCache.clear();
@@ -302,6 +312,19 @@ const int3 Object::getVisibleTop() const
 	return topTile;
 }
 
+bool rmg::Object::isGuarded() const
+{
+	return guarded;
+}
+
+void rmg::Object::setGuardedIfMonster(const Instance& object)
+{
+	if (object.object().ID == Obj::MONSTER)
+	{
+		guarded = true;
+	}
+}
+
 void Object::Instance::finalize(RmgMap & map)
 {
 	if(!map.isOnMap(getPosition(true)))

+ 4 - 0
lib/rmg/RmgObject.h

@@ -77,6 +77,9 @@ public:
 	
 	const Area & getArea() const;  //lazy cache invalidation
 	const int3 getVisibleTop() const;
+
+	bool isGuarded() const;
+	void setGuardedIfMonster(const Instance & object);
 	
 	void finalize(RmgMap & map);
 	void clear();
@@ -87,6 +90,7 @@ private:
 	mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache;
 	int3 dPosition;
 	ui32 dStrength;
+	bool guarded;
 };
 }
 

+ 28 - 2
lib/rmg/Zone.cpp

@@ -203,8 +203,34 @@ void Zone::fractalize()
 	rmg::Area possibleTiles(dAreaPossible);
 	rmg::Area tilesToIgnore; //will be erased in this iteration
 
-	const float minDistance = 10 * 10; //squared
-	float blockDistance = minDistance * 0.25f;
+	//Squared
+	float minDistance = 10 * 10;
+	float spanFactor = (pos.z ? 0.25 : 0.5f); //Narrower passages in the Underground
+
+	int treasureValue = 0;
+	int treasureDensity = 0;
+	for (auto t : treasureInfo)
+	{
+		treasureValue += ((t.min + t.max) / 2) * t.density / 1000.f; //Thousands
+		treasureDensity += t.density;
+	}
+
+	if (treasureValue > 200)
+	{
+		//Less obstacles - max span is 1 (no obstacles)
+		spanFactor = 1.0f - ((std::max(0, (1000 - treasureValue)) / (1000.f - 200)) * (1 - spanFactor));
+	}
+	else if (treasureValue < 100)
+	{
+		//Dense obstacles
+		spanFactor *= (treasureValue / 100.f);
+		vstd::amax(spanFactor, 0.2f);
+	}
+	if (treasureDensity <= 10)
+	{
+		vstd::amin(spanFactor, 0.25f); //Add extra obstacles to fill up space
+	}
+	float blockDistance = minDistance * spanFactor; //More obstacles in the Underground
 	
 	if(type != ETemplateZoneType::JUNCTION)
 	{

+ 58 - 3
lib/rmg/modificators/ObjectManager.cpp

@@ -166,7 +166,6 @@ int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object
 		}
 	}
 	
-	//FIXME: Race condition for tiles? For Area?
 	if(result.valid())
 		obj.setPosition(result);
 	return result;
@@ -183,8 +182,15 @@ int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object
 
 		for(const auto & t : obj.getArea().getTilesVector())
 		{
-			if(map.getTileInfo(t).getNearestObjectDistance() < min_dist)
+			auto localDist = map.getTileInfo(t).getNearestObjectDistance();
+			if (localDist < min_dist)
+			{
 				return -1.f;
+			}
+			else
+			{
+				vstd::amin(dist, localDist); //Evaluate object tile which will be closest to another object
+			}
 		}
 		
 		return dist;
@@ -206,6 +212,55 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 				return -1.f;
 		}
 		
+		rmg::Area perimeter;
+		rmg::Area areaToBlock;
+		if (obj.isGuarded())
+		{
+			auto guardedArea = obj.instances().back()->getAccessibleArea();
+			guardedArea.add(obj.instances().back()->getVisitablePosition());
+			areaToBlock = obj.getAccessibleArea(true);
+			areaToBlock.subtract(guardedArea);
+		
+			if (!areaToBlock.empty())
+			{
+				perimeter = areaToBlock;
+				perimeter.unite(areaToBlock.getBorderOutside());
+				//We could have added border around guard
+				perimeter.subtract(guardedArea);
+			}
+		}
+		else
+		{
+			perimeter = obj.getArea();
+			perimeter.subtract(obj.getAccessibleArea());
+			if (!perimeter.empty())
+			{
+				perimeter.unite(perimeter.getBorderOutside());
+				perimeter.subtract(obj.getAccessibleArea());
+			}
+		}
+		//Check if perimeter of the object intersects with more than one blocked areas
+
+		auto tiles = perimeter.getTiles();
+		vstd::erase_if(tiles, [this](const int3& tile) -> bool
+		{
+			//Out-of-map area also is an obstacle
+			if (!map.isOnMap(tile))
+				return false;
+			return !(map.isBlocked(tile) || map.isUsed(tile));
+		});
+
+		if (!tiles.empty())
+		{
+			rmg::Area border(tiles);
+			border.subtract(areaToBlock);
+			if (!border.connected())
+			{
+				//We don't want to connect two blocked areas to create impassable obstacle
+				return -1.f;
+			}
+		}
+		
 		return dist;
 	}, isGuarded, onlyStraight, optimizer);
 }
@@ -230,7 +285,7 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 			accessibleArea.intersect(guardedArea);
 			accessibleArea.add(obj.instances().back()->getPosition(true));
 		}
-		
+
 		auto path = zone.searchPath(accessibleArea, onlyStraight, [&obj, isGuarded](const int3 & t)
 		{
 			if(isGuarded)

+ 58 - 10
lib/rmg/modificators/ObstaclePlacer.cpp

@@ -12,7 +12,7 @@
 #include "ObstaclePlacer.h"
 #include "ObjectManager.h"
 #include "TreasurePlacer.h"
-#include "RockPlacer.h"
+#include "RockFiller.h"
 #include "WaterRoutes.h"
 #include "WaterProxy.h"
 #include "RoadPlacer.h"
@@ -35,14 +35,56 @@ void ObstaclePlacer::process()
 
 	collectPossibleObstacles(zone.getTerrainType());
 	
-	blockedArea = zone.area().getSubarea([this](const int3 & t)
 	{
-		return map.shouldBeBlocked(t);
-	});
-	blockedArea.subtract(zone.areaUsed());
-	zone.areaPossible().subtract(blockedArea);
+		Zone::Lock lock(zone.areaMutex);
+		blockedArea = zone.area().getSubarea([this](const int3& t)
+			{
+				return map.shouldBeBlocked(t);
+			});
+		blockedArea.subtract(zone.areaUsed());
+		zone.areaPossible().subtract(blockedArea);
 
-	prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea();
+		prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea();
+
+		//Progressively block tiles, but make sure they don't seal any gap between blocks
+		rmg::Area toBlock;
+		do
+		{
+			toBlock.clear();
+			for (const auto& tile : zone.areaPossible().getTiles())
+			{
+				rmg::Area neighbors;
+				rmg::Area t;
+				t.add(tile);
+
+				for (const auto& n : t.getBorderOutside())
+				{
+					//Area outside the map is also impassable
+					if (!map.isOnMap(n) || map.shouldBeBlocked(n))
+					{
+						neighbors.add(n);
+					}
+				}
+				if (neighbors.empty())
+				{
+					continue;
+				}
+				//Will only be added if it doesn't connect two disjointed blocks
+				if (neighbors.connected(true)) //Do not block diagonal pass
+				{
+					toBlock.add(tile);
+				}
+			}
+			zone.areaPossible().subtract(toBlock);
+			for (const auto& tile : toBlock.getTiles())
+			{
+				map.setOccupied(tile, ETileType::BLOCKED);
+			}
+
+		} while (!toBlock.empty());
+
+		prohibitedArea.unite(zone.areaPossible());
+	}
 
 	auto objs = createObstacles(zone.getRand());
 	mapProxy->insertObjects(objs);
@@ -52,10 +94,16 @@ void ObstaclePlacer::init()
 {
 	DEPENDENCY(ObjectManager);
 	DEPENDENCY(TreasurePlacer);
-	DEPENDENCY(WaterRoutes);
-	DEPENDENCY(WaterProxy);
 	DEPENDENCY(RoadPlacer);
-	DEPENDENCY_ALL(RockPlacer);
+	if (zone.isUnderground())
+	{
+		DEPENDENCY(RockFiller);
+	}
+	else
+	{
+		DEPENDENCY(WaterRoutes);
+		DEPENDENCY(WaterProxy);
+	}
 }
 
 bool ObstaclePlacer::isInTheMap(const int3& tile)

+ 4 - 1
lib/rmg/modificators/RiverPlacer.cpp

@@ -82,7 +82,10 @@ void RiverPlacer::process()
 
 void RiverPlacer::init()
 {
-	DEPENDENCY_ALL(WaterProxy);
+	if (!zone.isUnderground())
+	{
+		DEPENDENCY_ALL(WaterProxy);
+	}
 	DEPENDENCY(ObjectManager);
 	DEPENDENCY(ObstaclePlacer);
 }

+ 9 - 0
lib/rmg/modificators/RoadPlacer.cpp

@@ -12,6 +12,7 @@
 #include "RoadPlacer.h"
 #include "ObjectManager.h"
 #include "ObstaclePlacer.h"
+#include "RockFiller.h"
 #include "../Functions.h"
 #include "../CMapGenerator.h"
 #include "../threadpool/MapProxy.h"
@@ -28,6 +29,14 @@ void RoadPlacer::process()
 	connectRoads();
 }
 
+void RoadPlacer::init()
+{
+	if (zone.isUnderground())
+	{
+		DEPENDENCY_ALL(RockFiller);
+	}
+}
+
 rmg::Area & RoadPlacer::areaForRoads()
 {
 	return areaRoads;

+ 1 - 0
lib/rmg/modificators/RoadPlacer.h

@@ -19,6 +19,7 @@ public:
 	MODIFICATOR(RoadPlacer);
 	
 	void process() override;
+	void init() override;
 	char dump(const int3 &) override;
 	
 	void addRoadNode(const int3 & node);

+ 0 - 2
lib/rmg/modificators/RockFiller.cpp

@@ -13,7 +13,6 @@
 #include "RockPlacer.h"
 #include "TreasurePlacer.h"
 #include "ObjectManager.h"
-#include "RoadPlacer.h"
 #include "RiverPlacer.h"
 #include "../RmgMap.h"
 #include "../CMapGenerator.h"
@@ -63,7 +62,6 @@ void RockFiller::processMap()
 void RockFiller::init()
 {
 	DEPENDENCY_ALL(RockPlacer);
-	POSTFUNCTION_ALL(RoadPlacer);
 }
 
 char RockFiller::dump(const int3 & t)

+ 11 - 1
lib/rmg/modificators/RockPlacer.cpp

@@ -67,7 +67,17 @@ void RockPlacer::postProcess()
 
 void RockPlacer::init()
 {
-	DEPENDENCY_ALL(TreasurePlacer);
+	for (const auto& zone : map.getZones())
+	{
+		if (zone.second->isUnderground())
+		{
+			auto * tp = zone.second->getModificator<TreasurePlacer>();
+			if (tp)
+			{
+				dependency(tp);
+			}
+		}
+	}
 }
 
 char RockPlacer::dump(const int3 & t)

+ 7 - 4
lib/rmg/modificators/TownPlacer.cpp

@@ -163,11 +163,14 @@ void TownPlacer::cleanupBoundaries(const rmg::Object & rmgObject)
 	Zone::Lock lock(zone.areaMutex);
 	for(const auto & t : rmgObject.getArea().getBorderOutside())
 	{
-		if(map.isOnMap(t))
+		if (t.y > rmgObject.getVisitablePosition().y) //Line below the town
 		{
-			map.setOccupied(t, ETileType::FREE);
-			zone.areaPossible().erase(t);
-			zone.freePaths().add(t);
+			if (map.isOnMap(t))
+			{
+				map.setOccupied(t, ETileType::FREE);
+				zone.areaPossible().erase(t);
+				zone.freePaths().add(t);
+			}
 		}
 	}
 }

+ 120 - 88
lib/rmg/modificators/TreasurePlacer.cpp

@@ -562,7 +562,7 @@ std::vector<ObjectInfo*> TreasurePlacer::prepareTreasurePile(const CTreasureInfo
 	bool hasLargeObject = false;
 	while(currentValue <= static_cast<int>(desiredValue) - 100) //no objects with value below 100 are available
 	{
-		auto * oi = getRandomObject(desiredValue, currentValue, maxValue, !hasLargeObject);
+		auto * oi = getRandomObject(desiredValue, currentValue, !hasLargeObject);
 		if(!oi) //fail
 			break;
 		
@@ -659,13 +659,13 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 	return rmgObject;
 }
 
-ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects)
+ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValue, 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 maxVal = desiredValue - currentValue;
 	ui32 minValue = static_cast<ui32>(0.25f * (desiredValue - currentValue));
 	
 	for(ObjectInfo & oi : possibleObjects) //copy constructor turned out to be costly
@@ -701,133 +701,165 @@ ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValu
 	}
 }
 
-void TreasurePlacer::createTreasures(ObjectManager & manager)
+void TreasurePlacer::createTreasures(ObjectManager& manager)
 {
 	const int maxAttempts = 2;
-	
+
 	int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength();
-	int monsterStrength = (zone.monsterStrength == EMonsterStrength::ZONE_NONE ? 0 : zone.monsterStrength  + mapMonsterStrength - 1); //array index from 0 to 4; pick any correct value for ZONE_NONE, minGuardedValue won't be used in this case anyway
+	int monsterStrength = (zone.monsterStrength == EMonsterStrength::ZONE_NONE ? 0 : zone.monsterStrength + mapMonsterStrength - 1); //array index from 0 to 4; pick any correct value for ZONE_NONE, minGuardedValue won't be used in this case anyway
 	static int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 };
 	minGuardedValue = minGuardedValues[monsterStrength];
-	
-	auto valueComparator = [](const CTreasureInfo & lhs, const CTreasureInfo & rhs) -> bool
+
+	auto valueComparator = [](const CTreasureInfo& lhs, const CTreasureInfo& rhs) -> bool
 	{
 		return lhs.max > rhs.max;
 	};
-	
-	auto restoreZoneLimits = [](const std::vector<ObjectInfo*> & treasurePile)
+
+	auto restoreZoneLimits = [](const std::vector<ObjectInfo*>& treasurePile)
 	{
-		for(auto * oi : 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;
 	});
-	
+
+	size_t size = 0;
+	{
+		Zone::Lock lock(zone.areaMutex);
+		size = zone.getArea().getTiles().size();
+	}
+
 	int totalDensity = 0;
-	for (auto t : treasureInfo)
+
+	for (auto t  = treasureInfo.begin(); t != treasureInfo.end(); t++)
 	{
+		std::vector<rmg::Object> treasures;
+
 		//discard objects with too high value to be ever placed
 		vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool
 		{
-			return oi.value > t.max;
+			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
+
+		totalDensity += t->density;
+
+		size_t count = size * t->density / 500;
+
+		//Assure space for lesser treasures, if there are any left
+		if (t != (treasureInfo.end() - 1))
+		{
+			const int averageValue = (t->min + t->max) / 2;
+			if (averageValue > 10000)
+			{
+				//Will surely be guarded => larger piles => less space inbetween
+				vstd::amin(count, size * (10.f / 500) / (std::sqrt((float)averageValue / 10000)));
+			}
+		}
 		
 		//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
-		
-		for(int attempt = 0; attempt <= maxAttempts;)
+		const float minDistance = std::max<float>((125.f / totalDensity), 1.0f);
+
+		size_t emergencyLoopFinish = 0;
+		while(treasures.size() < count && emergencyLoopFinish < count)
 		{
-			auto treasurePileInfos = prepareTreasurePile(t);
-			if(treasurePileInfos.empty())
+			auto treasurePileInfos = prepareTreasurePile(*t);
+			if (treasurePileInfos.empty())
 			{
-				++attempt;
+				emergencyLoopFinish++; //Exit potentially infinite loop for bad settings
 				continue;
 			}
-			
-			int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo * oi){return v + oi->value;});
-			
-			auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts);
-			if(rmgObject.instances().empty()) //handle incorrect placement
+
+			int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; });
+
+			for (ui32 attempt = 0; attempt <= 2; attempt++)
 			{
-				restoreZoneLimits(treasurePileInfos);
-				continue;
+				auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts);
+
+				if (rmgObject.instances().empty()) //handle incorrect placement
+				{
+					restoreZoneLimits(treasurePileInfos);
+					continue;
+				}
+
+				//guard treasure pile
+				bool guarded = isGuardNeededForTreasure(value);
+				if (guarded)
+					guarded = manager.addGuard(rmgObject, value);
+
+				treasures.push_back(rmgObject);
+				break;
 			}
-			
-			//guard treasure pile
-			bool guarded = isGuardNeededForTreasure(value);
-			if(guarded)
-				guarded = manager.addGuard(rmgObject, value);
-			
-			Zone::Lock lock(zone.areaMutex); //We are going to subtract this area
-			//TODO: Don't place 
-			auto possibleArea = zone.areaPossible();
-			
-			auto path = rmg::Path::invalid();
-			if(guarded)
+		}
+
+		for (auto& rmgObject : treasures)
+		{
+			const bool guarded = rmgObject.isGuarded();
+
+			for (int attempt = 0; attempt <= maxAttempts;)
 			{
-				path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3 & tile)
+				auto path = rmg::Path::invalid();
+
+				Zone::Lock lock(zone.areaMutex); //We are going to subtract this area
+				auto possibleArea = zone.areaPossible();
+
+				if (guarded)
 				{
-					auto ti = map.getTileInfo(tile);
-					if(ti.getNearestObjectDistance() < minDistance)
-						return -1.f;
+					path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile)
+						{
+							auto ti = map.getTileInfo(tile);
+							if (ti.getNearestObjectDistance() < minDistance)
+								return -1.f;
+
+							for (const auto& t : rmgObject.getArea().getTilesVector())
+							{
+								if (map.getTileInfo(t).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 ti.getNearestObjectDistance();
+						}, guarded, false, ObjectManager::OptimizeType::DISTANCE);
+				}
+				else
+				{
+					path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
+				}
 
-					for(const auto & t : rmgObject.getArea().getTilesVector())
+				if (path.valid())
+				{
+					//debug purposes
+					treasureArea.unite(rmgObject.getArea());
+					if (guarded)
 					{
-						if(map.getTileInfo(t).getNearestObjectDistance() < minDistance)
-							return -1.f;
+						guards.unite(rmgObject.instances().back()->getBlockedArea());
+						auto guardedArea = rmgObject.instances().back()->getAccessibleArea();
+						auto areaToBlock = rmgObject.getAccessibleArea(true);
+						areaToBlock.subtract(guardedArea);
+						treasureBlockArea.unite(areaToBlock);
 					}
-					
-					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 ti.getNearestObjectDistance();
-				}, guarded, false, ObjectManager::OptimizeType::DISTANCE);
-			}
-			else
-			{
-				path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
-			}
-			
-			if(path.valid())
-			{
-				//debug purposes
-				treasureArea.unite(rmgObject.getArea());
-				if(guarded)
+					zone.connectPath(path);
+					manager.placeObject(rmgObject, guarded, true);
+					break;
+				}
+				else
 				{
-					guards.unite(rmgObject.instances().back()->getBlockedArea());
-					auto guardedArea = rmgObject.instances().back()->getAccessibleArea();
-					auto areaToBlock = rmgObject.getAccessibleArea(true);
-					areaToBlock.subtract(guardedArea);
-					treasureBlockArea.unite(areaToBlock);
+					++attempt;
 				}
-				zone.connectPath(path);
-				manager.placeObject(rmgObject, guarded, true);
-				attempt = 0;
-			}
-			else
-			{
-				restoreZoneLimits(treasurePileInfos);
-				rmgObject.clear();
-				++attempt;
 			}
 		}
 	}

+ 1 - 1
lib/rmg/modificators/TreasurePlacer.h

@@ -54,7 +54,7 @@ public:
 protected:
 	bool isGuardNeededForTreasure(int value);
 	
-	ObjectInfo * getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects);
+	ObjectInfo * getRandomObject(ui32 desiredValue, ui32 currentValue, bool allowLargeObjects);
 	std::vector<ObjectInfo*> prepareTreasurePile(const CTreasureInfo & treasureInfo);
 	rmg::Object constructTreasurePile(const std::vector<ObjectInfo*> & treasureInfos, bool densePlacement = false);
 

+ 5 - 2
lib/rmg/modificators/WaterProxy.cpp

@@ -83,8 +83,11 @@ void WaterProxy::init()
 {
 	for(auto & z : map.getZones())
 	{
-		dependency(z.second->getModificator<TownPlacer>());
-		dependency(z.second->getModificator<WaterAdopter>());
+		if (!zone.isUnderground())
+		{
+			dependency(z.second->getModificator<TownPlacer>());
+			dependency(z.second->getModificator<WaterAdopter>());
+		}
 		postfunction(z.second->getModificator<ConnectionsPlacer>());
 		postfunction(z.second->getModificator<ObjectManager>());
 	}