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:
24
lib/int3.h
24
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);
|
||||
}
|
||||
};
|
||||
};
|
@@ -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);
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
}
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user