diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/longRun.JSON b/Mods/vcmi/config/vcmi/rmg/hdmod/longRun.JSON index e4ca7f4ec..5adc35872 100755 --- a/Mods/vcmi/config/vcmi/rmg/hdmod/longRun.JSON +++ b/Mods/vcmi/config/vcmi/rmg/hdmod/longRun.JSON @@ -18,8 +18,7 @@ "treasure" : [ { "min" : 300, "max" : 3000, "density" : 12 }, - { "min" : 5000, "max" : 9000, "density" : 6 }, - { "min" : 0, "max" : 0, "density" : 1 } + { "min" : 5000, "max" : 9000, "density" : 6 } ] }, "2" : @@ -34,8 +33,8 @@ "mines" : { "wood" : 1, "mercury" : 1, "ore" : 1, "sulfur" : 1, "crystal" : 1, "gems" : 1, "gold" : 1 }, "treasure" : [ - { "min" : 5000, "max" : 7000, "density" : 30 }, { "min" : 10000, "max" : 15000, "density" : 1 }, + { "min" : 5000, "max" : 7000, "density" : 30 }, { "min" : 300, "max" : 3000, "density" : 5 } ] }, @@ -51,8 +50,8 @@ "mines" : { "wood" : 1, "mercury" : 1, "ore" : 1, "sulfur" : 1, "crystal" : 1, "gems" : 1, "gold" : 0 }, "treasure" : [ - { "min" : 12000, "max" : 16000, "density" : 5 }, { "min" : 20000, "max" : 21000, "density" : 6 }, + { "min" : 12000, "max" : 16000, "density" : 5 }, { "min" : 300, "max" : 3000, "density" : 5 } ] }, @@ -69,8 +68,7 @@ "treasure" : [ { "min" : 25000, "max" : 30000, "density" : 10 }, - { "min" : 300, "max" : 3000, "density" : 10 }, - { "min" : 0, "max" : 0, "density" : 1 } + { "min" : 300, "max" : 3000, "density" : 10 } ] }, "5" : @@ -85,9 +83,7 @@ "mines" : { "wood" : 0, "mercury" : 0, "ore" : 0, "sulfur" : 0, "crystal" : 0, "gems" : 0, "gold" : 4 }, "treasure" : [ - { "min" : 30000, "max" : 90000, "density" : 25 }, - { "min" : 0, "max" : 0, "density" : 1 }, - { "min" : 0, "max" : 0, "density" : 1 } + { "min" : 30000, "max" : 90000, "density" : 25 } ] }, "6" : @@ -167,8 +163,7 @@ "treasure" : [ { "min" : 300, "max" : 3000, "density" : 12 }, - { "min" : 5000, "max" : 9000, "density" : 6 }, - { "min" : 0, "max" : 0, "density" : 1 } + { "min" : 5000, "max" : 9000, "density" : 6 } ] }, "2" : @@ -183,8 +178,8 @@ "mines" : { "wood" : 1, "mercury" : 1, "ore" : 1, "sulfur" : 1, "crystal" : 1, "gems" : 1, "gold" : 1 }, "treasure" : [ - { "min" : 5000, "max" : 7000, "density" : 10 }, { "min" : 10000, "max" : 15000, "density" : 1 }, + { "min" : 5000, "max" : 7000, "density" : 10 }, { "min" : 300, "max" : 3000, "density" : 5 } ] }, @@ -200,8 +195,8 @@ "mines" : { "wood" : 1, "mercury" : 1, "ore" : 1, "sulfur" : 1, "crystal" : 1, "gems" : 1, "gold" : 0 }, "treasure" : [ - { "min" : 12000, "max" : 16000, "density" : 5 }, { "min" : 20000, "max" : 21000, "density" : 6 }, + { "min" : 12000, "max" : 16000, "density" : 5 }, { "min" : 300, "max" : 3000, "density" : 5 } ] }, @@ -218,8 +213,7 @@ "treasure" : [ { "min" : 25000, "max" : 30000, "density" : 10 }, - { "min" : 300, "max" : 3000, "density" : 10 }, - { "min" : 0, "max" : 0, "density" : 1 } + { "min" : 300, "max" : 3000, "density" : 10 } ] }, "5" : @@ -233,9 +227,7 @@ "mines" : { "wood" : 0, "mercury" : 0, "ore" : 0, "sulfur" : 0, "crystal" : 0, "gems" : 0, "gold" : 10 }, "treasure" : [ - { "min" : 30000, "max" : 90000, "density" : 25 }, - { "min" : 0, "max" : 0, "density" : 1 }, - { "min" : 0, "max" : 0, "density" : 1 } + { "min" : 30000, "max" : 90000, "density" : 25 } ] }, "6" : diff --git a/config/objects/generic.json b/config/objects/generic.json index 0301f45a7..04c781969 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -511,6 +511,7 @@ "index" : 0, "aiValue" : 100, "rmg" : { + "zoneLimit" : 1, "mapLimit" : 32, "value" : 100, "rarity" : 20 diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 93f1db2a4..643b66dcb 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -50,6 +50,34 @@ } }, + "randomResource": + { + "index" :76, + "handler": "resource", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VA" ] + } + }, + "types" : { + "randomResource" : { + "index" : 0, + "rmg" : { + "value" : 1500, + "rarity" : 2000 + }, + "templates" : + { + "res" : + { + "animation" : "AVTrndm0.def" + } + } + } + } + }, + // subtype: resource ID "resource" : { "index" :79, diff --git a/config/objects/rewardableBonusing.json b/config/objects/rewardableBonusing.json index ff1f4c2cd..64f1855c0 100644 --- a/config/objects/rewardableBonusing.json +++ b/config/objects/rewardableBonusing.json @@ -15,6 +15,7 @@ "index" : 0, "aiValue" : 100, "rmg" : { + "zoneLimit" : 1, "value" : 100, "rarity" : 100 }, @@ -253,6 +254,7 @@ "index" : 0, "aiValue" : 100, "rmg" : { + "zoneLimit" : 1, "value" : 100, "rarity" : 20 }, diff --git a/lib/rmg/RmgArea.cpp b/lib/rmg/RmgArea.cpp index 5be26a8c4..3131bf188 100644 --- a/lib/rmg/RmgArea.cpp +++ b/lib/rmg/RmgArea.cpp @@ -64,21 +64,35 @@ void Area::invalidate() dBorderOutsideCache.clear(); } -bool Area::connected() const +bool Area::connected(bool noDiagonals) const { std::list queue({*dTiles.begin()}); Tileset connected = dTiles; //use invalidated cache - ok + while(!queue.empty()) { auto t = queue.front(); connected.erase(t); queue.pop_front(); - for(auto & i : int3::getDirs()) + if (noDiagonals) { - if(connected.count(t + i)) + for (auto& i : dirs4) { - queue.push_back(t + i); + if (connected.count(t + i)) + { + queue.push_back(t + i); + } + } + } + else + { + for (auto& i : int3::getDirs()) + { + if (connected.count(t + i)) + { + queue.push_back(t + i); + } } } } diff --git a/lib/rmg/RmgArea.h b/lib/rmg/RmgArea.h index 154c4acbf..1755f22d8 100644 --- a/lib/rmg/RmgArea.h +++ b/lib/rmg/RmgArea.h @@ -44,7 +44,7 @@ namespace rmg Area getSubarea(const std::function & filter) const; - bool connected() const; //is connected + bool connected(bool noDiagonals = false) const; //is connected bool empty() const; bool contains(const int3 & tile) const; bool contains(const std::vector & tiles) const; diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index bfe775f1c..540463948 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -161,17 +161,21 @@ const CGObjectInstance & Object::Instance::object() const return dObject; } -Object::Object(CGObjectInstance & object, const int3 & position) +Object::Object(CGObjectInstance & object, const int3 & position): + guarded(false) { addInstance(object, position); } -Object::Object(CGObjectInstance & object) +Object::Object(CGObjectInstance & object): + guarded(false) { addInstance(object); } -Object::Object(const Object & object): dStrength(object.dStrength) +Object::Object(const Object & object): + dStrength(object.dStrength), + guarded(false) { for(const auto & i : object.dInstances) addInstance(const_cast(i.object()), i.getPosition()); @@ -197,7 +201,9 @@ std::list Object::instances() const void Object::addInstance(Instance & object) { //assert(object.dParent == *this); + setGuardedIfMonster(object); dInstances.push_back(object); + dFullAreaCache.clear(); dAccessibleAreaCache.clear(); dAccessibleAreaFullCache.clear(); @@ -206,6 +212,8 @@ void Object::addInstance(Instance & object) Object::Instance & Object::addInstance(CGObjectInstance & object) { dInstances.emplace_back(*this, object); + setGuardedIfMonster(dInstances.back()); + dFullAreaCache.clear(); dAccessibleAreaCache.clear(); dAccessibleAreaFullCache.clear(); @@ -215,6 +223,8 @@ Object::Instance & Object::addInstance(CGObjectInstance & object) Object::Instance & Object::addInstance(CGObjectInstance & object, const int3 & position) { dInstances.emplace_back(*this, object, position); + setGuardedIfMonster(dInstances.back()); + dFullAreaCache.clear(); dAccessibleAreaCache.clear(); dAccessibleAreaFullCache.clear(); @@ -302,6 +312,19 @@ const int3 Object::getVisibleTop() const return topTile; } +bool rmg::Object::isGuarded() const +{ + return guarded; +} + +void rmg::Object::setGuardedIfMonster(const Instance& object) +{ + if (object.object().ID == Obj::MONSTER) + { + guarded = true; + } +} + void Object::Instance::finalize(RmgMap & map) { if(!map.isOnMap(getPosition(true))) diff --git a/lib/rmg/RmgObject.h b/lib/rmg/RmgObject.h index a164b9161..d444a90b4 100644 --- a/lib/rmg/RmgObject.h +++ b/lib/rmg/RmgObject.h @@ -77,6 +77,9 @@ public: const Area & getArea() const; //lazy cache invalidation const int3 getVisibleTop() const; + + bool isGuarded() const; + void setGuardedIfMonster(const Instance & object); void finalize(RmgMap & map); void clear(); @@ -87,6 +90,7 @@ private: mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache; int3 dPosition; ui32 dStrength; + bool guarded; }; } diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index ca0e3e159..8da771c6d 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -203,8 +203,34 @@ void Zone::fractalize() rmg::Area possibleTiles(dAreaPossible); rmg::Area tilesToIgnore; //will be erased in this iteration - const float minDistance = 10 * 10; //squared - float blockDistance = minDistance * 0.25f; + //Squared + float minDistance = 10 * 10; + float spanFactor = (pos.z ? 0.25 : 0.5f); //Narrower passages in the Underground + + int treasureValue = 0; + int treasureDensity = 0; + for (auto t : treasureInfo) + { + treasureValue += ((t.min + t.max) / 2) * t.density / 1000.f; //Thousands + treasureDensity += t.density; + } + + if (treasureValue > 200) + { + //Less obstacles - max span is 1 (no obstacles) + spanFactor = 1.0f - ((std::max(0, (1000 - treasureValue)) / (1000.f - 200)) * (1 - spanFactor)); + } + else if (treasureValue < 100) + { + //Dense obstacles + spanFactor *= (treasureValue / 100.f); + vstd::amax(spanFactor, 0.2f); + } + if (treasureDensity <= 10) + { + vstd::amin(spanFactor, 0.25f); //Add extra obstacles to fill up space + } + float blockDistance = minDistance * spanFactor; //More obstacles in the Underground if(type != ETemplateZoneType::JUNCTION) { diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index 52c3eb8ce..c82e632e0 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -166,7 +166,6 @@ int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object } } - //FIXME: Race condition for tiles? For Area? if(result.valid()) obj.setPosition(result); return result; @@ -183,8 +182,15 @@ int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object for(const auto & t : obj.getArea().getTilesVector()) { - if(map.getTileInfo(t).getNearestObjectDistance() < min_dist) + auto localDist = map.getTileInfo(t).getNearestObjectDistance(); + if (localDist < min_dist) + { return -1.f; + } + else + { + vstd::amin(dist, localDist); //Evaluate object tile which will be closest to another object + } } return dist; @@ -206,6 +212,55 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg return -1.f; } + rmg::Area perimeter; + rmg::Area areaToBlock; + if (obj.isGuarded()) + { + auto guardedArea = obj.instances().back()->getAccessibleArea(); + guardedArea.add(obj.instances().back()->getVisitablePosition()); + areaToBlock = obj.getAccessibleArea(true); + areaToBlock.subtract(guardedArea); + + if (!areaToBlock.empty()) + { + perimeter = areaToBlock; + perimeter.unite(areaToBlock.getBorderOutside()); + //We could have added border around guard + perimeter.subtract(guardedArea); + } + } + else + { + perimeter = obj.getArea(); + perimeter.subtract(obj.getAccessibleArea()); + if (!perimeter.empty()) + { + perimeter.unite(perimeter.getBorderOutside()); + perimeter.subtract(obj.getAccessibleArea()); + } + } + //Check if perimeter of the object intersects with more than one blocked areas + + auto tiles = perimeter.getTiles(); + vstd::erase_if(tiles, [this](const int3& tile) -> bool + { + //Out-of-map area also is an obstacle + if (!map.isOnMap(tile)) + return false; + return !(map.isBlocked(tile) || map.isUsed(tile)); + }); + + if (!tiles.empty()) + { + rmg::Area border(tiles); + border.subtract(areaToBlock); + if (!border.connected()) + { + //We don't want to connect two blocked areas to create impassable obstacle + return -1.f; + } + } + return dist; }, isGuarded, onlyStraight, optimizer); } @@ -230,7 +285,7 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg accessibleArea.intersect(guardedArea); accessibleArea.add(obj.instances().back()->getPosition(true)); } - + auto path = zone.searchPath(accessibleArea, onlyStraight, [&obj, isGuarded](const int3 & t) { if(isGuarded) diff --git a/lib/rmg/modificators/ObstaclePlacer.cpp b/lib/rmg/modificators/ObstaclePlacer.cpp index a19ffcb3f..26244f8da 100644 --- a/lib/rmg/modificators/ObstaclePlacer.cpp +++ b/lib/rmg/modificators/ObstaclePlacer.cpp @@ -12,7 +12,7 @@ #include "ObstaclePlacer.h" #include "ObjectManager.h" #include "TreasurePlacer.h" -#include "RockPlacer.h" +#include "RockFiller.h" #include "WaterRoutes.h" #include "WaterProxy.h" #include "RoadPlacer.h" @@ -35,14 +35,56 @@ void ObstaclePlacer::process() collectPossibleObstacles(zone.getTerrainType()); - blockedArea = zone.area().getSubarea([this](const int3 & t) { - return map.shouldBeBlocked(t); - }); - blockedArea.subtract(zone.areaUsed()); - zone.areaPossible().subtract(blockedArea); + Zone::Lock lock(zone.areaMutex); + blockedArea = zone.area().getSubarea([this](const int3& t) + { + return map.shouldBeBlocked(t); + }); + blockedArea.subtract(zone.areaUsed()); + zone.areaPossible().subtract(blockedArea); - prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea(); + prohibitedArea = zone.freePaths() + zone.areaUsed() + manager->getVisitableArea(); + + //Progressively block tiles, but make sure they don't seal any gap between blocks + rmg::Area toBlock; + do + { + toBlock.clear(); + for (const auto& tile : zone.areaPossible().getTiles()) + { + rmg::Area neighbors; + rmg::Area t; + t.add(tile); + + for (const auto& n : t.getBorderOutside()) + { + //Area outside the map is also impassable + if (!map.isOnMap(n) || map.shouldBeBlocked(n)) + { + neighbors.add(n); + } + } + if (neighbors.empty()) + { + continue; + } + //Will only be added if it doesn't connect two disjointed blocks + if (neighbors.connected(true)) //Do not block diagonal pass + { + toBlock.add(tile); + } + } + zone.areaPossible().subtract(toBlock); + for (const auto& tile : toBlock.getTiles()) + { + map.setOccupied(tile, ETileType::BLOCKED); + } + + } while (!toBlock.empty()); + + prohibitedArea.unite(zone.areaPossible()); + } auto objs = createObstacles(zone.getRand()); mapProxy->insertObjects(objs); @@ -52,10 +94,16 @@ void ObstaclePlacer::init() { DEPENDENCY(ObjectManager); DEPENDENCY(TreasurePlacer); - DEPENDENCY(WaterRoutes); - DEPENDENCY(WaterProxy); DEPENDENCY(RoadPlacer); - DEPENDENCY_ALL(RockPlacer); + if (zone.isUnderground()) + { + DEPENDENCY(RockFiller); + } + else + { + DEPENDENCY(WaterRoutes); + DEPENDENCY(WaterProxy); + } } bool ObstaclePlacer::isInTheMap(const int3& tile) diff --git a/lib/rmg/modificators/RiverPlacer.cpp b/lib/rmg/modificators/RiverPlacer.cpp index 901a5ec7e..5ce043c42 100644 --- a/lib/rmg/modificators/RiverPlacer.cpp +++ b/lib/rmg/modificators/RiverPlacer.cpp @@ -82,7 +82,10 @@ void RiverPlacer::process() void RiverPlacer::init() { - DEPENDENCY_ALL(WaterProxy); + if (!zone.isUnderground()) + { + DEPENDENCY_ALL(WaterProxy); + } DEPENDENCY(ObjectManager); DEPENDENCY(ObstaclePlacer); } diff --git a/lib/rmg/modificators/RoadPlacer.cpp b/lib/rmg/modificators/RoadPlacer.cpp index 2ecd74a89..dd9768fc1 100644 --- a/lib/rmg/modificators/RoadPlacer.cpp +++ b/lib/rmg/modificators/RoadPlacer.cpp @@ -12,6 +12,7 @@ #include "RoadPlacer.h" #include "ObjectManager.h" #include "ObstaclePlacer.h" +#include "RockFiller.h" #include "../Functions.h" #include "../CMapGenerator.h" #include "../threadpool/MapProxy.h" @@ -28,6 +29,14 @@ void RoadPlacer::process() connectRoads(); } +void RoadPlacer::init() +{ + if (zone.isUnderground()) + { + DEPENDENCY_ALL(RockFiller); + } +} + rmg::Area & RoadPlacer::areaForRoads() { return areaRoads; diff --git a/lib/rmg/modificators/RoadPlacer.h b/lib/rmg/modificators/RoadPlacer.h index 6ca5036c4..531b0407d 100644 --- a/lib/rmg/modificators/RoadPlacer.h +++ b/lib/rmg/modificators/RoadPlacer.h @@ -19,6 +19,7 @@ public: MODIFICATOR(RoadPlacer); void process() override; + void init() override; char dump(const int3 &) override; void addRoadNode(const int3 & node); diff --git a/lib/rmg/modificators/RockFiller.cpp b/lib/rmg/modificators/RockFiller.cpp index 85c896280..9b0e0bf19 100644 --- a/lib/rmg/modificators/RockFiller.cpp +++ b/lib/rmg/modificators/RockFiller.cpp @@ -13,7 +13,6 @@ #include "RockPlacer.h" #include "TreasurePlacer.h" #include "ObjectManager.h" -#include "RoadPlacer.h" #include "RiverPlacer.h" #include "../RmgMap.h" #include "../CMapGenerator.h" @@ -63,7 +62,6 @@ void RockFiller::processMap() void RockFiller::init() { DEPENDENCY_ALL(RockPlacer); - POSTFUNCTION_ALL(RoadPlacer); } char RockFiller::dump(const int3 & t) diff --git a/lib/rmg/modificators/RockPlacer.cpp b/lib/rmg/modificators/RockPlacer.cpp index ba0360619..7056bdf0b 100644 --- a/lib/rmg/modificators/RockPlacer.cpp +++ b/lib/rmg/modificators/RockPlacer.cpp @@ -67,7 +67,17 @@ void RockPlacer::postProcess() void RockPlacer::init() { - DEPENDENCY_ALL(TreasurePlacer); + for (const auto& zone : map.getZones()) + { + if (zone.second->isUnderground()) + { + auto * tp = zone.second->getModificator(); + if (tp) + { + dependency(tp); + } + } + } } char RockPlacer::dump(const int3 & t) diff --git a/lib/rmg/modificators/TownPlacer.cpp b/lib/rmg/modificators/TownPlacer.cpp index f4e4f6895..883508c2c 100644 --- a/lib/rmg/modificators/TownPlacer.cpp +++ b/lib/rmg/modificators/TownPlacer.cpp @@ -163,11 +163,14 @@ void TownPlacer::cleanupBoundaries(const rmg::Object & rmgObject) Zone::Lock lock(zone.areaMutex); for(const auto & t : rmgObject.getArea().getBorderOutside()) { - if(map.isOnMap(t)) + if (t.y > rmgObject.getVisitablePosition().y) //Line below the town { - map.setOccupied(t, ETileType::FREE); - zone.areaPossible().erase(t); - zone.freePaths().add(t); + if (map.isOnMap(t)) + { + map.setOccupied(t, ETileType::FREE); + zone.areaPossible().erase(t); + zone.freePaths().add(t); + } } } } diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 4ac80b42e..0e6b3c9d8 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -562,7 +562,7 @@ std::vector TreasurePlacer::prepareTreasurePile(const CTreasureInfo bool hasLargeObject = false; while(currentValue <= static_cast(desiredValue) - 100) //no objects with value below 100 are available { - auto * oi = getRandomObject(desiredValue, currentValue, maxValue, !hasLargeObject); + auto * oi = getRandomObject(desiredValue, currentValue, !hasLargeObject); if(!oi) //fail break; @@ -659,13 +659,13 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector return rmgObject; } -ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects) +ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValue, bool allowLargeObjects) { std::vector> thresholds; //handle complex object via pointer ui32 total = 0; //calculate actual treasure value range based on remaining value - ui32 maxVal = maxValue - currentValue; + ui32 maxVal = desiredValue - currentValue; ui32 minValue = static_cast(0.25f * (desiredValue - currentValue)); for(ObjectInfo & oi : possibleObjects) //copy constructor turned out to be costly @@ -701,133 +701,165 @@ ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValu } } -void TreasurePlacer::createTreasures(ObjectManager & manager) +void TreasurePlacer::createTreasures(ObjectManager& manager) { const int maxAttempts = 2; - + int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength(); - int monsterStrength = (zone.monsterStrength == EMonsterStrength::ZONE_NONE ? 0 : zone.monsterStrength + mapMonsterStrength - 1); //array index from 0 to 4; pick any correct value for ZONE_NONE, minGuardedValue won't be used in this case anyway + int monsterStrength = (zone.monsterStrength == EMonsterStrength::ZONE_NONE ? 0 : zone.monsterStrength + mapMonsterStrength - 1); //array index from 0 to 4; pick any correct value for ZONE_NONE, minGuardedValue won't be used in this case anyway static int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 }; minGuardedValue = minGuardedValues[monsterStrength]; - - auto valueComparator = [](const CTreasureInfo & lhs, const CTreasureInfo & rhs) -> bool + + auto valueComparator = [](const CTreasureInfo& lhs, const CTreasureInfo& rhs) -> bool { return lhs.max > rhs.max; }; - - auto restoreZoneLimits = [](const std::vector & treasurePile) + + auto restoreZoneLimits = [](const std::vector& treasurePile) { - for(auto * oi : treasurePile) + for (auto* oi : treasurePile) { oi->maxPerZone++; } }; - + //place biggest treasures first at large distance, place smaller ones inbetween auto treasureInfo = zone.getTreasureInfo(); boost::sort(treasureInfo, valueComparator); - + //sort treasures by ascending value so we can stop checking treasures with too high value boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool { return oi1.value < oi2.value; }); - - int totalDensity = 0; - for (auto t : treasureInfo) + + size_t size = 0; { + Zone::Lock lock(zone.areaMutex); + size = zone.getArea().getTiles().size(); + } + + int totalDensity = 0; + + for (auto t = treasureInfo.begin(); t != treasureInfo.end(); t++) + { + std::vector treasures; + //discard objects with too high value to be ever placed vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool { - return oi.value > t.max; + return oi.value > t->max; }); - - totalDensity += t.density; - - //treasure density is inversely proportional to zone size but must be scaled back to map size - //also, normalize it to zone count - higher count means relatively smaller zones + + totalDensity += t->density; + + size_t count = size * t->density / 500; + + //Assure space for lesser treasures, if there are any left + 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))); + } + } //this is squared distance for optimization purposes - const float minDistance = std::max((125.f / totalDensity), 2.0f); - //distance lower than 2 causes objects to overlap and crash - - for(int attempt = 0; attempt <= maxAttempts;) - { - auto treasurePileInfos = prepareTreasurePile(t); - if(treasurePileInfos.empty()) - { - ++attempt; - continue; - } - - int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo * oi){return v + oi->value;}); - - auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts); - if(rmgObject.instances().empty()) //handle incorrect placement - { - restoreZoneLimits(treasurePileInfos); - continue; - } - - //guard treasure pile - bool guarded = isGuardNeededForTreasure(value); - if(guarded) - guarded = manager.addGuard(rmgObject, value); - - Zone::Lock lock(zone.areaMutex); //We are going to subtract this area - //TODO: Don't place - auto possibleArea = zone.areaPossible(); - - auto path = rmg::Path::invalid(); - if(guarded) - { - path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3 & tile) - { - auto ti = map.getTileInfo(tile); - if(ti.getNearestObjectDistance() < minDistance) - return -1.f; + const float minDistance = std::max((125.f / totalDensity), 1.0f); - 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 + size_t emergencyLoopFinish = 0; + while(treasures.size() < count && emergencyLoopFinish < count) + { + auto treasurePileInfos = prepareTreasurePile(*t); + if (treasurePileInfos.empty()) { - path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE); + emergencyLoopFinish++; //Exit potentially infinite loop for bad settings + continue; } - - if(path.valid()) + + int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; }); + + for (ui32 attempt = 0; attempt <= 2; attempt++) { - //debug purposes - treasureArea.unite(rmgObject.getArea()); - if(guarded) + auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts); + + if (rmgObject.instances().empty()) //handle incorrect placement { - guards.unite(rmgObject.instances().back()->getBlockedArea()); - auto guardedArea = rmgObject.instances().back()->getAccessibleArea(); - auto areaToBlock = rmgObject.getAccessibleArea(true); - areaToBlock.subtract(guardedArea); - treasureBlockArea.unite(areaToBlock); + restoreZoneLimits(treasurePileInfos); + continue; } - zone.connectPath(path); - manager.placeObject(rmgObject, guarded, true); - attempt = 0; + + //guard treasure pile + bool guarded = isGuardNeededForTreasure(value); + if (guarded) + guarded = manager.addGuard(rmgObject, value); + + treasures.push_back(rmgObject); + break; } - else + } + + for (auto& rmgObject : treasures) + { + const bool guarded = rmgObject.isGuarded(); + + for (int attempt = 0; attempt <= maxAttempts;) { - restoreZoneLimits(treasurePileInfos); - rmgObject.clear(); - ++attempt; + auto path = rmg::Path::invalid(); + + Zone::Lock lock(zone.areaMutex); //We are going to subtract this area + auto possibleArea = zone.areaPossible(); + + if (guarded) + { + path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile) + { + auto ti = map.getTileInfo(tile); + if (ti.getNearestObjectDistance() < minDistance) + return -1.f; + + for (const auto& t : rmgObject.getArea().getTilesVector()) + { + if (map.getTileInfo(t).getNearestObjectDistance() < minDistance) + return -1.f; + } + + auto guardedArea = rmgObject.instances().back()->getAccessibleArea(); + auto areaToBlock = rmgObject.getAccessibleArea(true); + areaToBlock.subtract(guardedArea); + if (areaToBlock.overlap(zone.freePaths()) || areaToBlock.overlap(manager.getVisitableArea())) + return -1.f; + + return ti.getNearestObjectDistance(); + }, guarded, false, ObjectManager::OptimizeType::DISTANCE); + } + else + { + path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE); + } + + if (path.valid()) + { + //debug purposes + treasureArea.unite(rmgObject.getArea()); + if (guarded) + { + guards.unite(rmgObject.instances().back()->getBlockedArea()); + auto guardedArea = rmgObject.instances().back()->getAccessibleArea(); + auto areaToBlock = rmgObject.getAccessibleArea(true); + areaToBlock.subtract(guardedArea); + treasureBlockArea.unite(areaToBlock); + } + zone.connectPath(path); + manager.placeObject(rmgObject, guarded, true); + break; + } + else + { + ++attempt; + } } } } diff --git a/lib/rmg/modificators/TreasurePlacer.h b/lib/rmg/modificators/TreasurePlacer.h index edef4581b..822bc793b 100644 --- a/lib/rmg/modificators/TreasurePlacer.h +++ b/lib/rmg/modificators/TreasurePlacer.h @@ -54,7 +54,7 @@ public: protected: bool isGuardNeededForTreasure(int value); - ObjectInfo * getRandomObject(ui32 desiredValue, ui32 currentValue, ui32 maxValue, bool allowLargeObjects); + ObjectInfo * getRandomObject(ui32 desiredValue, ui32 currentValue, bool allowLargeObjects); std::vector prepareTreasurePile(const CTreasureInfo & treasureInfo); rmg::Object constructTreasurePile(const std::vector & treasureInfos, bool densePlacement = false); diff --git a/lib/rmg/modificators/WaterProxy.cpp b/lib/rmg/modificators/WaterProxy.cpp index a99489077..cf4e58684 100644 --- a/lib/rmg/modificators/WaterProxy.cpp +++ b/lib/rmg/modificators/WaterProxy.cpp @@ -83,8 +83,11 @@ void WaterProxy::init() { for(auto & z : map.getZones()) { - dependency(z.second->getModificator()); - dependency(z.second->getModificator()); + if (!zone.isUnderground()) + { + dependency(z.second->getModificator()); + dependency(z.second->getModificator()); + } postfunction(z.second->getModificator()); postfunction(z.second->getModificator()); }