diff --git a/config/objects/generic.json b/config/objects/generic.json index 396ddc374..b3f2e967a 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -10,7 +10,11 @@ } }, "types" : { - "prison" : { "index" : 0, "aiValue" : 5000 } + "prison" : { + "index" : 0, + "aiValue" : 5000, + "removable": true + } } }, @@ -136,6 +140,7 @@ "object" : { "index" : 0, "aiValue" : 10000, + "removable": true, "templates" : { "normal" : { "animation" : "ava0128.def", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] } }, @@ -150,6 +155,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "rmg" : { } } @@ -313,6 +319,7 @@ "object" : { "index" : 0, "aiValue" : 0, + "removable": true, "rmg" : { } } @@ -444,6 +451,7 @@ "object" : { "index" : 0, "aiValue" : 10000, + "removable": true, "rmg" : { } } @@ -495,6 +503,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "rmg" : { "value" : 2000, "rarity" : 150 @@ -511,6 +520,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "rmg" : { "value" : 5000, "rarity" : 150 @@ -527,6 +537,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "rmg" : { "value" : 10000, "rarity" : 150 @@ -543,6 +554,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "rmg" : { "value" : 20000, "rarity" : 150 @@ -572,6 +584,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "templates" : { "normal" : { "animation" : "AVWmon1", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] } } @@ -584,6 +597,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "templates" : { "normal" : { "animation" : "AVWmon2", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] } } @@ -596,6 +610,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "templates" : { "normal" : { "animation" : "AVWmon3", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] } } @@ -608,6 +623,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "templates" : { "normal" : { "animation" : "AVWmon4", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] } } @@ -632,6 +648,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "templates" : { "normal" : { "animation" : "AVWmon6", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] } } @@ -644,6 +661,7 @@ "types" : { "object" : { "index" : 0, + "removable": true, "templates" : { "normal" : { "animation" : "AVWmon7", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] } } @@ -687,6 +705,7 @@ "object" : { "index" : 0, "aiValue" : 0, + "removable": true, "rmg" : { } } @@ -723,6 +742,7 @@ "index" :95, "handler": "generic", "base" : { + "blockVisit": true, "sounds" : { "ambient" : ["LOOPTAV"], "visit" : ["STORE"] diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 2a2b31371..b97606eb9 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -8,6 +8,7 @@ "index" :5, "handler": "artifact", "base" : { + "removable": true, "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] @@ -25,6 +26,7 @@ "handler": "hero", "base" : { "aiValue" : 7500, + "removable": true, "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VVV", "VAV"] @@ -40,6 +42,7 @@ "index" :54, "handler": "monster", "base" : { + "removable": true, "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] @@ -55,6 +58,7 @@ "index" :76, "handler": "randomResource", "base" : { + "removable": true, "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VA" ] @@ -85,6 +89,7 @@ "handler": "resource", "lastReservedIndex" : 6, "base" : { + "removable": true, "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VA" ] @@ -138,6 +143,7 @@ "lastReservedIndex" : 2, "base" : { "aiValue" : 0, + "removable": true, "layer" : "sail", "onboardAssaultAllowed" : true, "onboardVisitAllowed" : true, @@ -175,6 +181,7 @@ "lastReservedIndex" : 7, "base" : { "aiValue" : 0, + "removable": true, "sounds" : { "visit" : ["CAVEHEAD"], "removal" : [ "PICKUP01", "PICKUP02", "PICKUP03", "PICKUP04", "PICKUP05", "PICKUP06", "PICKUP07" ] diff --git a/config/objects/rewardablePickable.json b/config/objects/rewardablePickable.json index 6b01cf3d6..5f6f327de 100644 --- a/config/objects/rewardablePickable.json +++ b/config/objects/rewardablePickable.json @@ -19,6 +19,7 @@ "value" : 2000, "rarity" : 500 }, + "removable": true, "compatibilityIdentifiers" : [ "object" ], "blockedVisitable" : true, @@ -61,6 +62,7 @@ "value" : 2000, "rarity" : 100 }, + "removable": true, "compatibilityIdentifiers" : [ "object" ], "blockedVisitable" : true, @@ -119,6 +121,7 @@ "value" : 1500, "rarity" : 500 }, + "removable": true, "compatibilityIdentifiers" : [ "object" ], "blockedVisitable" : true, @@ -170,6 +173,7 @@ "value" : 1500, "rarity" : 50 }, + "removable": true, "compatibilityIdentifiers" : [ "object" ], "blockedVisitable" : true, @@ -221,6 +225,7 @@ "value" : 1500, "rarity" : 1000 }, + "removable": true, "compatibilityIdentifiers" : [ "object" ], "blockedVisitable" : true, diff --git a/lib/mapObjectConstructors/AObjectTypeHandler.cpp b/lib/mapObjectConstructors/AObjectTypeHandler.cpp index 9766f4123..99af1e610 100644 --- a/lib/mapObjectConstructors/AObjectTypeHandler.cpp +++ b/lib/mapObjectConstructors/AObjectTypeHandler.cpp @@ -96,6 +96,18 @@ void AObjectTypeHandler::init(const JsonNode & input) else aiValue = static_cast>(input["aiValue"].Integer()); + // TODO: Define properties, move them to actual object instance + if(input["blockVisit"].isNull()) + blockVisit = false; + else + blockVisit = input["blockVisit"].Bool(); + + if(input["removable"].isNull()) + removable = false; + else + removable = input["removable"].Bool(); + + battlefield = BattleField::NONE; if(!input["battleground"].isNull()) @@ -120,6 +132,8 @@ void AObjectTypeHandler::preInitObject(CGObjectInstance * obj) const obj->subID = subtype; obj->typeName = typeName; obj->subTypeName = subTypeName; + obj->blockVisit = blockVisit; + obj->removable = removable; } void AObjectTypeHandler::initTypeData(const JsonNode & input) diff --git a/lib/mapObjectConstructors/AObjectTypeHandler.h b/lib/mapObjectConstructors/AObjectTypeHandler.h index 2f5028e57..04e7c2ca5 100644 --- a/lib/mapObjectConstructors/AObjectTypeHandler.h +++ b/lib/mapObjectConstructors/AObjectTypeHandler.h @@ -43,6 +43,9 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable si32 type; si32 subtype; + bool blockVisit; + bool removable; + protected: void preInitObject(CGObjectInstance * obj) const; virtual bool objectFilter(const CGObjectInstance * obj, std::shared_ptr tmpl) const; diff --git a/lib/mapObjects/CGObjectInstance.cpp b/lib/mapObjects/CGObjectInstance.cpp index 167e24c75..b2f7138dd 100644 --- a/lib/mapObjects/CGObjectInstance.cpp +++ b/lib/mapObjects/CGObjectInstance.cpp @@ -33,7 +33,8 @@ CGObjectInstance::CGObjectInstance(): ID(Obj::NO_OBJ), subID(-1), tempOwner(PlayerColor::UNFLAGGABLE), - blockVisit(false) + blockVisit(false), + removable(false) { } @@ -173,12 +174,7 @@ void CGObjectInstance::pickRandomObject(CRandomGenerator & rand) void CGObjectInstance::initObj(CRandomGenerator & rand) { - switch(ID.toEnum()) - { - case Obj::TAVERN: - blockVisit = true; - break; - } + // no-op } void CGObjectInstance::setProperty( ObjProperty what, ObjPropertyID identifier ) @@ -191,6 +187,7 @@ void CGObjectInstance::setProperty( ObjProperty what, ObjPropertyID identifier ) tempOwner = identifier.as(); break; case ObjProperty::BLOCKVIS: + // Never actually used in code, but possible in ERM blockVisit = identifier.getNum(); break; case ObjProperty::ID: @@ -327,9 +324,16 @@ bool CGObjectInstance::isVisitable() const bool CGObjectInstance::isBlockedVisitable() const { + // TODO: Read from json return blockVisit; } +bool CGObjectInstance::isRemovable() const +{ + // TODO: Read from json + return removable; +} + bool CGObjectInstance::isCoastVisitable() const { return false; diff --git a/lib/mapObjects/CGObjectInstance.h b/lib/mapObjects/CGObjectInstance.h index f85a9a550..7292435ba 100644 --- a/lib/mapObjects/CGObjectInstance.h +++ b/lib/mapObjects/CGObjectInstance.h @@ -54,6 +54,7 @@ public: int3 getSightCenter() const; /// If true hero can visit this object only from neighbouring tiles and can't stand on this object bool blockVisit; + bool removable; PlayerColor getOwner() const override { @@ -85,6 +86,9 @@ public: /// If true hero can visit this object only from neighbouring tiles and can't stand on this object virtual bool isBlockedVisitable() const; + // If true, can be possibly removed from the map + virtual bool isRemovable() const; + /// If true this object can be visited by hero standing on the coast virtual bool isCoastVisitable() const; @@ -144,6 +148,7 @@ public: h & id; h & tempOwner; h & blockVisit; + h & removable; h & appearance; //definfo is handled by map serializer } diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index 50b88899d..f826dff51 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -153,6 +153,11 @@ bool Object::Instance::isBlockedVisitable() const return dObject.isBlockedVisitable(); } +bool Object::Instance::isRemovable() const +{ + return dObject.isRemovable(); +} + CGObjectInstance & Object::Instance::object() { return dObject; @@ -269,21 +274,52 @@ const rmg::Area & Object::getBlockVisitableArea() const { if(dInstances.empty()) return dBlockVisitableCache; - for(auto i = dInstances.begin(); i != std::prev(dInstances.end()); ++i) + + for(const auto i : dInstances) { // FIXME: Account for blockvis objects with multiple visitable tiles - if (i->isBlockedVisitable()) - dAccessibleAreaCache.add(i->getVisitablePosition()); + 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 (i.isRemovable()) + dRemovableAreaCache.unite(i.getBlockedArea()); + } + + return dRemovableAreaCache; +} + +const rmg::Area Object::getEntrableArea() const +{ + // Calculate Area that hero can freely pass + + // Do not use blockVisitTiles, unless they belong to removable objects (resources etc.) + // area = accessibleArea - (blockVisitableArea - removableArea) + + rmg::Area entrableArea = getAccessibleArea(); + rmg::Area blockVisitableArea = getBlockVisitableArea(); + blockVisitableArea.subtract(getRemovableArea()); + entrableArea.subtract(blockVisitableArea); + + return entrableArea; +} + 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); dPosition = position; @@ -390,6 +426,7 @@ void Object::clearCachedArea() const dAccessibleAreaCache.clear(); dAccessibleAreaFullCache.clear(); dBlockVisitableCache.clear(); + dRemovableAreaCache.clear(); } void Object::clear() diff --git a/lib/rmg/RmgObject.h b/lib/rmg/RmgObject.h index 5176f1d7f..f5c523dcc 100644 --- a/lib/rmg/RmgObject.h +++ b/lib/rmg/RmgObject.h @@ -36,6 +36,7 @@ public: int3 getVisitablePosition() const; bool isVisitableFrom(const int3 & tile) const; bool isBlockedVisitable() const; + bool isRemovable() const; const Area & getAccessibleArea() const; void setTemplate(TerrainId terrain, CRandomGenerator &); //cache invalidation void setAnyTemplate(CRandomGenerator &); //cache invalidation @@ -73,6 +74,8 @@ public: int3 getVisitablePosition() const; const Area & getAccessibleArea(bool exceptLast = false) const; const Area & getBlockVisitableArea() const; + const Area & getRemovableArea() const; + const Area getEntrableArea() const; const int3 & getPosition() const; void setPosition(const int3 & position); @@ -93,6 +96,7 @@ private: mutable Area dFullAreaCache; mutable Area dAccessibleAreaCache, dAccessibleAreaFullCache; mutable Area dBlockVisitableCache; + mutable Area dRemovableAreaCache; int3 dPosition; ui32 dStrength; bool guarded; diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index 683f9f6e8..93ae31612 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -664,7 +664,19 @@ bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard if(!guard) return false; - rmg::Area visitablePos({object.getVisitablePosition()}); + // Prefer non-blocking tiles, if any + auto entrableTiles = object.getEntrableArea().getTiles(); + int3 entrableTile; + if (entrableTiles.empty()) + { + entrableTile = object.getVisitablePosition(); + } + else + { + *RandomGeneratorUtil::nextItem(entrableTiles, zone.getRand()); + } + + rmg::Area visitablePos({entrableTile}); visitablePos.unite(visitablePos.getBorderOutside()); auto accessibleArea = object.getAccessibleArea(); diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 7027b4f69..84afa71e6 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -625,20 +625,31 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector for(const auto & oi : treasureInfos) { auto blockedArea = rmgObject.getArea(); - auto accessibleArea = rmgObject.getAccessibleArea(); + auto entrableArea = rmgObject.getEntrableArea(); + if(rmgObject.instances().empty()) - accessibleArea.add(int3()); + entrableArea.add(int3()); auto * object = oi->generateObject(); if(oi->templates.empty()) continue; object->appearance = *RandomGeneratorUtil::nextItem(oi->templates, zone.getRand()); + + auto blockingIssue = object->isBlockedVisitable() && !object->isRemovable(); + if (blockingIssue) + { + // 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); + } + auto & instance = rmgObject.addInstance(*object); do { - if(accessibleArea.empty()) + if(entrableArea.empty()) { //fail - fallback rmgObject.clear(); @@ -649,12 +660,14 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector if(densePlacement) { int bestPositionsWeight = std::numeric_limits::max(); - for(const auto & t : accessibleArea.getTilesVector()) + for(const auto & t : entrableArea.getTilesVector()) { instance.setPosition(t); - int w = rmgObject.getAccessibleArea().getTilesVector().size(); - if(w < bestPositionsWeight) + int w = rmgObject.getEntrableArea().getTilesVector().size(); + + if(w && w < bestPositionsWeight) { + // Minimum 1 position must be entrable bestPositions.clear(); bestPositions.push_back(t); bestPositionsWeight = w; @@ -664,10 +677,12 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector bestPositions.push_back(t); } } + } - else + + if (bestPositions.empty()) { - bestPositions = accessibleArea.getTilesVector(); + bestPositions = entrableArea.getTilesVector(); } int3 nextPos = *RandomGeneratorUtil::nextItem(bestPositions, zone.getRand()); @@ -676,7 +691,6 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector auto instanceAccessibleArea = instance.getAccessibleArea(); if(instance.getBlockedArea().getTilesVector().size() == 1) { - //TOOD: Move hardcoded option to template if(instance.object().appearance->isVisitableFromTop() && !instance.object().isBlockedVisitable()) instanceAccessibleArea.add(instance.getVisitablePosition()); } @@ -684,16 +698,12 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector //first object is good if(rmgObject.instances().size() == 1) break; - - // TODO: Do not place blockvis objects so that they block access to existing accessible area - // Either object must be removable, or both must be accessible independently - //condition for good position - if(!blockedArea.overlap(instance.getBlockedArea()) && accessibleArea.overlap(instanceAccessibleArea)) + if(!blockedArea.overlap(instance.getBlockedArea()) && entrableArea.overlap(instanceAccessibleArea)) break; - + //fail - new position - accessibleArea.erase(nextPos); + entrableArea.erase(nextPos); } while(true); } return rmgObject;