瀏覽代碼

Merge pull request #3308 from vcmi/improve_treasure_placement

Improve treasure placement
Ivan Savenko 1 年之前
父節點
當前提交
48a8826aa1

+ 16 - 8
lib/int3.h

@@ -180,6 +180,19 @@ public:
 		return { { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0),
 			int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) } };
 	}
+
+	// Solution by ChatGPT
+
+	// Assume values up to +- 1000
+    friend std::size_t hash_value(const int3& v) {
+        // Since the range is [-1000, 1000], offsetting by 1000 maps it to [0, 2000]
+        std::size_t hx = v.x + 1000;
+        std::size_t hy = v.y + 1000;
+        std::size_t hz = v.z + 1000;
+
+        // Combine the hash values, multiplying them by prime numbers
+        return ((hx * 4000037u) ^ (hy * 2003u)) + hz;
+    }
 };
 
 template<typename Container>
@@ -204,14 +217,9 @@ int3 findClosestTile (Container & container, int3 dest)
 
 VCMI_LIB_NAMESPACE_END
 
-
 template<>
 struct std::hash<VCMI_LIB_WRAP_NAMESPACE(int3)> {
-	size_t operator()(VCMI_LIB_WRAP_NAMESPACE(int3) const& pos) const
-	{
-		size_t ret = std::hash<int>()(pos.x);
-		VCMI_LIB_WRAP_NAMESPACE(vstd)::hash_combine(ret, pos.y);
-		VCMI_LIB_WRAP_NAMESPACE(vstd)::hash_combine(ret, pos.z);
-		return ret;
+	std::size_t operator()(VCMI_LIB_WRAP_NAMESPACE(int3) const& pos) const noexcept {
+		return hash_value(pos);
 	}
-};
+};

+ 20 - 0
lib/mapObjectConstructors/AObjectTypeHandler.cpp

@@ -223,6 +223,26 @@ std::vector<std::shared_ptr<const ObjectTemplate>>AObjectTypeHandler::getTemplat
 		return filtered;
 }
 
+std::vector<std::shared_ptr<const ObjectTemplate>>AObjectTypeHandler::getMostSpecificTemplates(TerrainId terrainType) const
+{
+	auto templates = getTemplates(terrainType);
+	if (!templates.empty())
+	{
+		//Get terrain-specific template if possible
+		int leastTerrains = (*boost::min_element(templates, [](const std::shared_ptr<const ObjectTemplate> & tmp1, const std::shared_ptr<const ObjectTemplate> & tmp2)
+		{
+			return tmp1->getAllowedTerrains().size() < tmp2->getAllowedTerrains().size();
+		}))->getAllowedTerrains().size();
+
+		vstd::erase_if(templates, [leastTerrains](const std::shared_ptr<const ObjectTemplate> & tmp)
+		{
+			return tmp->getAllowedTerrains().size() > leastTerrains;
+		});
+	}
+
+	return templates;
+}
+
 std::shared_ptr<const ObjectTemplate> AObjectTypeHandler::getOverride(TerrainId terrainType, const CGObjectInstance * object) const
 {
 	std::vector<std::shared_ptr<const ObjectTemplate>> ret = getTemplates(terrainType);

+ 1 - 0
lib/mapObjectConstructors/AObjectTypeHandler.h

@@ -79,6 +79,7 @@ public:
 	/// returns all templates matching parameters
 	std::vector<std::shared_ptr<const ObjectTemplate>> getTemplates() const;
 	std::vector<std::shared_ptr<const ObjectTemplate>> getTemplates(const TerrainId terrainType) const;
+	std::vector<std::shared_ptr<const ObjectTemplate>> getMostSpecificTemplates(TerrainId terrainType) const;
 
 	/// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle)
 	/// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server)

+ 4 - 1
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -314,7 +314,10 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObj
 		if (objects.at(type.getNum()) == nullptr)
 			return objects.front()->objects.front();
 
-		auto result = objects.at(type.getNum())->objects.at(subtype.getNum());
+		auto subID = subtype.getNum();
+		if (type == Obj::PRISON)
+			subID = 0;
+		auto result = objects.at(type.getNum())->objects.at(subID);
 
 		if (result != nullptr)
 			return result;

+ 16 - 5
lib/mapping/ObstacleProxy.cpp

@@ -84,16 +84,27 @@ int ObstacleProxy::getWeightedObjects(const int3 & tile, CRandomGenerator & rand
 			rmg::Object * rmgObject = &allObjects.back();
 			for(const auto & offset : obj->getBlockedOffsets())
 			{
-				rmgObject->setPosition(tile - offset);
+				auto newPos = tile - offset;
 
-				if(!isInTheMap(rmgObject->getPosition()))
+				if(!isInTheMap(newPos))
 					continue;
 
-				if(!rmgObject->getArea().getSubarea([this](const int3 & t)
+				rmgObject->setPosition(newPos);
+
+				bool isInTheMapEntirely = true;
+				for (const auto & t : rmgObject->getArea().getTiles())
+				{
+					if (!isInTheMap(t))
+					{
+						isInTheMapEntirely = false;
+						break;
+					}
+
+				}
+				if (!isInTheMapEntirely)
 				{
-					return !isInTheMap(t);
-				}).empty())
 					continue;
+				}
 
 				if(isProhibited(rmgObject->getArea()))
 					continue;

+ 24 - 18
lib/rmg/RmgArea.cpp

@@ -19,12 +19,12 @@ namespace rmg
 
 void toAbsolute(Tileset & tiles, const int3 & position)
 {
-	Tileset temp;
-	for(const auto & tile : tiles)
+	std::vector vec(tiles.begin(), tiles.end());
+	tiles.clear();
+	std::transform(vec.begin(), vec.end(), vstd::set_inserter(tiles), [position](const int3 & tile)
 	{
-		temp.insert(tile + position);
-	}
-	tiles = std::move(temp);
+		return tile + position;
+	});
 }
 
 void toRelative(Tileset & tiles, const int3 & position)
@@ -161,6 +161,7 @@ const Tileset & Area::getBorder() const
 		return dBorderCache;
 	
 	//compute border cache
+	dBorderCache.reserve(dTiles.bucket_count());
 	for(const auto & t : dTiles)
 	{
 		for(auto & i : int3::getDirs())
@@ -182,6 +183,7 @@ const Tileset & Area::getBorderOutside() const
 		return dBorderOutsideCache;
 	
 	//compute outside border cache
+	dBorderOutsideCache.reserve(dBorderCache.bucket_count() * 2);
 	for(const auto & t : dTiles)
 	{
 		for(auto & i : int3::getDirs())
@@ -238,6 +240,7 @@ bool Area::contains(const Area & area) const
 
 bool Area::overlap(const std::vector<int3> & tiles) const
 {
+	// Important: Make sure that tiles.size < area.size
 	for(const auto & t : tiles)
 	{
 		if(contains(t))
@@ -296,15 +299,15 @@ int3 Area::nearest(const Area & area) const
 Area Area::getSubarea(const std::function<bool(const int3 &)> & filter) const
 {
 	Area subset;
-	for(const auto & t : getTilesVector())
-		if(filter(t))
-			subset.add(t);
+	subset.dTiles.reserve(getTilesVector().size());
+	vstd::copy_if(getTilesVector(), vstd::set_inserter(subset.dTiles), filter);
 	return subset;
 }
 
 void Area::clear()
 {
 	dTiles.clear();
+	dTilesVectorCache.clear();
 	dTotalShiftCache = int3();
 	invalidate();
 }
@@ -329,15 +332,16 @@ void Area::erase(const int3 & tile)
 void Area::unite(const Area & area)
 {
 	invalidate();
-	for(const auto & t : area.getTilesVector())
-	{
-		dTiles.insert(t);
-	}
+	const auto & vec = area.getTilesVector();
+	dTiles.reserve(dTiles.size() + vec.size());
+	dTiles.insert(vec.begin(), vec.end());
 }
+
 void Area::intersect(const Area & area)
 {
 	invalidate();
 	Tileset result;
+	result.reserve(std::max(dTiles.size(), area.getTilesVector().size()));
 	for(const auto & t : area.getTilesVector())
 	{
 		if(dTiles.count(t))
@@ -359,10 +363,9 @@ void Area::translate(const int3 & shift)
 {
 	dBorderCache.clear();
 	dBorderOutsideCache.clear();
-	
+
 	if(dTilesVectorCache.empty())
 	{
-		getTiles();
 		getTilesVector();
 	}
 	
@@ -373,7 +376,6 @@ void Area::translate(const int3 & shift)
 	{
 		t += shift;
 	}
-	//toAbsolute(dTiles, shift);
 }
 
 void Area::erase_if(std::function<bool(const int3&)> predicate)
@@ -398,8 +400,12 @@ Area operator+ (const Area & l, const int3 & r)
 
 Area operator+ (const Area & l, const Area & r)
 {
-	Area result(l);
-	result.unite(r);
+	Area result;
+	const auto & lTiles = l.getTilesVector();
+	const auto & rTiles = r.getTilesVector();
+	result.dTiles.reserve(lTiles.size() + rTiles.size());
+	result.dTiles.insert(lTiles.begin(), lTiles.end());
+	result.dTiles.insert(rTiles.begin(), rTiles.end());
 	return result;
 }
 
@@ -419,7 +425,7 @@ Area operator* (const Area & l, const Area & r)
 
 bool operator== (const Area & l, const Area & r)
 {
-	return l.getTiles() == r.getTiles();
+	return l.getTilesVector() == r.getTilesVector();
 }
 
 }

+ 1 - 1
lib/rmg/RmgArea.h

@@ -20,7 +20,7 @@ namespace rmg
 	static const std::array<int3, 4> dirs4 = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0) };
 	static const std::array<int3, 4> dirsDiagonal= { int3(1,1,0),int3(1,-1,0),int3(-1,1,0),int3(-1,-1,0) };
 
-	using Tileset = std::set<int3>;
+	using Tileset = std::unordered_set<int3>;
 	using DistanceMap = std::map<int3, int>;
 	void toAbsolute(Tileset & tiles, const int3 & position);
 	void toRelative(Tileset & tiles, const int3 & position);

+ 1 - 1
lib/rmg/RmgMap.cpp

@@ -309,7 +309,7 @@ void RmgMap::setZoneID(const int3& tile, TRmgTemplateZoneId zid)
 	zoneColouring[tile.x][tile.y][tile.z] = zid;
 }
 
-void RmgMap::setNearestObjectDistance(int3 &tile, float value)
+void RmgMap::setNearestObjectDistance(const int3 &tile, float value)
 {
 	assertOnMap(tile);
 	

+ 1 - 1
lib/rmg/RmgMap.h

@@ -61,7 +61,7 @@ public:
 	TerrainTile & getTile(const int3 & tile) const;
 		
 	float getNearestObjectDistance(const int3 &tile) const;
-	void setNearestObjectDistance(int3 &tile, float value);
+	void setNearestObjectDistance(const int3 &tile, float value);
 	
 	TRmgTemplateZoneId getZoneID(const int3& tile) const;
 	void setZoneID(const int3& tile, TRmgTemplateZoneId zid);

+ 90 - 55
lib/rmg/RmgObject.cpp

@@ -38,11 +38,10 @@ const Area & Object::Instance::getBlockedArea() const
 {
 	if(dBlockedAreaCache.empty())
 	{
-		dBlockedAreaCache.assign(dObject.getBlockedPos());
+		std::set<int3> blockedArea = dObject.getBlockedPos();
+		dBlockedAreaCache.assign(rmg::Tileset(blockedArea.begin(), blockedArea.end()));
 		if(dObject.isVisitable() || dBlockedAreaCache.empty())
-			if (!dObject.isBlockedVisitable())
-				// Do no assume blocked tile is accessible
-				dBlockedAreaCache.add(dObject.visitablePos());
+			dBlockedAreaCache.add(dObject.visitablePos());
 	}
 	return dBlockedAreaCache;
 }
@@ -70,8 +69,10 @@ const rmg::Area & Object::Instance::getAccessibleArea() const
 	if(dAccessibleAreaCache.empty())
 	{
 		auto neighbours = rmg::Area({getVisitablePosition()}).getBorderOutside();
+		// FIXME: Blocked area of removable object is also accessible area for neighbors
 		rmg::Area visitable = rmg::Area(neighbours) - getBlockedArea();
-		for(const auto & from : visitable.getTiles())
+		// TODO: Add in one operation to avoid multiple invalidation
+		for(const auto & from : visitable.getTilesVector())
 		{
 			if(isVisitableFrom(from))
 				dAccessibleAreaCache.add(from);
@@ -122,22 +123,13 @@ void Object::Instance::setAnyTemplate(CRandomGenerator & rng)
 
 void Object::Instance::setTemplate(TerrainId terrain, CRandomGenerator & rng)
 {
-	auto templates = dObject.getObjectHandler()->getTemplates(terrain);
+	auto templates = dObject.getObjectHandler()->getMostSpecificTemplates(terrain);
+
 	if (templates.empty())
 	{
 		auto terrainName = VLC->terrainTypeHandler->getById(terrain)->getNameTranslated();
 		throw rmgException(boost::str(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.getObjTypeIndex() % terrainName));
 	}
-	//Get terrain-specific template if possible
-	int leastTerrains = (*boost::min_element(templates, [](const std::shared_ptr<const ObjectTemplate> & tmp1, const std::shared_ptr<const ObjectTemplate> & tmp2)
-	{
-		return tmp1->getAllowedTerrains().size() < tmp2->getAllowedTerrains().size();
-	}))->getAllowedTerrains().size();
-
-	vstd::erase_if(templates, [leastTerrains](const std::shared_ptr<const ObjectTemplate> & tmp)
-	{
-		return tmp->getAllowedTerrains().size() > leastTerrains;
-	});
 	
 	dObject.appearance = *RandomGeneratorUtil::nextItem(templates, rng);
 	dAccessibleAreaCache.clear();
@@ -191,7 +183,6 @@ Object::Object(CGObjectInstance & object):
 }
 
 Object::Object(const Object & object):
-	dStrength(object.dStrength),
 	guarded(false)
 {
 	for(const auto & i : object.dInstances)
@@ -199,20 +190,24 @@ Object::Object(const Object & object):
 	setPosition(object.getPosition());
 }
 
-std::list<Object::Instance*> Object::instances()
+std::list<Object::Instance*> & Object::instances()
 {
-	std::list<Object::Instance*> result;
-	for(auto & i : dInstances)
-		result.push_back(&i);
-	return result;
+	if (cachedInstanceList.empty())
+	{
+		for(auto & i : dInstances)
+			cachedInstanceList.push_back(&i);
+	}
+	return cachedInstanceList;
 }
 
-std::list<const Object::Instance*> Object::instances() const
+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;
+	if (cachedInstanceConstList.empty())
+	{
+		for(const auto & i : dInstances)
+			cachedInstanceConstList.push_back(&i);
+	}
+	return cachedInstanceConstList;
 }
 
 void Object::addInstance(Instance & object)
@@ -220,16 +215,22 @@ void Object::addInstance(Instance & object)
 	//assert(object.dParent == *this);
 	setGuardedIfMonster(object);
 	dInstances.push_back(object);
+	cachedInstanceList.push_back(&object);
+	cachedInstanceConstList.push_back(&object);
 
 	clearCachedArea();
+	visibleTopOffset.reset();
 }
 
 Object::Instance & Object::addInstance(CGObjectInstance & object)
 {
 	dInstances.emplace_back(*this, object);
 	setGuardedIfMonster(dInstances.back());
+	cachedInstanceList.push_back(&dInstances.back());
+	cachedInstanceConstList.push_back(&dInstances.back());
 
 	clearCachedArea();
+	visibleTopOffset.reset();
 	return dInstances.back();
 }
 
@@ -237,8 +238,11 @@ Object::Instance & Object::addInstance(CGObjectInstance & object, const int3 & p
 {
 	dInstances.emplace_back(*this, object, position);
 	setGuardedIfMonster(dInstances.back());
+	cachedInstanceList.push_back(&dInstances.back());
+	cachedInstanceConstList.push_back(&dInstances.back());
 
 	clearCachedArea();
+	visibleTopOffset.reset();
 	return dInstances.back();
 }
 
@@ -265,15 +269,16 @@ const rmg::Area & Object::getAccessibleArea(bool exceptLast) const
 		return dAccessibleAreaCache;
 	if(!exceptLast && !dAccessibleAreaFullCache.empty())
 		return dAccessibleAreaFullCache;
-	
+
+	// FIXME: This clears tiles for every consecutive object
 	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
@@ -282,33 +287,45 @@ const rmg::Area & Object::getAccessibleArea(bool exceptLast) const
 
 const rmg::Area & Object::getBlockVisitableArea() const
 {
-	if(dInstances.empty())
-		return dBlockVisitableCache;
-
-	for(const auto & i : dInstances)
+	if(dBlockVisitableCache.empty())
 	{
-		// FIXME: Account for blockvis objects with multiple visitable tiles
-		if (i.isBlockedVisitable())
-			dBlockVisitableCache.add(i.getVisitablePosition());
+		for(const auto & i : dInstances)
+		{
+			// FIXME: Account for blockvis objects with multiple visitable tiles
+			if (i.isBlockedVisitable())
+				dBlockVisitableCache.add(i.getVisitablePosition());
+		}
 	}
-
 	return dBlockVisitableCache;
 }
 
 const rmg::Area & Object::getRemovableArea() const
 {
-	if(dInstances.empty())
-		return dRemovableAreaCache;
-
-	for(const auto & i : dInstances)
+	if(dRemovableAreaCache.empty())
 	{
-		if (i.isRemovable())
-			dRemovableAreaCache.unite(i.getBlockedArea());
+		for(const auto & i : dInstances)
+		{
+			if (i.isRemovable())
+				dRemovableAreaCache.unite(i.getBlockedArea());
+		}
 	}
 
 	return dRemovableAreaCache;
 }
 
+const rmg::Area & Object::getVisitableArea() const
+{
+	if(dVisitableCache.empty())
+	{
+		for(const auto & i : dInstances)
+		{
+			// FIXME: Account for bjects with multiple visitable tiles
+			dVisitableCache.add(i.getVisitablePosition());
+		}
+	}
+	return dVisitableCache;
+}
+
 const rmg::Area Object::getEntrableArea() const
 {
 	// Calculate Area that hero can freely pass
@@ -316,7 +333,8 @@ const rmg::Area Object::getEntrableArea() const
 	// Do not use blockVisitTiles, unless they belong to removable objects (resources etc.)
 	// area = accessibleArea - (blockVisitableArea - removableArea)
 
-	rmg::Area entrableArea = getAccessibleArea();
+	// FIXME: What does it do? AccessibleArea means area AROUND the object 
+	rmg::Area entrableArea = getVisitableArea();
 	rmg::Area blockVisitableArea = getBlockVisitableArea();
 	blockVisitableArea.subtract(getRemovableArea());
 	entrableArea.subtract(blockVisitableArea);
@@ -326,11 +344,14 @@ const rmg::Area Object::getEntrableArea() const
 
 void Object::setPosition(const int3 & position)
 {
-	dAccessibleAreaCache.translate(position - dPosition);
-	dAccessibleAreaFullCache.translate(position - dPosition);
-	dBlockVisitableCache.translate(position - dPosition);
-	dRemovableAreaCache.translate(position - dPosition);
-	dFullAreaCache.translate(position - dPosition);
+	auto shift = position - dPosition;
+
+	dAccessibleAreaCache.translate(shift);
+	dAccessibleAreaFullCache.translate(shift);
+	dBlockVisitableCache.translate(shift);
+	dVisitableCache.translate(shift);
+	dRemovableAreaCache.translate(shift);
+	dFullAreaCache.translate(shift);
 	
 	dPosition = position;
 	for(auto& i : dInstances)
@@ -341,6 +362,8 @@ void Object::setTemplate(const TerrainId & terrain, CRandomGenerator & rng)
 {
 	for(auto& i : dInstances)
 		i.setTemplate(terrain, rng);
+
+	visibleTopOffset.reset();
 }
 
 const Area & Object::getArea() const
@@ -358,15 +381,23 @@ const Area & Object::getArea() const
 
 const int3 Object::getVisibleTop() const
 {
-	int3 topTile(-1, 10000, -1); //Start at the bottom
-	for (const auto& i : dInstances)
+	if (visibleTopOffset)
+	{
+		return dPosition + visibleTopOffset.value();
+	}
+	else
 	{
-		if (i.getTopTile().y < topTile.y)
+		int3 topTile(-1, 10000, -1); //Start at the bottom
+		for (const auto& i : dInstances)
 		{
-			topTile = i.getTopTile();
+			if (i.getTopTile().y < topTile.y)
+			{
+				topTile = i.getTopTile();
+			}
 		}
+		visibleTopOffset = topTile - dPosition;
+		return topTile;
 	}
-	return topTile;
 }
 
 bool rmg::Object::isGuarded() const
@@ -436,6 +467,7 @@ void Object::clearCachedArea() const
 	dAccessibleAreaCache.clear();
 	dAccessibleAreaFullCache.clear();
 	dBlockVisitableCache.clear();
+	dVisitableCache.clear();
 	dRemovableAreaCache.clear();
 }
 
@@ -444,6 +476,9 @@ void Object::clear()
 	for(auto & instance : dInstances)
 		instance.clear();
 	dInstances.clear();
+	cachedInstanceList.clear();
+	cachedInstanceConstList.clear();
+	visibleTopOffset.reset();
 
 	clearCachedArea();
 }

+ 7 - 3
lib/rmg/RmgObject.h

@@ -68,12 +68,13 @@ public:
 	Instance & addInstance(CGObjectInstance & object);
 	Instance & addInstance(CGObjectInstance & object, const int3 & position);
 	
-	std::list<Instance*> instances();
-	std::list<const Instance*> instances() const;
+	std::list<Instance*> & instances();
+	std::list<const Instance*> & instances() const;
 	
 	int3 getVisitablePosition() const;
 	const Area & getAccessibleArea(bool exceptLast = false) const;
 	const Area & getBlockVisitableArea() const;
+	const Area & getVisitableArea() const;
 	const Area & getRemovableArea() const;
 	const Area getEntrableArea() const;
 	
@@ -96,9 +97,12 @@ private:
 	mutable Area dFullAreaCache;
 	mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache;
 	mutable Area dBlockVisitableCache;
+	mutable Area dVisitableCache;
 	mutable Area dRemovableAreaCache;
 	int3 dPosition;
-	ui32 dStrength;
+	mutable std::optional<int3> visibleTopOffset;
+	mutable std::list<Object::Instance*> cachedInstanceList;
+	mutable std::list<const Object::Instance*> cachedInstanceConstList;
 	bool guarded;
 };
 }

+ 4 - 3
lib/rmg/RmgPath.cpp

@@ -68,11 +68,12 @@ Path Path::search(const Tileset & dst, bool straight, std::function<float(const
 	if(!dArea)
 		return Path::invalid();
 	
+	if(dst.empty()) // Skip construction of same area
+		return Path(*dArea);
+
 	auto resultArea = *dArea + dst;
 	Path result(resultArea);
-	if(dst.empty())
-		return result;
-	
+
 	int3 src = rmg::Area(dst).nearest(dPath);
 	result.connect(src);
 	

+ 68 - 10
lib/rmg/Zone.cpp

@@ -15,6 +15,7 @@
 #include "TileInfo.h"
 #include "CMapGenerator.h"
 #include "RmgPath.h"
+#include "modificators/ObjectManager.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -177,6 +178,38 @@ rmg::Path Zone::searchPath(const rmg::Area & src, bool onlyStraight, const std::
 	return resultPath;
 }
 
+rmg::Path Zone::searchPath(const rmg::Area & src, bool onlyStraight, const rmg::Area & searchArea) const
+///connect current tile to any other free tile within searchArea
+{
+	auto movementCost = [this](const int3 & s, const int3 & d)
+	{
+		if(map.isFree(d))
+			return 1;
+		else if (map.isPossible(d))
+			return 2;
+		return 3;
+	};
+
+	rmg::Path freePath(searchArea);
+	rmg::Path resultPath(searchArea);
+	freePath.connect(dAreaFree);
+
+	//connect to all pieces
+	auto goals = connectedAreas(src, onlyStraight);
+	for(auto & goal : goals)
+	{
+		auto path = freePath.search(goal, onlyStraight, movementCost);
+		if(path.getPathArea().empty())
+			return rmg::Path::invalid();
+
+		freePath.connect(path.getPathArea());
+		resultPath.connect(path.getPathArea());
+	}
+
+	return resultPath;
+}
+
+
 rmg::Path Zone::searchPath(const int3 & src, bool onlyStraight, const std::function<bool(const int3 &)> & areafilter) const
 ///connect current tile to any other free tile within zone
 {
@@ -204,33 +237,38 @@ void Zone::fractalize()
 	rmg::Area tilesToIgnore; //will be erased in this iteration
 
 	//Squared
-	float minDistance = 10 * 10;
+	float minDistance = 9 * 9;
+	float freeDistance = pos.z ? (10 * 10) : 6 * 6;
 	float spanFactor = (pos.z ? 0.25 : 0.5f); //Narrower passages in the Underground
+	float marginFactor = 1.0f;
 
 	int treasureValue = 0;
 	int treasureDensity = 0;
-	for (auto t : treasureInfo)
+	for (const auto & t : treasureInfo)
 	{
 		treasureValue += ((t.min + t.max) / 2) * t.density / 1000.f; //Thousands
 		treasureDensity += t.density;
 	}
 
-	if (treasureValue > 200)
+	if (treasureValue > 400)
 	{
-		//Less obstacles - max span is 1 (no obstacles)
-		spanFactor = 1.0f - ((std::max(0, (1000 - treasureValue)) / (1000.f - 200)) * (1 - spanFactor));
+		// A quater at max density
+		marginFactor = (0.25f + ((std::max(0, (600 - treasureValue))) / (600.f - 400)) * 0.75f);
 	}
-	else if (treasureValue < 100)
+	else if (treasureValue < 125)
 	{
 		//Dense obstacles
-		spanFactor *= (treasureValue / 100.f);
-		vstd::amax(spanFactor, 0.2f);
+		spanFactor *= (treasureValue / 125.f);
+		vstd::amax(spanFactor, 0.15f);
 	}
 	if (treasureDensity <= 10)
 	{
-		vstd::amin(spanFactor, 0.25f); //Add extra obstacles to fill up space
+		vstd::amin(spanFactor, 0.1f + 0.01f * treasureDensity); //Add extra obstacles to fill up space
 	}
 	float blockDistance = minDistance * spanFactor; //More obstacles in the Underground
+	freeDistance = freeDistance * marginFactor;
+	vstd::amax(freeDistance, 4 * 4);
+	logGlobal->info("Zone %d: treasureValue %d blockDistance: %2.f, freeDistance: %2.f", getId(), treasureValue, blockDistance, freeDistance);
 	
 	if(type != ETemplateZoneType::JUNCTION)
 	{
@@ -240,6 +278,16 @@ void Zone::fractalize()
 		{
 			//link tiles in random order
 			std::vector<int3> tilesToMakePath = possibleTiles.getTilesVector();
+
+			// Do not fractalize tiles near the edge of the map to avoid paths adjacent to map edge
+			const auto h = map.height();
+			const auto w = map.width();
+			const size_t MARGIN = 3;
+			vstd::erase_if(tilesToMakePath, [&, h, w](const int3 & tile)
+			{
+				return tile.x < MARGIN || tile.x > (w - MARGIN) ||
+					tile.y < MARGIN || tile.y > (h - MARGIN);
+			});
 			RandomGeneratorUtil::randomShuffle(tilesToMakePath, getRand());
 			
 			int3 nodeFound(-1, -1, -1);
@@ -248,7 +296,7 @@ void Zone::fractalize()
 			{
 				//find closest free tile
 				int3 closestTile = clearedTiles.nearest(tileToMakePath);
-				if(closestTile.dist2dSQ(tileToMakePath) <= minDistance)
+				if(closestTile.dist2dSQ(tileToMakePath) <= freeDistance)
 					tilesToIgnore.add(tileToMakePath);
 				else
 				{
@@ -265,6 +313,16 @@ void Zone::fractalize()
 			tilesToIgnore.clear();
 		}
 	}
+	else
+	{
+		// Handle special case - place Monoliths at the edge of a zone
+		auto objectManager = getModificator<ObjectManager>();
+		if (objectManager)
+		{
+			objectManager->createMonoliths();
+		}
+	}
+
 	Lock lock(areaMutex);
 	//cut straight paths towards the center. A* is too slow for that.
 	auto areas = connectedAreas(clearedTiles, false);

+ 1 - 0
lib/rmg/Zone.h

@@ -66,6 +66,7 @@ public:
 	void connectPath(const rmg::Path & path);
 	rmg::Path searchPath(const rmg::Area & src, bool onlyStraight, const std::function<bool(const int3 &)> & areafilter = AREA_NO_FILTER) const;
 	rmg::Path searchPath(const int3 & src, bool onlyStraight, const std::function<bool(const int3 &)> & areafilter = AREA_NO_FILTER) const;
+	rmg::Path searchPath(const rmg::Area & src, bool onlyStraight, const rmg::Area & searchArea) const;
 
 	TModificators getModificators();
 

+ 2 - 1
lib/rmg/modificators/ConnectionsPlacer.cpp

@@ -302,6 +302,8 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
 	if(zone.isUnderground() != otherZone->isUnderground())
 	{
 		int3 zShift(0, 0, zone.getPos().z - otherZone->getPos().z);
+
+		std::scoped_lock doubleLock(zone.areaMutex, otherZone->areaMutex);
 		auto commonArea = zone.areaPossible() * (otherZone->areaPossible() + zShift);
 		if(!commonArea.empty())
 		{
@@ -322,7 +324,6 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
 			bool guarded2 = managerOther.addGuard(rmgGate2, connection.getGuardStrength(), true);
 			int minDist = 3;
 			
-			std::scoped_lock doubleLock(zone.areaMutex, otherZone->areaMutex);
 			rmg::Path path2(otherZone->area());
 			rmg::Path path1 = manager.placeAndConnectObject(commonArea, rmgGate1, [this, minDist, &path2, &rmgGate1, &zShift, guarded2, &managerOther, &rmgGate2	](const int3 & tile)
 			{

+ 91 - 31
lib/rmg/modificators/ObjectManager.cpp

@@ -95,7 +95,7 @@ void ObjectManager::updateDistances(std::function<ui32(const int3 & tile)> dista
 {
 	RecursiveLock lock(externalAccessMutex);
 	tilesByDistance.clear();
-	for (auto tile : zone.areaPossible().getTiles()) //don't need to mark distance for not possible tiles
+	for (const auto & tile : zone.areaPossible().getTilesVector()) //don't need to mark distance for not possible tiles
 	{
 		ui32 d = distanceFunction(tile);
 		map.setNearestObjectDistance(tile, std::min(static_cast<float>(d), map.getNearestObjectDistance(tile)));
@@ -178,7 +178,7 @@ int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object
 	}
 	else
 	{
-		for(const auto & tile : searchArea.getTiles())
+		for(const auto & tile : searchArea.getTilesVector())
 		{
 			obj.setPosition(tile);
 
@@ -238,15 +238,14 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 	RecursiveLock lock(externalAccessMutex);
 	return placeAndConnectObject(searchArea, obj, [this, min_dist, &obj](const int3 & tile)
 	{
-		auto ti = map.getTileInfo(tile);
-		float dist = ti.getNearestObjectDistance();
-		if(dist < min_dist)
-			return -1.f;
-
+		float bestDistance = 10e9;
 		for(const auto & t : obj.getArea().getTilesVector())
 		{
-			if(map.getTileInfo(t).getNearestObjectDistance() < min_dist)
+			float distance = map.getTileInfo(t).getNearestObjectDistance();
+			if(distance < min_dist)
 				return -1.f;
+			else
+				vstd::amin(bestDistance, distance);
 		}
 		
 		rmg::Area perimeter;
@@ -298,7 +297,7 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 			}
 		}
 		
-		return dist;
+		return bestDistance;
 	}, isGuarded, onlyStraight, optimizer);
 }
 
@@ -306,6 +305,7 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 {
 	int3 pos;
 	auto possibleArea = searchArea;
+	auto cachedArea = zone.areaPossible() + zone.freePaths();
 	while(true)
 	{
 		pos = findPlaceForObject(possibleArea, obj, weightFunction, optimizer);
@@ -314,7 +314,7 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 			return rmg::Path::invalid();
 		}
 		possibleArea.erase(pos); //do not place again at this point
-		auto accessibleArea = obj.getAccessibleArea(isGuarded) * (zone.areaPossible() + zone.freePaths());
+		auto accessibleArea = obj.getAccessibleArea(isGuarded) * cachedArea;
 		//we should exclude tiles which will be covered
 		if(isGuarded)
 		{
@@ -323,21 +323,31 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 			accessibleArea.add(obj.instances().back()->getPosition(true));
 		}
 
-		auto path = zone.searchPath(accessibleArea, onlyStraight, [&obj, isGuarded](const int3 & t)
+		rmg::Area subArea;
+		if (isGuarded)
 		{
-			if(isGuarded)
+			const auto & guardedArea = obj.instances().back()->getAccessibleArea();
+			const auto & unguardedArea = obj.getAccessibleArea(isGuarded);
+			subArea = cachedArea.getSubarea([guardedArea, unguardedArea, obj](const int3 & t)
 			{
-				const auto & guardedArea = obj.instances().back()->getAccessibleArea();
-				const auto & unguardedArea = obj.getAccessibleArea(isGuarded);
 				if(unguardedArea.contains(t) && !guardedArea.contains(t))
 					return false;
 				
 				//guard position is always target
 				if(obj.instances().back()->getPosition(true) == t)
 					return true;
-			}
-			return !obj.getArea().contains(t);
-		});
+
+				return !obj.getArea().contains(t);
+			});
+		}
+		else
+		{
+			subArea = cachedArea.getSubarea([obj](const int3 & t)
+			{
+				return !obj.getArea().contains(t);
+			});
+		}
+		auto path = zone.searchPath(accessibleArea, onlyStraight, subArea);
 		
 		if(path.valid())
 		{
@@ -346,6 +356,41 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 	}
 }
 
+bool ObjectManager::createMonoliths()
+{
+	// Special case for Junction zone only
+	logGlobal->trace("Creating Monoliths");
+	for(const auto & objInfo : requiredObjects)
+	{
+		if (objInfo.obj->ID != Obj::MONOLITH_TWO_WAY)
+		{
+			continue;
+		}
+
+		rmg::Object rmgObject(*objInfo.obj);
+		rmgObject.setTemplate(zone.getTerrainType(), zone.getRand());
+		bool guarded = addGuard(rmgObject, objInfo.guardStrength, true);
+
+		Zone::Lock lock(zone.areaMutex);
+		auto path = placeAndConnectObject(zone.areaPossible(), rmgObject, 3, guarded, false, OptimizeType::DISTANCE);
+		
+		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, objInfo.createRoad);
+	}
+
+	vstd::erase_if(requiredObjects, [](const auto & objInfo)
+	{
+		return  objInfo.obj->ID == Obj::MONOLITH_TWO_WAY;
+	});
+	return true;
+}
+
 bool ObjectManager::createRequiredObjects()
 {
 	logGlobal->trace("Creating required objects");
@@ -424,7 +469,8 @@ bool ObjectManager::createRequiredObjects()
 		}
 
 		rmg::Object rmgNearObject(*nearby.obj);
-		rmg::Area possibleArea(rmg::Area(targetObject->getBlockedPos()).getBorderOutside());
+		std::set<int3> blockedArea = targetObject->getBlockedPos();
+		rmg::Area possibleArea(rmg::Area(rmg::Tileset(blockedArea.begin(), blockedArea.end())).getBorderOutside());
 		possibleArea.intersect(zone.areaPossible());
 		if(possibleArea.empty())
 		{
@@ -513,6 +559,7 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
 			if(map.isOnMap(i) && map.isPossible(i))
 				map.setOccupied(i, ETileType::BLOCKED);
 	}
+	lock.unlock();
 	
 	if (updateDistance)
 	{
@@ -535,11 +582,13 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
 			auto manager = map.getZones().at(id)->getModificator<ObjectManager>();
 			if (manager)
 			{
+				// TODO: Update distances for perimeter of guarded object, not just treasures
 				manager->updateDistances(object);
 			}
 		}
 	}
 	
+	// TODO: Add multiple tiles in one operation to avoid multiple invalidation
 	for(auto * instance : object.instances())
 	{
 		objectsVisitableArea.add(instance->getVisitablePosition());
@@ -552,10 +601,23 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
 				continue;
 			}
 			else if(instance->object().appearance->isVisitableFromTop())
+			{
+				//Passable objects
 				m->areaForRoads().add(instance->getVisitablePosition());
-			else
+			}
+			else if(!instance->object().appearance->isVisitableFromTop())
 			{
-				m->areaIsolated().add(instance->getVisitablePosition() + int3(0, -1, 0));
+				// Do not route road behind visitable tile
+				int3 visitablePos = instance->getVisitablePosition();
+				auto areaVisitable = rmg::Area({visitablePos});
+				auto borderAbove = areaVisitable.getBorderOutside();
+				vstd::erase_if(borderAbove, [&](const int3 & tile)
+				{
+					return tile.y >= visitablePos.y ||
+					(!instance->object().blockingAt(tile + int3(0, 1, 0)) && 
+					instance->object().blockingAt(tile));
+				});				
+				m->areaIsolated().unite(borderAbove);
 			}
 		}
 
@@ -669,22 +731,20 @@ bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard
 		return false;
 	
 	// Prefer non-blocking tiles, if any
-	auto entrableTiles = object.getEntrableArea().getTiles();
-	int3 entrableTile(-1, -1, -1);
-	if (entrableTiles.empty())
+	auto entrableArea = object.getEntrableArea();
+	if (entrableArea.empty())
 	{
-		entrableTile = object.getVisitablePosition();
-	}
-	else
-	{
-		entrableTile = *RandomGeneratorUtil::nextItem(entrableTiles, zone.getRand());
+		entrableArea.add(object.getVisitablePosition());
 	}
 
-	rmg::Area visitablePos({entrableTile});
-	visitablePos.unite(visitablePos.getBorderOutside());
+	rmg::Area entrableBorder = entrableArea.getBorderOutside();
 	
 	auto accessibleArea = object.getAccessibleArea();
-	accessibleArea.intersect(visitablePos);
+	accessibleArea.erase_if([&](const int3 & tile)
+	{
+		return !entrableBorder.contains(tile);
+	});
+	
 	if(accessibleArea.empty())
 	{
 		delete guard;

+ 3 - 1
lib/rmg/modificators/ObjectManager.h

@@ -48,7 +48,8 @@ public:
 	{
 		NONE = 0x00000000,
 		WEIGHT = 0x00000001,
-		DISTANCE = 0x00000010
+		DISTANCE = 0x00000010,
+		BOTH = 0x00000011
 	};
 
 public:
@@ -61,6 +62,7 @@ public:
 	void addCloseObject(const RequiredObjectInfo & info);
 	void addNearbyObject(const RequiredObjectInfo & info);
 
+	bool createMonoliths();
 	bool createRequiredObjects();
 
 	int3 findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, si32 min_dist, OptimizeType optimizer) const;

+ 2 - 2
lib/rmg/modificators/ObstaclePlacer.cpp

@@ -51,7 +51,7 @@ void ObstaclePlacer::process()
 		do
 		{
 			toBlock.clear();
-			for (const auto& tile : zone.areaPossible().getTiles())
+			for (const auto& tile : zone.areaPossible().getTilesVector())
 			{
 				rmg::Area neighbors;
 				rmg::Area t;
@@ -76,7 +76,7 @@ void ObstaclePlacer::process()
 				}
 			}
 			zone.areaPossible().subtract(toBlock);
-			for (const auto& tile : toBlock.getTiles())
+			for (const auto& tile : toBlock.getTilesVector())
 			{
 				map.setOccupied(tile, ETileType::BLOCKED);
 			}

+ 92 - 68
lib/rmg/modificators/TreasurePlacer.cpp

@@ -584,7 +584,7 @@ std::vector<ObjectInfo*> TreasurePlacer::prepareTreasurePile(const CTreasureInfo
 	int maxValue = treasureInfo.max;
 	int minValue = treasureInfo.min;
 	
-	const ui32 desiredValue =zone.getRand().nextInt(minValue, maxValue);
+	const ui32 desiredValue = zone.getRand().nextInt(minValue, maxValue);
 	
 	int currentValue = 0;
 	bool hasLargeObject = false;
@@ -614,6 +614,13 @@ std::vector<ObjectInfo*> TreasurePlacer::prepareTreasurePile(const CTreasureInfo
 		oi->maxPerZone--;
 		
 		currentValue += oi->value;
+
+		if (currentValue >= minValue)
+		{
+			// 50% chance to end right here
+			if (zone.getRand().nextInt() & 1)
+				break;
+		}
 	}
 	
 	return objectInfos;
@@ -626,30 +633,41 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 	{
 		auto blockedArea = rmgObject.getArea();
 		auto entrableArea = rmgObject.getEntrableArea();
+		auto accessibleArea = rmgObject.getAccessibleArea();
 		
 		if(rmgObject.instances().empty())
-			entrableArea.add(int3());
+		{
+			accessibleArea.add(int3());
+		}
 		
 		auto * object = oi->generateObject();
 		if(oi->templates.empty())
 			continue;
 		
-		object->appearance = *RandomGeneratorUtil::nextItem(oi->templates, zone.getRand());
+		auto templates = object->getObjectHandler()->getMostSpecificTemplates(zone.getTerrainType());
+
+		if (templates.empty())
+		{
+			throw rmgException(boost::str(boost::format("Did not find template for object (%d,%d) at %s") % object->ID % object->subID % zone.getTerrainType().encode(zone.getTerrainType())));
+		}
 
-		auto blockingIssue = object->isBlockedVisitable() && !object->isRemovable();
-		if (blockingIssue)
+		object->appearance = *RandomGeneratorUtil::nextItem(templates, zone.getRand());
+
+		//Put object in accessible area next to entrable area (excluding blockvis tiles)
+		if (!entrableArea.empty())
 		{
-			// Do not place next to another such object (Corpse issue)
-			// Calculate this before instance is added to rmgObject
-			auto blockVisitProximity = rmgObject.getBlockVisitableArea().getBorderOutside();
-			entrableArea.subtract(blockVisitProximity);
+			auto entrableBorder = entrableArea.getBorderOutside();
+			accessibleArea.erase_if([&](const int3 & tile)
+			{
+				return !entrableBorder.count(tile);
+			});
 		}
 
 		auto & instance = rmgObject.addInstance(*object);
 
 		do
 		{
-			if(entrableArea.empty())
+			if(accessibleArea.empty())
 			{
 				//fail - fallback
 				rmgObject.clear();
@@ -657,15 +675,24 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 			}
 			
 			std::vector<int3> bestPositions;
-			if(densePlacement)
+			if(densePlacement && !entrableArea.empty())
 			{
+				// Choose positon which has access to as many entrable tiles as possible
 				int bestPositionsWeight = std::numeric_limits<int>::max();
-				for(const auto & t : entrableArea.getTilesVector())
+				for(const auto & t : accessibleArea.getTilesVector())
 				{
 					instance.setPosition(t);
-					int w = rmgObject.getEntrableArea().getTilesVector().size();
 
-					if(w && w < bestPositionsWeight)
+					auto currentAccessibleArea = rmgObject.getAccessibleArea();
+					auto currentEntrableBorder = rmgObject.getEntrableArea().getBorderOutside();
+					currentAccessibleArea.erase_if([&](const int3 & tile)
+					{
+						return !currentEntrableBorder.count(tile);
+					});
+
+					size_t w = currentAccessibleArea.getTilesVector().size();
+
+					if(w > bestPositionsWeight)
 					{
 						// Minimum 1 position must be entrable
 						bestPositions.clear();
@@ -677,12 +704,11 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 						bestPositions.push_back(t);
 					}
 				}
-
 			}
 
 			if (bestPositions.empty())
 			{
-				bestPositions = entrableArea.getTilesVector();
+				bestPositions = accessibleArea.getTilesVector();
 			}
 			
 			int3 nextPos = *RandomGeneratorUtil::nextItem(bestPositions, zone.getRand());
@@ -699,11 +725,11 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 			if(rmgObject.instances().size() == 1)
 				break;
 
-			if(!blockedArea.overlap(instance.getBlockedArea()) && entrableArea.overlap(instanceAccessibleArea))
+			if(!blockedArea.overlap(instance.getBlockedArea()) && accessibleArea.overlap(instanceAccessibleArea))
 				break;
 
 			//fail - new position
-			entrableArea.erase(nextPos);
+			accessibleArea.erase(nextPos);
 		} while(true);
 	}
 	return rmgObject;
@@ -791,7 +817,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 	size_t size = 0;
 	{
 		Zone::Lock lock(zone.areaMutex);
-		size = zone.getArea().getTiles().size();
+		size = zone.getArea().getTilesVector().size();
 	}
 
 	int totalDensity = 0;
@@ -808,16 +834,17 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 
 		totalDensity += t->density;
 
-		size_t count = size * t->density / 500;
+		const int DENSITY_CONSTANT = 300;
+		size_t count = (size * t->density) / DENSITY_CONSTANT;
 
 		//Assure space for lesser treasures, if there are any left
+		const int averageValue = (t->min + t->max) / 2;
 		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)));
+				vstd::amin(count, size * (10.f / DENSITY_CONSTANT) / (std::sqrt((float)averageValue / 10000)));
 			}
 		}
 		
@@ -837,7 +864,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 			int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; });
 
 			const ui32 maxPileGenerationAttemps = 2;
-			for (ui32 attempt = 0; attempt <= maxPileGenerationAttemps; attempt++)
+			for (ui32 attempt = 0; attempt < maxPileGenerationAttemps; attempt++)
 			{
 				auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts);
 
@@ -865,61 +892,58 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 		{
 			const bool guarded = rmgObject.isGuarded();
 
-			for (int attempt = 0; attempt <= maxAttempts;)
-			{
-				auto path = rmg::Path::invalid();
+			auto path = rmg::Path::invalid();
 
-				Zone::Lock lock(zone.areaMutex); //We are going to subtract this area
-				auto possibleArea = zone.areaPossible();
+			Zone::Lock lock(zone.areaMutex); //We are going to subtract this area
+			auto possibleArea = zone.areaPossible();
+			possibleArea.erase_if([this, &minDistance](const int3& tile) -> bool
+			{
+				auto ti = map.getTileInfo(tile);
+				return (ti.getNearestObjectDistance() < minDistance);
+			});
 
-				if (guarded)
-				{
-					path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile)
+			if (guarded)
+			{
+				path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile)
+					{
+						float bestDistance = 10e9;
+						for (const auto& t : rmgObject.getArea().getTilesVector())
 						{
-							auto ti = map.getTileInfo(tile);
-							if (ti.getNearestObjectDistance() < minDistance)
+							float distance = map.getTileInfo(t).getNearestObjectDistance();
+							if (distance < minDistance)
 								return -1.f;
+							else
+								vstd::amin(bestDistance, distance);
+						}
 
-							for (const auto& t : rmgObject.getArea().getTilesVector())
-							{
-								if (map.getTileInfo(t).getNearestObjectDistance() < minDistance)
-									return -1.f;
-							}
+						const auto & guardedArea = rmgObject.instances().back()->getAccessibleArea();
+						const auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea;
 
-							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;
+						if (zone.freePaths().overlap(areaToBlock) || manager.getVisitableArea().overlap(areaToBlock))
+							return -1.f;
 
-							return ti.getNearestObjectDistance();
-						}, guarded, false, ObjectManager::OptimizeType::DISTANCE);
-				}
-				else
-				{
-					path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
-				}
+						return bestDistance;
+					}, guarded, false, ObjectManager::OptimizeType::BOTH);
+			}
+			else
+			{
+				path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
+			}
+			lock.unlock();
 
-				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);
-					break;
-				}
-				else
+			if (path.valid())
+			{
+				//debug purposes
+				treasureArea.unite(rmgObject.getArea());
+				if (guarded)
 				{
-					++attempt;
+					guards.unite(rmgObject.instances().back()->getBlockedArea());
+					auto guardedArea = rmgObject.instances().back()->getAccessibleArea();
+					auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea;
+					treasureBlockArea.unite(areaToBlock);
 				}
+				zone.connectPath(path);
+				manager.placeObject(rmgObject, guarded, true);
 			}
 		}
 	}

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

@@ -112,7 +112,7 @@ void WaterProxy::collectLakes()
 		for(const auto & t : lake.getBorderOutside())
 			if(map.isOnMap(t))
 				lakes.back().neighbourZones[map.getZoneID(t)].add(t);
-		for(const auto & t : lake.getTiles())
+		for(const auto & t : lake.getTilesVector())
 			lakeMap[t] = lakeId;
 		
 		//each lake must have at least one free tile
@@ -143,7 +143,7 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 		{
 			if(!lake.keepConnections.count(dst.getId()))
 			{
-				for(const auto & ct : lake.neighbourZones[dst.getId()].getTiles())
+				for(const auto & ct : lake.neighbourZones[dst.getId()].getTilesVector())
 				{
 					if(map.isPossible(ct))
 						map.setOccupied(ct, ETileType::BLOCKED);
@@ -155,7 +155,7 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 			}
 
 			//Don't place shipyard or boats on the very small lake
-			if (lake.area.getTiles().size() < 25)
+			if (lake.area.getTilesVector().size() < 25)
 			{
 				logGlobal->info("Skipping very small lake at zone %d", dst.getId());
 				continue;
@@ -273,7 +273,7 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, bool createRoad, Rout
 
 	while(!boardingPositions.empty())
 	{
-		auto boardingPosition = *boardingPositions.getTiles().begin();
+		auto boardingPosition = *boardingPositions.getTilesVector().begin();
 		rmg::Area shipPositions({boardingPosition});
 		auto boutside = shipPositions.getBorderOutside();
 		shipPositions.assign(boutside);
@@ -336,7 +336,7 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, bool
 	
 	while(!boardingPositions.empty())
 	{
-		auto boardingPosition = *boardingPositions.getTiles().begin();
+		auto boardingPosition = *boardingPositions.getTilesVector().begin();
 		rmg::Area shipPositions({boardingPosition});
 		auto boutside = shipPositions.getBorderOutside();
 		shipPositions.assign(boutside);