mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-05 15:05:40 +02:00
Merge pull request #3869 from vcmi/road_routing
Do not place guards near roads
This commit is contained in:
commit
1d28f25575
@ -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()
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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 '+';
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user