Răsfoiți Sursa

Merge pull request #3869 from vcmi/road_routing

Do not place guards near roads
DjWarmonger 1 an în urmă
părinte
comite
1d28f25575

+ 61 - 3
lib/rmg/RmgObject.cpp

@@ -175,19 +175,22 @@ const CGObjectInstance & Object::Instance::object() const
 }
 
 Object::Object(CGObjectInstance & object, const int3 & position):
-	guarded(false)
+	guarded(false),
+	value(0)
 {
 	addInstance(object, position);
 }
 
 Object::Object(CGObjectInstance & object):
-	guarded(false)
+	guarded(false),
+	value(0)
 {
 	addInstance(object);
 }
 
 Object::Object(const Object & object):
-	guarded(false)
+	guarded(false),
+	value(object.value)
 {
 	for(const auto & i : object.dInstances)
 		addInstance(const_cast<CGObjectInstance &>(i.object()), i.getPosition());
@@ -356,6 +359,7 @@ void Object::setPosition(const int3 & position)
 	dVisitableCache.translate(shift);
 	dRemovableAreaCache.translate(shift);
 	dFullAreaCache.translate(shift);
+	dBorderAboveCache.translate(shift);
 	
 	dPosition = position;
 	for(auto& i : dInstances)
@@ -383,6 +387,20 @@ const Area & Object::getArea() const
 	return dFullAreaCache;
 }
 
+const Area & Object::getBorderAbove() const
+{
+	if(dBorderAboveCache.empty())
+	{
+		for(const auto & instance : dInstances)
+		{
+			if (instance.isRemovable() || instance.object().appearance->isVisitableFromTop())
+				continue;
+			dBorderAboveCache.unite(instance.getBorderAbove());
+		}
+	}
+	return dBorderAboveCache;
+}
+
 const int3 Object::getVisibleTop() const
 {
 	if (visibleTopOffset)
@@ -417,6 +435,45 @@ void rmg::Object::setGuardedIfMonster(const Instance& object)
 	}
 }
 
+int3 rmg::Object::getGuardPos() const
+{
+	if (guarded)
+	{
+		for (auto & instance : dInstances)
+		{
+			if (instance.object().ID == Obj::MONSTER)
+			{
+				return instance.getVisitablePosition();
+			}
+		}
+	}
+	return int3(-1,-1,-1);
+}
+
+void rmg::Object::setValue(uint32_t newValue)
+{
+	value = newValue;
+}
+
+uint32_t rmg::Object::getValue() const
+{
+	return value;
+}
+
+rmg::Area Object::Instance::getBorderAbove() const
+{
+	int3 visitablePos = getVisitablePosition();
+	auto areaVisitable = rmg::Area({visitablePos});
+	auto borderAbove = areaVisitable.getBorderOutside();
+	vstd::erase_if(borderAbove, [&](const int3 & tile)
+	{
+		return tile.y >= visitablePos.y ||
+		(!object().blockingAt(tile + int3(0, 1, 0)) && 
+		object().blockingAt(tile));
+	});
+	return borderAbove;
+}
+
 void Object::Instance::finalize(RmgMap & map, CRandomGenerator & rng)
 {
 	if(!map.isOnMap(getPosition(true)))
@@ -473,6 +530,7 @@ void Object::clearCachedArea() const
 	dBlockVisitableCache.clear();
 	dVisitableCache.clear();
 	dRemovableAreaCache.clear();
+	dBorderAboveCache.clear();
 }
 
 void Object::clear()

+ 7 - 0
lib/rmg/RmgObject.h

@@ -38,6 +38,7 @@ public:
 		bool isBlockedVisitable() const;
 		bool isRemovable() const;
 		const Area & getAccessibleArea() const;
+		Area getBorderAbove() const;
 		void setTemplate(TerrainId terrain, CRandomGenerator &); //cache invalidation
 		void setAnyTemplate(CRandomGenerator &); //cache invalidation
 		
@@ -78,6 +79,7 @@ public:
 	const Area & getVisitableArea() const;
 	const Area & getRemovableArea() const;
 	const Area getEntrableArea() const;
+	const Area & getBorderAbove() const;
 	
 	const int3 & getPosition() const;
 	void setPosition(const int3 & position);
@@ -87,7 +89,10 @@ public:
 	const int3 getVisibleTop() const;
 
 	bool isGuarded() const;
+	int3 getGuardPos() const;
 	void setGuardedIfMonster(const Instance & object);
+	void setValue(uint32_t value);
+	uint32_t getValue() const;
 	
 	void finalize(RmgMap & map, CRandomGenerator &);
 	void clearCachedArea() const;
@@ -101,11 +106,13 @@ private:
 	mutable Area dBlockVisitableCache;
 	mutable Area dVisitableCache;
 	mutable Area dRemovableAreaCache;
+	mutable Area dBorderAboveCache;
 	int3 dPosition;
 	mutable std::optional<int3> visibleTopOffset;
 	mutable std::list<Object::Instance*> cachedInstanceList;
 	mutable std::list<const Object::Instance*> cachedInstanceConstList;
 	bool guarded;
+	uint32_t value;
 };
 }
 

+ 1 - 9
lib/rmg/modificators/ObjectManager.cpp

@@ -648,15 +648,7 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
 			else if(!instance->object().appearance->isVisitableFromTop())
 			{
 				// 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));
-				});				
+				auto borderAbove = instance->getBorderAbove();
 				rp->areaIsolated().unite(borderAbove);
 			}
 

+ 5 - 1
lib/rmg/modificators/ObstaclePlacer.cpp

@@ -64,6 +64,10 @@ void ObstaclePlacer::process()
 		areaPossible->subtract(blockedArea);
 
 		prohibitedArea = zone.freePaths() + areaUsed + manager->getVisitableArea();
+		if (auto * rp = zone.getModificator<RoadPlacer>())
+		{
+			prohibitedArea.unite(rp->getRoads());
+		}
 
 		//Progressively block tiles, but make sure they don't seal any gap between blocks
 		rmg::Area toBlock;
@@ -116,7 +120,7 @@ void ObstaclePlacer::init()
 	DEPENDENCY(RoadPlacer);
 	if (zone.isUnderground())
 	{
-		DEPENDENCY(RockFiller);
+		DEPENDENCY_ALL(RockFiller);
 	}
 	else
 	{

+ 21 - 23
lib/rmg/modificators/RoadPlacer.cpp

@@ -34,12 +34,14 @@ void RoadPlacer::process()
 	connectRoads();
 }
 
+void RoadPlacer::postProcess()
+{
+	//Draw dirt roads if there are only mines
+	drawRoads(noRoadNodes);
+}
+
 void RoadPlacer::init()
 {
-	if(zone.isUnderground())
-	{
-		DEPENDENCY_ALL(RockFiller);
-	}
 }
 
 rmg::Area & RoadPlacer::areaForRoads()
@@ -75,11 +77,13 @@ bool RoadPlacer::createRoad(const int3 & dst)
 	{
 		if(areaIsolated().contains(dst))
 		{
-			return 1000.0f; //Do not route road behind objects that are not visitable from top
+			return 1000.0f; //Do not route road behind objects that are not visitable from top, such as Monoliths
 		}
 		else
 		{
-			auto ret = 1.0f;
+			float weight = dst.dist2dSQ(src);
+			auto ret =  weight * weight;
+
 			if (visitableTiles.contains(src) || visitableTiles.contains(dst))
 			{
 				ret *= VISITABLE_PENALTY;
@@ -133,20 +137,12 @@ bool RoadPlacer::createRoad(const int3 & dst)
 
 void RoadPlacer::drawRoads(bool secondary)
 {	
+	//Do not draw roads on underground rock or water
+	roads.erase_if([this](const int3& pos) -> bool
 	{
-		//Clean space under roads even if they won't be eventually generated
-		Zone::Lock lock(zone.areaMutex);
-
-		//Do not draw roads on underground rock or water
-		roads.erase_if([this](const int3& pos) -> bool
-		{
-			const auto* terrain = map.getTile(pos).terType;
-			return !terrain->isPassable() || !terrain->isLand();
-		});
-
-		zone.areaPossible()->subtract(roads);
-		zone.freePaths()->unite(roads);
-	}
+		const auto* terrain = map.getTile(pos).terType;
+		return !terrain->isPassable() || !terrain->isLand();
+	});
 
 	if(!generator.getMapGenOptions().isRoadEnabled())
 	{
@@ -184,7 +180,6 @@ void RoadPlacer::addRoadNode(const int3& node)
 
 void RoadPlacer::connectRoads()
 {
-	bool noRoadNodes = false;
 	//Assumes objects are already placed
 	if(roadNodes.size() < 2)
 	{
@@ -224,13 +219,16 @@ void RoadPlacer::connectRoads()
 		}
 	}
 	
-	//Draw dirt roads if there are only mines
-	drawRoads(noRoadNodes);
+	if (!zone.isUnderground())
+	{
+		// Otherwise roads will be drawn only after rock is placed
+		postProcess();
+	}
 }
 
 char RoadPlacer::dump(const int3 & t)
 {
-	if(roadNodes.count(t))
+	if(vstd::contains(roadNodes, t))
 		return '@';
 	if(roads.contains(t))
 		return '+';

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

@@ -19,6 +19,7 @@ public:
 	MODIFICATOR(RoadPlacer);
 	
 	void process() override;
+	void postProcess();
 	void init() override;
 	char dump(const int3 &) override;
 	
@@ -41,6 +42,8 @@ protected:
 	rmg::Area areaRoads;
 	rmg::Area isolated;
 	rmg::Area visitableTiles; // Tiles occupied by removable or passable objects
+
+	bool noRoadNodes = false;
 };
 
 VCMI_LIB_NAMESPACE_END

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

@@ -14,6 +14,7 @@
 #include "TreasurePlacer.h"
 #include "ObjectManager.h"
 #include "RiverPlacer.h"
+#include "RoadPlacer.h"
 #include "../RmgMap.h"
 #include "../CMapGenerator.h"
 #include "../Functions.h"
@@ -35,7 +36,7 @@ void RockFiller::process()
 void RockFiller::processMap()
 {
 	//Merge all areas
-	for(auto & z : map.getZones())
+	for(auto & z : map.getZonesOnLevel(1))
 	{
 		auto zone = z.second;
 		if(auto * m = zone->getModificator<RockPlacer>())
@@ -45,7 +46,7 @@ void RockFiller::processMap()
 		}
 	}
 	
-	for(auto & z : map.getZones())
+	for(auto & z : map.getZonesOnLevel(1))
 	{
 		auto zone = z.second;
 		if(auto * m = zone->getModificator<RockPlacer>())
@@ -56,6 +57,12 @@ void RockFiller::processMap()
 
 			m->postProcess();
 		}
+
+		// Draw roads after rock is placed
+		if(auto * rp = zone->getModificator<RoadPlacer>())
+		{
+			rp->postProcess();
+		}
 	}
 }
 

+ 18 - 7
lib/rmg/modificators/RockPlacer.cpp

@@ -30,14 +30,21 @@ void RockPlacer::process()
 {
 	blockRock();
 }
+
 void RockPlacer::blockRock()
 {
 	rockTerrain = VLC->terrainTypeHandler->getById(zone.getTerrainType())->rockTerrain;
 	assert(!VLC->terrainTypeHandler->getById(rockTerrain)->isPassable());
 
 	accessibleArea = zone.freePaths() + zone.areaUsed();
+	if(auto * rp = zone.getModificator<RoadPlacer>())
+	{
+		accessibleArea.unite(rp->getRoads());
+	}
 	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)
@@ -55,6 +62,12 @@ void RockPlacer::postProcess()
 		{
 			return !map.getTile(t).terType->isPassable();
 		});
+
+		// Do not place rock on roads
+		if(auto * rp = zone.getModificator<RoadPlacer>())
+		{
+			rockArea.subtract(rp->getRoads());
+		}
 		
 		zone.areaUsed()->unite(rockArea);
 		zone.areaPossible()->subtract(rockArea);
@@ -70,15 +83,13 @@ void RockPlacer::postProcess()
 
 void RockPlacer::init()
 {
-	for (const auto& zone : map.getZones())
+	DEPENDENCY(RoadPlacer);
+	for (const auto& zone : map.getZonesOnLevel(1))
 	{
-		if (zone.second->isUnderground())
+		auto * tp = zone.second->getModificator<TreasurePlacer>();
+		if (tp)
 		{
-			auto * tp = zone.second->getModificator<TreasurePlacer>();
-			if (tp)
-			{
-				dependency(tp);
-			}
+			dependency(tp);
 		}
 	}
 }

+ 33 - 3
lib/rmg/modificators/TreasurePlacer.cpp

@@ -55,7 +55,7 @@ void TreasurePlacer::init()
 	DEPENDENCY(ObjectManager);
 	DEPENDENCY(ConnectionsPlacer);
 	DEPENDENCY_ALL(PrisonHeroPlacer);
-	POSTFUNCTION(RoadPlacer);
+	DEPENDENCY(RoadPlacer);
 }
 
 void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi)
@@ -670,6 +670,7 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 		
 		if(rmgObject.instances().empty())
 		{
+			rmgObject.setValue(0);
 			accessibleArea.add(int3());
 		}
 		
@@ -702,6 +703,7 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 		}
 
 		auto & instance = rmgObject.addInstance(*object);
+		rmgObject.setValue(rmgObject.getValue() + oi->value);
 		instance.onCleared = oi->destroyObject;
 
 		do
@@ -829,6 +831,7 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 	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 const int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 };
 	minGuardedValue = minGuardedValues[monsterStrength];
+	const auto blockingGuardMaxValue = zone.getMaxTreasureValue() / 3;
 
 	auto valueComparator = [](const CTreasureInfo& lhs, const CTreasureInfo& rhs) -> bool
 	{
@@ -842,6 +845,15 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 			oi->maxPerZone++;
 		}
 	};
+
+	rmg::Area roads;
+	auto rp = zone.getModificator<RoadPlacer>();
+	if (rp)
+	{
+		roads = rp->getRoads();
+	}
+	rmg::Area nextToRoad(roads.getBorderOutside());
+
 	//place biggest treasures first at large distance, place smaller ones inbetween
 	auto treasureInfo = zone.getTreasureInfo();
 	boost::sort(treasureInfo, valueComparator);
@@ -928,7 +940,9 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 
 				if (guarded)
 				{
-					path = manager.placeAndConnectObject(searchArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile)
+					searchArea.subtract(roads);
+
+					path = manager.placeAndConnectObject(searchArea, rmgObject, [this, &rmgObject, &minDistance, &manager, blockingGuardMaxValue, &roads, &nextToRoad](const int3& tile)
 						{
 							float bestDistance = 10e9;
 							for (const auto& t : rmgObject.getArea().getTilesVector())
@@ -940,17 +954,33 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 									vstd::amin(bestDistance, distance);
 							}
 
+							// Guard cannot be adjacent to road, but blocked side of an object could be
+							if (rmgObject.getValue() > blockingGuardMaxValue && nextToRoad.contains(rmgObject.getGuardPos()))
+							{
+								return -1.f;
+							}
+
 							const auto & guardedArea = rmgObject.instances().back()->getAccessibleArea();
 							const auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea;
 
-							if (zone.freePaths()->overlap(areaToBlock) || manager.getVisitableArea().overlap(areaToBlock))
+							if (zone.freePaths()->overlap(areaToBlock) || roads.overlap(areaToBlock) || manager.getVisitableArea().overlap(areaToBlock))
 								return -1.f;
 
+							// Add huge penalty for objects hiding roads
+							if (rmgObject.getBorderAbove().overlap(roads))
+								bestDistance /= 10.0f;
+
 							return bestDistance;
 						}, guarded, false, ObjectManager::OptimizeType::BOTH);
 				}
 				else
 				{
+					// Do not place non-removable objects on roads
+					if (!rmgObject.getRemovableArea().contains(rmgObject.getArea()))
+					{
+						searchArea.subtract(roads);
+					}
+
 					path = manager.placeAndConnectObject(searchArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
 				}
 			}