diff --git a/AI/VCAI/CMakeLists.txt b/AI/VCAI/CMakeLists.txt index 8d2973275..a4fb5e9a3 100644 --- a/AI/VCAI/CMakeLists.txt +++ b/AI/VCAI/CMakeLists.txt @@ -11,6 +11,7 @@ set(VCAI_SRCS AIUtility.cpp AIhelper.cpp ResourceManager.cpp + MapObjectsEvaluator.cpp Fuzzy.cpp Goals.cpp main.cpp @@ -23,6 +24,7 @@ set(VCAI_HEADERS AIUtility.h AIhelper.h ResourceManager.h + MapObjectsEvaluator.h Fuzzy.h Goals.h VCAI.h diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index c1b4af713..3db117451 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -19,6 +19,7 @@ #include "../../lib/VCMI_Lib.h" #include "../../CCallback.h" #include "VCAI.h" +#include "MapObjectsEvaluator.h" #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us @@ -97,6 +98,8 @@ FuzzyHelper::FuzzyHelper() ta.configure(); initVisitTile(); vt.configure(); + initWanderTarget(); + wanderTarget.configure(); } @@ -199,6 +202,20 @@ void FuzzyHelper::initTacticalAdvantage() } } +float FuzzyHelper::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const +{ + float turns = 0.0f; + float distance = CPathfinderHelper::getMovementCost(h, tile); + if(distance) + { + if(distance < h->movement) //we can move there within one turn + turns = (fl::scalar)distance / h->movement; + else + turns = 1 + (fl::scalar)(distance - h->movement) / h->maxMovePoints(true); //bool on land? + } + return turns; +} + ui64 FuzzyHelper::estimateBankDanger(const CBank * bank) { //this one is not fuzzy anymore, just calculate weighted average @@ -272,6 +289,38 @@ float FuzzyHelper::getTacticalAdvantage(const CArmedInstance * we, const CArmedI return output; } +float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj) +{ + float distFromObject = calculateTurnDistanceInputValue(&h, obj->pos); + boost::optional objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID); + int objValue = 0; + + if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map + { + objValue = std::min(std::max(objValueKnownByAI.get(), 0), 20000); + } + else + { + MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0); + logGlobal->warn("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue)); + } + + float output = -1.0f; + try + { + wanderTarget.distance->setValue(distFromObject); + wanderTarget.objectValue->setValue(objValue); + wanderTarget.engine.process(); + output = wanderTarget.visitGain->getValue(); + } + catch (fl::Exception & fe) + { + logAi->error("evaluate getWanderTargetObjectValue: %s", fe.getWhat()); + } + assert(output >= 0.0f); + return output; +} + FuzzyHelper::TacticalAdvantage::~TacticalAdvantage() { //TODO: smart pointers? @@ -413,6 +462,55 @@ void FuzzyHelper::initVisitTile() } } +void FuzzyHelper::initWanderTarget() +{ + try + { + wanderTarget.distance = new fl::InputVariable("distance"); //distance on map from object + wanderTarget.objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI + wanderTarget.visitGain = new fl::OutputVariable("visitGain"); + wanderTarget.visitGain->setMinimum(0); + wanderTarget.visitGain->setMaximum(10); + + wanderTarget.engine.addInputVariable(wanderTarget.distance); + wanderTarget.engine.addInputVariable(wanderTarget.objectValue); + wanderTarget.engine.addOutputVariable(wanderTarget.visitGain); + + //for now distance variable same as in as VisitTile + wanderTarget.distance->addTerm(new fl::Ramp("SHORT", 0.5, 0)); + wanderTarget.distance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8)); + wanderTarget.distance->addTerm(new fl::Ramp("LONG", 0.5, 3)); + wanderTarget.distance->setRange(0, 3.0); + + //objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges + wanderTarget.objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms + wanderTarget.objectValue->addTerm(new fl::Triangle("MEDIUM", 2500, 6000)); + wanderTarget.objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000)); + wanderTarget.objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down. + + wanderTarget.visitGain->addTerm(new fl::Ramp("LOW", 5, 0)); + wanderTarget.visitGain->addTerm(new fl::Triangle("MEDIUM", 4, 6)); + wanderTarget.visitGain->addTerm(new fl::Ramp("HIGH", 5, 10)); + wanderTarget.visitGain->setRange(0, 10); + + wanderTarget.addRule("if distance is LONG and objectValue is HIGH then visitGain is MEDIUM"); + wanderTarget.addRule("if distance is MEDIUM and objectValue is HIGH then visitGain is somewhat HIGH"); + wanderTarget.addRule("if distance is SHORT and objectValue is HIGH then visitGain is HIGH"); + + wanderTarget.addRule("if distance is LONG and objectValue is MEDIUM then visitGain is somewhat LOW"); + wanderTarget.addRule("if distance is MEDIUM and objectValue is MEDIUM then visitGain is MEDIUM"); + wanderTarget.addRule("if distance is SHORT and objectValue is MEDIUM then visitGain is somewhat HIGH"); + + wanderTarget.addRule("if distance is LONG and objectValue is LOW then visitGain is very LOW"); + wanderTarget.addRule("if distance is MEDIUM and objectValue is LOW then visitGain is LOW"); + wanderTarget.addRule("if distance is SHORT and objectValue is LOW then visitGain is MEDIUM"); + } + catch(fl::Exception & fe) + { + logAi->error("FindWanderTarget: %s", fe.getWhat()); + } +} + float FuzzyHelper::evaluate(Goals::VisitTile & g) { //we assume that hero is already set and we want to choose most suitable one for the mission @@ -420,20 +518,7 @@ float FuzzyHelper::evaluate(Goals::VisitTile & g) return 0; //assert(cb->isInTheMap(g.tile)); - float turns = 0; - float distance = CPathfinderHelper::getMovementCost(g.hero.h, g.tile); - if(!distance) //we stand on that tile - { - turns = 0; - } - else - { - if(distance < g.hero->movement) //we can move there within one turn - turns = (fl::scalar)distance / g.hero->movement; - else - turns = 1 + (fl::scalar)(distance - g.hero->movement) / g.hero->maxMovePoints(true); //bool on land? - } - + float turns = calculateTurnDistanceInputValue(g.hero.h, g.tile); float missionImportance = 0; if(vstd::contains(ai->lockedHeroes, g.hero)) missionImportance = ai->lockedHeroes[g.hero]->priority; @@ -549,3 +634,10 @@ void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pa { g->setpriority(g->accept(this)); //this enforces returned value is set } + +FuzzyHelper::EvalWanderTargetObject::~EvalWanderTargetObject() +{ + delete distance; + delete objectValue; + delete visitGain; +} diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 1879e929b..a7709ccc2 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -55,7 +55,17 @@ class FuzzyHelper ~EvalVisitTile(); } vt; - + class EvalWanderTargetObject : public engineBase //designed for use with VCAI::wander() + { + public: + fl::InputVariable * distance; + fl::InputVariable * objectValue; + fl::OutputVariable * visitGain; + ~EvalWanderTargetObject(); + } wanderTarget; + +private: + float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const; public: enum RuleBlocks {BANK_DANGER, TACTICAL_ADVANTAGE, VISIT_TILE}; @@ -64,6 +74,7 @@ public: FuzzyHelper(); void initTacticalAdvantage(); void initVisitTile(); + void initWanderTarget(); float evaluate(Goals::Explore & g); float evaluate(Goals::RecruitHero & g); @@ -82,6 +93,7 @@ public: ui64 estimateBankDanger(const CBank * bank); float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us + float getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj); Goals::TSubgoal chooseSolution(Goals::TGoalVec vec); //std::shared_ptr chooseSolution (std::vector> & vec); diff --git a/AI/VCAI/MapObjectsEvaluator.cpp b/AI/VCAI/MapObjectsEvaluator.cpp new file mode 100644 index 000000000..7783f68dd --- /dev/null +++ b/AI/VCAI/MapObjectsEvaluator.cpp @@ -0,0 +1,63 @@ +#include "StdInc.h" +#include "MapObjectsEvaluator.h" +#include "../../lib/GameConstants.h" +#include "../../lib/VCMI_Lib.h" + +MapObjectsEvaluator & MapObjectsEvaluator::getInstance() +{ + static std::unique_ptr singletonInstance; + if(singletonInstance == nullptr) + singletonInstance.reset(new MapObjectsEvaluator()); + + return *(singletonInstance.get()); +} + +MapObjectsEvaluator::MapObjectsEvaluator() +{ + for(auto primaryID : VLC->objtypeh->knownObjects()) + { + for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID)) + { + auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID); + if(!handler->isStaticObject()) + { + if(handler->getAiValue() != boost::none) + { + objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = handler->getAiValue().get(); + } + else if(VLC->objtypeh->getObjGroupAiValue(primaryID) != boost::none) //if value is not initialized - fallback to default value for this object family if it exists + { + objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = VLC->objtypeh->getObjGroupAiValue(primaryID).get(); + } + else + { + objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; //some default handling when aiValue not found + } + } + } + } +} + +boost::optional MapObjectsEvaluator::getObjectValue(int primaryID, int secondaryID) const +{ + CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID); + auto object = objectDatabase.find(internalIdentifier); + if(object != objectDatabase.end()) + return object->second; + + logGlobal->trace("Unknown object for AI, ID: " + std::to_string(primaryID) + ", SubID: " + std::to_string(secondaryID)); + return boost::optional(); +} + +void MapObjectsEvaluator::addObjectData(int primaryID, int secondaryID, int value) //by current design it updates value if already in AI database +{ + CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID); + objectDatabase[internalIdentifier] = value; +} + +void MapObjectsEvaluator::removeObjectData(int primaryID, int secondaryID) +{ + CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID); + vstd::erase_if_present(objectDatabase, internalIdentifier); +} + diff --git a/AI/VCAI/MapObjectsEvaluator.h b/AI/VCAI/MapObjectsEvaluator.h new file mode 100644 index 000000000..bdee725b6 --- /dev/null +++ b/AI/VCAI/MapObjectsEvaluator.h @@ -0,0 +1,25 @@ +/* +* MapObjectsEvaluator.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once +#include "../../lib/mapObjects/CObjectClassesHandler.h" + +class MapObjectsEvaluator +{ +private: + std::map objectDatabase; //value for each object type + +public: + MapObjectsEvaluator(); + static MapObjectsEvaluator & getInstance(); + boost::optional getObjectValue(int primaryID, int secondaryID) const; + void addObjectData(int primaryID, int secondaryID, int value); + void removeObjectData(int primaryID, int secondaryID); +}; + diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index b005d7f86..9687f3922 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1592,7 +1592,12 @@ void VCAI::wander(HeroPtr h) if(dests.size()) //performance improvement { - const ObjectIdRef & dest = *boost::min_element(dests, CDistanceSorter(h.get())); //find next closest one + auto fuzzyLogicSorter = [h](const ObjectIdRef & l, const ObjectIdRef & r) -> bool //TODO: create elementar GetObj goal usable for goal decomposition and Wander based on VisitTile logic and object value on top of it + { + return fh->getWanderTargetObjectValue( *h.get(), l) < fh->getWanderTargetObjectValue(*h.get(), r); + }; + + const ObjectIdRef & dest = *boost::max_element(dests, fuzzyLogicSorter); //find best object to visit based on fuzzy logic evaluation, TODO: use elementar version of GetObj here in future //wander should not cause heroes to be reserved - they are always considered free logAi->debug("Of all %d destinations, object oid=%d seems nice", dests.size(), dest.id.getNum()); diff --git a/AI/VCAI/VCAI.vcxproj b/AI/VCAI/VCAI.vcxproj index 2069fb806..72b355308 100644 --- a/AI/VCAI/VCAI.vcxproj +++ b/AI/VCAI/VCAI.vcxproj @@ -141,6 +141,7 @@ + Create Create @@ -153,6 +154,7 @@ + diff --git a/config/objects/creatureBanks.json b/config/objects/creatureBanks.json index 18aeec4c3..2a841cd6d 100644 --- a/config/objects/creatureBanks.json +++ b/config/objects/creatureBanks.json @@ -12,6 +12,7 @@ { "index" : 0, "name" : "Cyclops Stockpile", + "aiValue" : 3000, "sounds" : { "ambient" : ["LOOPCAVE"] }, @@ -120,6 +121,7 @@ "index" : 1, "resetDuration" : 0, "name" : "Dwarven Treasury", + "aiValue" : 2000, "sounds" : { "ambient" : ["LOOPDWAR"] }, @@ -210,6 +212,7 @@ "index" : 2, "resetDuration" : 0, "name" : "Griffin Conservatory", + "aiValue" : 9000, "sounds" : { "ambient" : ["LOOPGRIF"] }, @@ -284,6 +287,7 @@ "index" : 3, "resetDuration" : 0, "name" : "Imp Cache", + "aiValue" : 1500, "sounds" : { "ambient" : ["LOOPFIRE"] }, @@ -373,6 +377,7 @@ "index" : 4, "resetDuration" : 0, "name" : "Medusa Stores", + "aiValue" : 1500, "sounds" : { "ambient" : ["LOOPMEDU"] }, @@ -463,6 +468,7 @@ "index" : 5, "resetDuration" : 0, "name" : "Naga Bank", + "aiValue" : 3000, "sounds" : { "ambient" : ["LOOPNAGA"] }, @@ -553,6 +559,7 @@ "index" : 6, "resetDuration" : 0, "name" : "Dragon Fly Hive", + "aiValue" : 9000, "sounds" : { "ambient" : ["LOOPLEAR"] }, @@ -633,6 +640,7 @@ "index" : 0, "resetDuration" : 0, "name" : "Shipwreck", + "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 100 @@ -724,6 +732,7 @@ "index" : 0, "resetDuration" : 0, "name" : "Derelict Ship", + "aiValue" : 4000, "rmg" : { "value" : 4000, "rarity" : 20 @@ -822,6 +831,7 @@ "index" : 0, "resetDuration" : 0, "name" : "Crypt", + "aiValue" : 1500, "rmg" : { "value" : 1000, "rarity" : 100 @@ -917,6 +927,7 @@ "index" : 0, "resetDuration" : 0, "name" : "Dragon Utopia", + "aiValue" : 11000, "rmg" : { "value" : 10000, "rarity" : 100 @@ -1031,6 +1042,7 @@ "index" : 0, "resetDuration" : 0, "name" : "Pyramid", + "aiValue" : 8000, "rmg" : { "value" : 5000, "rarity" : 20 diff --git a/config/objects/generic.json b/config/objects/generic.json index 76fb27a71..7b5ee3c0c 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -10,7 +10,7 @@ } }, "types" : { - "prison" : { "index" : 0 } + "prison" : { "index" : 0, "aiValue" : 5000 } } }, @@ -25,6 +25,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -45,6 +46,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -65,6 +67,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -79,6 +82,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -100,6 +104,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 8000, "rmg" : { "value" : 8000, "rarity" : 20 @@ -120,6 +125,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 10000, "templates" : { "normal" : { "animation" : "ava0128.def", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] } }, @@ -151,6 +157,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 750, "templates" : { "base" : { "animation" : "avxredw.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "VV", "VA"], "allowedTerrains":["grass", "swamp", "dirt", "sand", "lava", "rough"] }, @@ -176,6 +183,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 750, "rmg" : { "zoneLimit" : 1, "value" : 750, @@ -195,6 +203,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { } } @@ -246,6 +255,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 2000, "rmg" : { "value" : 5000, "rarity" : 20 @@ -265,6 +275,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "zoneLimit" : 1, "value" : 1500, @@ -285,6 +296,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 500, "rmg" : { "value" : 500, "rarity" : 100 @@ -304,6 +316,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 100 @@ -323,6 +336,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 3000, "rmg" : { "value" : 3000, "rarity" : 100 @@ -344,6 +358,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 0, "rmg" : { } } @@ -360,6 +375,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 750, "rmg" : { } } @@ -376,6 +392,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 500, "rmg" : { } } @@ -392,6 +409,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 350, "rmg" : { "mapLimit" : 48, "value" : 3500, @@ -411,6 +429,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 0, "rmg" : { } } @@ -428,6 +447,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 100 @@ -447,6 +467,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1000, "rmg" : { } } @@ -463,6 +484,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 0, "rmg" : { } } @@ -480,6 +502,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "mapLimit" : 32, "value" : 100, @@ -500,6 +523,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "templates" : { "green" : { "animation" : "avxdent.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "BA"], "allowedTerrains":["grass", "swamp", "dirt"] }, @@ -524,6 +548,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 2500, "rmg" : { "value" : 2500, "rarity" : 20 @@ -542,6 +567,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "zoneLimit" : 3, "value" : 1500, @@ -565,6 +591,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 10000, "rmg" : { } } @@ -581,6 +608,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 250, "rmg" : { "zoneLimit" : 1, "value" : 250, @@ -819,6 +847,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 7000, "rmg" : { "zoneLimit" : 1, "value" : 7000, @@ -833,6 +862,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 0, "rmg" : { } } @@ -850,6 +880,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -870,6 +901,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 458816934..3b8b6aa7a 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -23,6 +23,7 @@ "hero" : { "index" :34, "handler": "hero", + "defaultAiValue" : 5000, "base" : { "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], @@ -63,14 +64,14 @@ } }, "types" : { - "wood" : { "index" : 0, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTwood0.def" } } }, - "mercury" : { "index" : 1, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } }, - "ore" : { "index" : 2, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTore0.def" } } }, - "sulfur" : { "index" : 3, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } }, - "crystal" : { "index" : 4, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } }, - "gems" : { "index" : 5, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgems0.def" } } }, - "gold" : { "index" : 6, "rmg" : { "value" : 750, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } }, - "mithril" : { "index" : 7 } // TODO: move to WoG? + "wood" : { "index" : 0, "aiValue" : 1400, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTwood0.def" } } }, + "mercury" : { "index" : 1, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } }, + "ore" : { "index" : 2, "aiValue" : 1400, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTore0.def" } } }, + "sulfur" : { "index" : 3, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } }, + "crystal" : { "index" : 4, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } }, + "gems" : { "index" : 5, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgems0.def" } } }, + "gold" : { "index" : 6, "aiValue" : 750, "rmg" : { "value" : 750, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } }, + "mithril" : { "index" : 7, "aiValue" : 3500 } // TODO: move to WoG? } }, @@ -78,6 +79,7 @@ "town" : { "index" :98, "handler": "town", + "defaultAiValue" : 20000, "base" : { "filters" : { // village image - fort not present @@ -107,6 +109,7 @@ "boat" : { "index" :8, "handler": "boat", + "defaultAiValue" : 0, "base" : { "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], @@ -124,6 +127,7 @@ "borderGuard" : { "index" :9, "handler": "borderGuard", + "defaultAiValue" : 0, "base" : { "sounds" : { "visit" : ["CAVEHEAD"], @@ -144,6 +148,7 @@ "borderGate" : { "index" :212, "handler": "borderGate", + "defaultAiValue" : 0, "base" : { "sounds" : { "visit" : ["CAVEHEAD"] @@ -163,6 +168,7 @@ "keymasterTent" : { "index" :10, "handler": "keymaster", + "defaultAiValue" : 10000, "base" : { "sounds" : { "visit" : ["CAVEHEAD"] @@ -183,6 +189,7 @@ "seerHut" : { "index" :83, "handler": "seerHut", + "defaultAiValue" : 10000, "base" : { "base" : { "visitableFrom" : [ "---", "+++", "+++" ], @@ -209,9 +216,9 @@ } }, "types" : { - "water" : { "index" : 0, "rmg" : { "zoneLimit" : 1, "value" : 5000, "rarity" : 20 } }, - "land" : { "index" : 1, "rmg" : { "zoneLimit" : 1, "value" : 10000, "rarity" : 20 } }, - "subterra" : { "index" : 2, "rmg" : { "zoneLimit" : 1, "value" : 7500, "rarity" : 20 } } + "water" : { "index" : 0, "aiValue" : 5000, "rmg" : { "zoneLimit" : 1, "value" : 5000, "rarity" : 20 } }, + "land" : { "index" : 1, "aiValue": 10000, "rmg" : { "zoneLimit" : 1, "value" : 10000, "rarity" : 20 } }, + "subterra" : { "index" : 2, "aiValue" : 7500, "rmg" : { "zoneLimit" : 1, "value" : 7500, "rarity" : 20 } } } }, @@ -227,6 +234,7 @@ "types" : { "sawmill" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500 }, @@ -236,6 +244,7 @@ }, "alchemistLab" : { "index" : 1, + "aiValue" : 3500, "rmg" : { "value" : 3500 }, @@ -245,6 +254,7 @@ }, "orePit" : { "index" : 2, + "aiValue" : 1500, "rmg" : { "value" : 1500 }, @@ -254,6 +264,7 @@ }, "sulfurDune" : { "index" : 3, + "aiValue" : 3500, "rmg" : { "value" : 3500 }, @@ -263,6 +274,7 @@ }, "crystalCavern" : { "index" : 4, + "aiValue" : 3500, "rmg" : { "value" : 3500 }, @@ -272,6 +284,7 @@ }, "gemPond" : { "index" : 5, + "aiValue" : 3500, "rmg" : { "value" : 3500 }, @@ -281,6 +294,7 @@ }, "goldMine" : { "index" : 6, + "aiValue" : 7000, "rmg" : { "value" : 7000 }, @@ -290,6 +304,7 @@ }, "abandoned" : { "index" : 7, + "aiValue" : 3500, "sounds" : { "ambient" : ["LOOPCAVE"], "visit" : ["MYSTERY"] @@ -300,6 +315,7 @@ "abandonedMine" : { "index" :220, "handler": "mine", + "defaultAiValue" : 3500, "base" : { "sounds" : { "ambient" : ["LOOPCAVE"] @@ -313,6 +329,7 @@ "garrisonHorizontal": { "index" :33, "handler": "garrison", + "defaultAiValue" : 0, "base" : { "sounds" : { "visit" : ["MILITARY"] @@ -336,6 +353,7 @@ "garrisonVertical" : { "index" :219, "handler": "garrison", + "defaultAiValue" : 0, "base" : { "sounds" : { "visit" : ["MILITARY"] @@ -454,6 +472,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 500, "templates" : { "normal" : { "visitableFrom" : [ "+++", "+-+", "+++" ], diff --git a/config/objects/rewardable.json b/config/objects/rewardable.json index e173f3704..95399f840 100644 --- a/config/objects/rewardable.json +++ b/config/objects/rewardable.json @@ -12,7 +12,8 @@ }, "types" : { "object" : { - "index" : 0//, + "index" : 0, + "aiValue" : 500//, //"rmg" : { // "zoneLimit" : 1, // "value" : 500, @@ -35,6 +36,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 500, "rmg" : { "value" : 500, "rarity" : 50 @@ -54,6 +56,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 80 @@ -73,6 +76,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 750, "rmg" : { "value" : 750, "rarity" : 50 @@ -92,6 +96,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 500, "rmg" : { "value" : 500, "rarity" : 100 @@ -110,6 +115,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 500, "rmg" : { "value" : 500, "rarity" : 100 @@ -128,6 +134,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 500, "rmg" : { "value" : 500, "rarity" : 50 @@ -146,6 +153,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 6000, "rmg" : { "value" : 6000, "rarity" : 20 @@ -167,6 +175,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 500 @@ -186,6 +195,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 100 @@ -205,6 +215,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 500 @@ -224,6 +235,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 50 @@ -243,6 +255,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 1000 @@ -263,6 +276,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 3000, "rmg" : { "value" : 3000, "rarity" : 50 @@ -282,6 +296,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 100 @@ -301,6 +316,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 100 @@ -319,6 +335,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 12000, "rmg" : { "value" : 12000, "rarity" : 20 @@ -338,6 +355,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 100 @@ -357,6 +375,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 100 @@ -375,6 +394,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 2500, "rmg" : { "mapLimit" : 100, "value" : 2500, @@ -395,6 +415,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1000, "rmg" : { "value" : 1000, "rarity" : 50 @@ -414,6 +435,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1000, "rmg" : { "value" : 1000, "rarity" : 50 @@ -433,6 +455,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 1500, "rmg" : { "value" : 1500, "rarity" : 200 @@ -454,6 +477,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "value" : 100, "rarity" : 100 @@ -472,6 +496,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -492,6 +517,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -512,6 +538,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -532,6 +559,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -551,6 +579,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -573,6 +602,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "value" : 100, "rarity" : 20 @@ -591,6 +621,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -611,6 +642,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 200, "rmg" : { "zoneLimit" : 1, "value" : 200, @@ -631,6 +663,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -651,6 +684,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 100, "rmg" : { "zoneLimit" : 1, "value" : 100, @@ -670,6 +704,7 @@ "types" : { "object" : { "index" : 0, + "aiValue" : 500, "rmg" : { "zoneLimit" : 1, "value" : 500, diff --git a/config/schemas/object.json b/config/schemas/object.json index 09cd96feb..d5c2c9b78 100644 --- a/config/schemas/object.json +++ b/config/schemas/object.json @@ -13,6 +13,9 @@ "name": { "type":"string", }, + "defaultAiValue": { + "type":"number", + }, "handler": { "type":"string", diff --git a/config/schemas/objectType.json b/config/schemas/objectType.json index 076fca19b..52797c95b 100644 --- a/config/schemas/objectType.json +++ b/config/schemas/objectType.json @@ -13,6 +13,9 @@ "name": { "type":"string", }, + "aiValue": { + "type":"number", + }, "sounds": { "type":"object", diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 8f43a808f..fce778e8a 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -206,6 +206,11 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co obj->handlerName = json["handler"].String(); obj->base = json["base"]; obj->id = selectNextID(json["index"], objects, 256); + if(json["defaultAiValue"].isNull()) + obj->groupDefaultAiValue = boost::none; + else + obj->groupDefaultAiValue = json["defaultAiValue"].Integer(); + for (auto entry : json["types"].Struct()) { loadObjectEntry(entry.first, entry.second, obj); @@ -285,6 +290,11 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string type, std::s throw std::runtime_error("Object type handler not found"); } +TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID compoundIdentifier) const +{ + return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID); +} + std::set CObjectClassesHandler::knownObjects() const { std::set ret; @@ -382,6 +392,11 @@ std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const return objects.at(type)->handlerName; } +boost::optional CObjectClassesHandler::getObjGroupAiValue(si32 primaryID) const +{ + return objects.at(primaryID)->groupDefaultAiValue; +} + AObjectTypeHandler::AObjectTypeHandler(): type(-1), subtype(-1) { @@ -452,6 +467,11 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional AObjectTypeHandler::getAiValue() const +{ + return aiValue; +} + bool AObjectTypeHandler::isStaticObject() { return false; // most of classes are not static diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index c94732f2b..9ea070f47 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -65,6 +65,27 @@ struct DLL_LINKAGE RandomMapInfo } }; +struct DLL_LINKAGE CompoundMapObjectID +{ + si32 primaryID; + si32 secondaryID; + + CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {}; + + bool operator<(const CompoundMapObjectID& other) const + { + if(this->primaryID != other.primaryID) + return this->primaryID < other.primaryID; + else + return this->secondaryID < other.secondaryID; + } + + bool operator==(const CompoundMapObjectID& other) const + { + return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID); + } +}; + class DLL_LINKAGE IObjectInfo { public: @@ -125,6 +146,8 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable std::vector templates; SObjectSounds sounds; + + boost::optional aiValue; protected: void preInitObject(CGObjectInstance * obj) const; virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; @@ -163,6 +186,8 @@ public: const RandomMapInfo & getRMGInfo(); + boost::optional getAiValue() const; + virtual bool isStaticObject(); virtual void afterLoadFinalization(); @@ -194,6 +219,10 @@ public: { h & sounds; } + if(version >= 789) + { + h & aiValue; + } } }; @@ -216,6 +245,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase SObjectSounds sounds; + boost::optional groupDefaultAiValue; + template void serialize(Handler &h, const int version) { h & name; @@ -231,6 +262,10 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase { h & sounds; } + if(version >= 789) + { + h & groupDefaultAiValue; + } } }; @@ -274,6 +309,7 @@ public: /// returns handler for specified object (ID-based). ObjectHandler keeps ownership TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const; TObjectTypeHandler getHandlerFor(std::string type, std::string subtype) const; + TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const; std::string getObjectName(si32 type) const; std::string getObjectName(si32 type, si32 subtype) const; @@ -284,7 +320,7 @@ public: /// Returns handler string describing the handler (for use in client) std::string getObjectHandlerName(si32 type) const; - + boost::optional getObjGroupAiValue(si32 primaryID) const; //default AI value of objects belonging to particular primaryID template void serialize(Handler &h, const int version) { diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 8c1e1e5b0..436a124ca 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -12,7 +12,7 @@ #include "../ConstTransitivePtr.h" #include "../GameConstants.h" -const ui32 SERIALIZATION_VERSION = 788; +const ui32 SERIALIZATION_VERSION = 789; const ui32 MINIMAL_SERIALIZATION_VERSION = 753; const std::string SAVEGAME_MAGIC = "VCMISVG";