mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
Generate all treasures of certain value beforehand and try to place them all, don't interrupt at first failure.
This commit is contained in:
@@ -161,17 +161,21 @@ const CGObjectInstance & Object::Instance::object() const
|
|||||||
return dObject;
|
return dObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object::Object(CGObjectInstance & object, const int3 & position)
|
Object::Object(CGObjectInstance & object, const int3 & position):
|
||||||
|
guarded(false)
|
||||||
{
|
{
|
||||||
addInstance(object, position);
|
addInstance(object, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object::Object(CGObjectInstance & object)
|
Object::Object(CGObjectInstance & object):
|
||||||
|
guarded(false)
|
||||||
{
|
{
|
||||||
addInstance(object);
|
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)
|
for(const auto & i : object.dInstances)
|
||||||
addInstance(const_cast<CGObjectInstance &>(i.object()), i.getPosition());
|
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)
|
void Object::addInstance(Instance & object)
|
||||||
{
|
{
|
||||||
//assert(object.dParent == *this);
|
//assert(object.dParent == *this);
|
||||||
|
setGuardedIfMonster(object);
|
||||||
dInstances.push_back(object);
|
dInstances.push_back(object);
|
||||||
|
|
||||||
dFullAreaCache.clear();
|
dFullAreaCache.clear();
|
||||||
dAccessibleAreaCache.clear();
|
dAccessibleAreaCache.clear();
|
||||||
dAccessibleAreaFullCache.clear();
|
dAccessibleAreaFullCache.clear();
|
||||||
@@ -206,6 +212,8 @@ void Object::addInstance(Instance & object)
|
|||||||
Object::Instance & Object::addInstance(CGObjectInstance & object)
|
Object::Instance & Object::addInstance(CGObjectInstance & object)
|
||||||
{
|
{
|
||||||
dInstances.emplace_back(*this, object);
|
dInstances.emplace_back(*this, object);
|
||||||
|
setGuardedIfMonster(dInstances.back());
|
||||||
|
|
||||||
dFullAreaCache.clear();
|
dFullAreaCache.clear();
|
||||||
dAccessibleAreaCache.clear();
|
dAccessibleAreaCache.clear();
|
||||||
dAccessibleAreaFullCache.clear();
|
dAccessibleAreaFullCache.clear();
|
||||||
@@ -215,6 +223,8 @@ Object::Instance & Object::addInstance(CGObjectInstance & object)
|
|||||||
Object::Instance & Object::addInstance(CGObjectInstance & object, const int3 & position)
|
Object::Instance & Object::addInstance(CGObjectInstance & object, const int3 & position)
|
||||||
{
|
{
|
||||||
dInstances.emplace_back(*this, object, position);
|
dInstances.emplace_back(*this, object, position);
|
||||||
|
setGuardedIfMonster(dInstances.back());
|
||||||
|
|
||||||
dFullAreaCache.clear();
|
dFullAreaCache.clear();
|
||||||
dAccessibleAreaCache.clear();
|
dAccessibleAreaCache.clear();
|
||||||
dAccessibleAreaFullCache.clear();
|
dAccessibleAreaFullCache.clear();
|
||||||
@@ -302,6 +312,19 @@ const int3 Object::getVisibleTop() const
|
|||||||
return topTile;
|
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)
|
void Object::Instance::finalize(RmgMap & map)
|
||||||
{
|
{
|
||||||
if(!map.isOnMap(getPosition(true)))
|
if(!map.isOnMap(getPosition(true)))
|
||||||
|
@@ -77,6 +77,9 @@ public:
|
|||||||
|
|
||||||
const Area & getArea() const; //lazy cache invalidation
|
const Area & getArea() const; //lazy cache invalidation
|
||||||
const int3 getVisibleTop() const;
|
const int3 getVisibleTop() const;
|
||||||
|
|
||||||
|
bool isGuarded() const;
|
||||||
|
void setGuardedIfMonster(const Instance & object);
|
||||||
|
|
||||||
void finalize(RmgMap & map);
|
void finalize(RmgMap & map);
|
||||||
void clear();
|
void clear();
|
||||||
@@ -87,6 +90,7 @@ private:
|
|||||||
mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache;
|
mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache;
|
||||||
int3 dPosition;
|
int3 dPosition;
|
||||||
ui32 dStrength;
|
ui32 dStrength;
|
||||||
|
bool guarded;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -701,133 +701,160 @@ ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TreasurePlacer::createTreasures(ObjectManager & manager)
|
void TreasurePlacer::createTreasures(ObjectManager& manager)
|
||||||
{
|
{
|
||||||
const int maxAttempts = 2;
|
const int maxAttempts = 2;
|
||||||
|
const int minDistance = 2;
|
||||||
|
|
||||||
int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength();
|
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 };
|
static int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 };
|
||||||
minGuardedValue = minGuardedValues[monsterStrength];
|
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;
|
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++;
|
oi->maxPerZone++;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//place biggest treasures first at large distance, place smaller ones inbetween
|
//place biggest treasures first at large distance, place smaller ones inbetween
|
||||||
auto treasureInfo = zone.getTreasureInfo();
|
auto treasureInfo = zone.getTreasureInfo();
|
||||||
boost::sort(treasureInfo, valueComparator);
|
boost::sort(treasureInfo, valueComparator);
|
||||||
|
|
||||||
//sort treasures by ascending value so we can stop checking treasures with too high value
|
//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
|
boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool
|
||||||
{
|
{
|
||||||
return oi1.value < oi2.value;
|
return oi1.value < oi2.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
size_t size = 0;
|
||||||
|
{
|
||||||
|
Zone::Lock lock(zone.areaMutex);
|
||||||
|
size = zone.areaPossible().getTiles().size();
|
||||||
|
}
|
||||||
|
|
||||||
int totalDensity = 0;
|
int totalDensity = 0;
|
||||||
|
|
||||||
for (auto t : treasureInfo)
|
for (auto t : treasureInfo)
|
||||||
{
|
{
|
||||||
|
std::vector<rmg::Object> treasures;
|
||||||
|
|
||||||
//discard objects with too high value to be ever placed
|
//discard objects with too high value to be ever placed
|
||||||
vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool
|
vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool
|
||||||
{
|
{
|
||||||
return oi.value > t.max;
|
return oi.value > t.max;
|
||||||
});
|
});
|
||||||
|
|
||||||
totalDensity += t.density;
|
totalDensity += t.density;
|
||||||
|
|
||||||
|
const size_t count = size * t.density / 300;
|
||||||
|
|
||||||
//treasure density is inversely proportional to zone size but must be scaled back to map size
|
//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
|
//also, normalize it to zone count - higher count means relatively smaller zones
|
||||||
|
|
||||||
//this is squared distance for optimization purposes
|
//this is squared distance for optimization purposes
|
||||||
const float minDistance = std::max<float>((125.f / totalDensity), 2.0f);
|
const float minDistance = std::max<float>((125.f / totalDensity), 1.0f);
|
||||||
//distance lower than 2 causes objects to overlap and crash
|
|
||||||
|
for (size_t i = 0; i < count;)
|
||||||
for(int attempt = 0; attempt <= maxAttempts;)
|
|
||||||
{
|
{
|
||||||
auto treasurePileInfos = prepareTreasurePile(t);
|
auto treasurePileInfos = prepareTreasurePile(t);
|
||||||
if(treasurePileInfos.empty())
|
if (treasurePileInfos.empty())
|
||||||
{
|
{
|
||||||
++attempt;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
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
|
|
||||||
{
|
{
|
||||||
restoreZoneLimits(treasurePileInfos);
|
i++;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//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)
|
|
||||||
{
|
|
||||||
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())
|
int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; });
|
||||||
{
|
|
||||||
if(map.getTileInfo(t).getNearestObjectDistance() < minDistance)
|
for (ui32 attempt = 0; attempt <= 2; attempt++)
|
||||||
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);
|
auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts);
|
||||||
}
|
|
||||||
|
if (rmgObject.instances().empty()) //handle incorrect placement
|
||||||
if(path.valid())
|
|
||||||
{
|
|
||||||
//debug purposes
|
|
||||||
treasureArea.unite(rmgObject.getArea());
|
|
||||||
if(guarded)
|
|
||||||
{
|
{
|
||||||
guards.unite(rmgObject.instances().back()->getBlockedArea());
|
restoreZoneLimits(treasurePileInfos);
|
||||||
auto guardedArea = rmgObject.instances().back()->getAccessibleArea();
|
continue;
|
||||||
auto areaToBlock = rmgObject.getAccessibleArea(true);
|
|
||||||
areaToBlock.subtract(guardedArea);
|
|
||||||
treasureBlockArea.unite(areaToBlock);
|
|
||||||
}
|
}
|
||||||
zone.connectPath(path);
|
|
||||||
manager.placeObject(rmgObject, guarded, true);
|
//guard treasure pile
|
||||||
attempt = 0;
|
bool guarded = isGuardNeededForTreasure(value);
|
||||||
|
if (guarded)
|
||||||
|
guarded = manager.addGuard(rmgObject, value);
|
||||||
|
|
||||||
|
treasures.push_back(rmgObject);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
for (auto& rmgObject : treasures)
|
||||||
|
{
|
||||||
|
const bool guarded = rmgObject.isGuarded();
|
||||||
|
|
||||||
|
for (int attempt = 0; attempt <= maxAttempts;)
|
||||||
{
|
{
|
||||||
restoreZoneLimits(treasurePileInfos);
|
auto path = rmg::Path::invalid();
|
||||||
rmgObject.clear();
|
|
||||||
++attempt;
|
Zone::Lock lock(zone.areaMutex); //We are going to subtract this area
|
||||||
|
auto possibleArea = zone.areaPossible();
|
||||||
|
|
||||||
|
if (guarded)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
++attempt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user