1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Merge pull request #3308 from vcmi/improve_treasure_placement

Improve treasure placement
This commit is contained in:
Ivan Savenko
2023-12-22 16:32:59 +02:00
committed by GitHub
20 changed files with 456 additions and 221 deletions

View File

@@ -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);
}
};
};

View File

@@ -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);

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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)
{
if (i.getTopTile().y < topTile.y)
{
topTile = i.getTopTile();
}
return dPosition + visibleTopOffset.value();
}
else
{
int3 topTile(-1, 10000, -1); //Start at the bottom
for (const auto& i : dInstances)
{
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();
}

View File

@@ -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;
};
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();

View File

@@ -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)
{

View File

@@ -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())
m->areaForRoads().add(instance->getVisitablePosition());
else
{
m->areaIsolated().add(instance->getVisitablePosition() + int3(0, -1, 0));
//Passable objects
m->areaForRoads().add(instance->getVisitablePosition());
}
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));
});
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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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());
auto blockingIssue = object->isBlockedVisitable() && !object->isRemovable();
if (blockingIssue)
if (templates.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);
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())));
}
object->appearance = *RandomGeneratorUtil::nextItem(templates, zone.getRand());
//Put object in accessible area next to entrable area (excluding blockvis tiles)
if (!entrableArea.empty())
{
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();
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 path = rmg::Path::invalid();
auto ti = map.getTileInfo(tile);
return (ti.getNearestObjectDistance() < minDistance);
});
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)
{
float bestDistance = 10e9;
for (const auto& t : rmgObject.getArea().getTilesVector())
{
float distance = map.getTileInfo(t).getNearestObjectDistance();
if (distance < minDistance)
return -1.f;
else
vstd::amin(bestDistance, distance);
}
const auto & guardedArea = rmgObject.instances().back()->getAccessibleArea();
const auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea;
if (zone.freePaths().overlap(areaToBlock) || manager.getVisitableArea().overlap(areaToBlock))
return -1.f;
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)
{
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;
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);
}
}
}

View File

@@ -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);