diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index 34c5d2a08..eb218de88 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -10,20 +10,16 @@ #include "StdInc.h" #include "AIUtility.h" #include "VCAI.h" -#include "FuzzyHelper.h" #include "Goals/Goals.h" #include "../../lib/UnlockGuard.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" -#include "../../lib/mapObjects/CBank.h" -#include "../../lib/mapObjects/CGTownInstance.h" -#include "../../lib/mapObjects/CQuest.h" +#include "../../lib/mapObjects/MapObjects.h" #include "../../lib/mapping/CMapDefines.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; //extern static const int3 dirs[8]; @@ -194,11 +190,6 @@ bool CDistanceSorter::operator()(const CGObjectInstance * lhs, const CGObjectIns return ln->cost < rn->cost; } -bool isSafeToVisit(HeroPtr h, crint3 tile) -{ - return isSafeToVisit(h, fh->evaluateDanger(tile, h.get())); -} - bool isSafeToVisit(HeroPtr h, const CCreatureSet * heroArmy, uint64_t dangerStrength) { const ui64 heroStrength = h->getFightingStrength() * heroArmy->getArmyStrength(); @@ -338,3 +329,26 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2 else return art1->price > art2->price; } + +bool isWeeklyRevisitable(const CGObjectInstance * obj) +{ + //TODO: allow polling of remaining creatures in dwelling + if(dynamic_cast(obj)) // ensures future compatibility, unlike IDs + return true; + if(dynamic_cast(obj)) + return true; + if(dynamic_cast(obj)) //banks tend to respawn often in mods + return true; + + switch(obj->ID) + { + case Obj::STABLES: + case Obj::MAGIC_WELL: + case Obj::HILL_FORT: + return true; + case Obj::BORDER_GATE: + case Obj::BORDERGUARD: + return (dynamic_cast(obj))->wasMyColorVisited(ai->playerID); //FIXME: they could be revisited sooner than in a week + } + return false; +} \ No newline at end of file diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index d8524799e..1fce47eb7 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -173,12 +173,10 @@ bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, Pla bool isBlockVisitObj(const int3 & pos); bool isWeeklyRevisitable(const CGObjectInstance * obj); -bool shouldVisit(HeroPtr h, const CGObjectInstance * obj); bool isObjectRemovable(const CGObjectInstance * obj); //FIXME FIXME: move logic to object property! bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength); bool isSafeToVisit(HeroPtr h, const CCreatureSet *, uint64_t dangerStrength); -bool isSafeToVisit(HeroPtr h, crint3 tile); bool compareHeroStrength(HeroPtr h1, HeroPtr h2); bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2); diff --git a/AI/Nullkiller/AIhelper.cpp b/AI/Nullkiller/AIhelper.cpp deleted file mode 100644 index 6bc52f3ea..000000000 --- a/AI/Nullkiller/AIhelper.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* -* AIhelper.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 -* -*/ -#include "StdInc.h" - -#include "AIhelper.h" - -AIhelper::AIhelper() -{ - pathfindingManager.reset(new PathfindingManager()); - armyManager.reset(new ArmyManager()); - heroManager.reset(new HeroManager()); -} - -AIhelper::~AIhelper() -{ -} - -void AIhelper::init(CPlayerSpecificInfoCallback * CB) -{ - pathfindingManager->init(CB); - armyManager->init(CB); - heroManager->init(CB); -} - -void AIhelper::setAI(VCAI * AI) -{ - pathfindingManager->setAI(AI); - armyManager->setAI(AI); - heroManager->setAI(AI); -} - -void AIhelper::update() -{ - armyManager->update(); - heroManager->update(); -} - -std::vector AIhelper::getPathsToTile(const HeroPtr & hero, const int3 & tile) const -{ - return pathfindingManager->getPathsToTile(hero, tile); -} - -std::vector AIhelper::getPathsToTile(const int3 & tile) const -{ - return pathfindingManager->getPathsToTile(tile); -} - -void AIhelper::updatePaths(std::vector heroes, bool useHeroChain) -{ - pathfindingManager->updatePaths(heroes, useHeroChain); -} - -uint64_t AIhelper::evaluateStackPower(const CCreature * creature, int count) const -{ - return armyManager->evaluateStackPower(creature, count); -} - -SlotInfo AIhelper::getTotalCreaturesAvailable(CreatureID creatureID) const -{ - return armyManager->getTotalCreaturesAvailable(creatureID); -} - -bool AIhelper::canGetArmy(const CArmedInstance * army, const CArmedInstance * source) const -{ - return armyManager->canGetArmy(army, source); -} - -ui64 AIhelper::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDwelling * t) const -{ - return armyManager->howManyReinforcementsCanBuy(h, t); -} - -ui64 AIhelper::howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const -{ - return armyManager->howManyReinforcementsCanGet(target, source); -} - -std::vector AIhelper::getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const -{ - return armyManager->getBestArmy(target, source); -} - -std::vector::iterator AIhelper::getWeakestCreature(std::vector & army) const -{ - return armyManager->getWeakestCreature(army); -} - -std::vector AIhelper::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const -{ - return armyManager->getSortedSlots(target, source); -} - -std::vector AIhelper::getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const -{ - return armyManager->getArmyAvailableToBuy(hero, dwelling); -} - -ArmyUpgradeInfo AIhelper::calculateCreateresUpgrade( - const CCreatureSet * army, - const CGObjectInstance * upgrader, - const TResources & availableResources) const -{ - return armyManager->calculateCreateresUpgrade(army, upgrader, availableResources); -} - -int AIhelper::selectBestSkill(const HeroPtr & hero, const std::vector & skills) const -{ - return heroManager->selectBestSkill(hero, skills); -} - -const std::map & AIhelper::getHeroRoles() const -{ - return heroManager->getHeroRoles(); -} - -HeroRole AIhelper::getHeroRole(const HeroPtr & hero) const -{ - return heroManager->getHeroRole(hero); -} - -float AIhelper::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const -{ - return heroManager->evaluateSecSkill(skill, hero); -} - -float AIhelper::evaluateHero(const CGHeroInstance * hero) const -{ - return heroManager->evaluateHero(hero); -} \ No newline at end of file diff --git a/AI/Nullkiller/AIhelper.h b/AI/Nullkiller/AIhelper.h deleted file mode 100644 index 9e001bd5e..000000000 --- a/AI/Nullkiller/AIhelper.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -* AIhelper.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 - -/* - !!! Note: Include THIS file at the end of include list to avoid "undefined base class" error -*/ - -#include "ArmyManager.h" -#include "HeroManager.h" -#include "Pathfinding/PathfindingManager.h" - -class ResourceManager; -class BuildingManager; - - -//TODO: remove class, put managers to engine -class DLL_EXPORT AIhelper : public IPathfindingManager, public IArmyManager, public IHeroManager -{ - //std::shared_ptr resourceManager; - //std::shared_ptr buildingManager; - std::shared_ptr pathfindingManager; - std::shared_ptr armyManager; - std::shared_ptr heroManager; - //TODO: vector -public: - AIhelper(); - ~AIhelper(); - - std::vector getPathsToTile(const HeroPtr & hero, const int3 & tile) const override; - std::vector getPathsToTile(const int3 & tile) const override; - void updatePaths(std::vector heroes, bool useHeroChain = false) override; - - STRONG_INLINE - bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const - { - return pathfindingManager->isTileAccessible(hero, tile); - } - - bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const override; - ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const override; - ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const override; - std::vector getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const override; - std::vector::iterator getWeakestCreature(std::vector & army) const override; - std::vector getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override; - std::vector getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override; - uint64_t evaluateStackPower(const CCreature * creature, int count) const override; - SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override; - ArmyUpgradeInfo calculateCreateresUpgrade( - const CCreatureSet * army, - const CGObjectInstance * upgrader, - const TResources & availableResources) const override; - - const std::map & getHeroRoles() const override; - HeroRole getHeroRole(const HeroPtr & hero) const override; - int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; - float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override; - float evaluateHero(const CGHeroInstance * hero) const override; - - void update() override; - - void init(CPlayerSpecificInfoCallback * CB) override; - void setAI(VCAI * AI) override; -}; - diff --git a/AI/Nullkiller/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp similarity index 98% rename from AI/Nullkiller/ArmyManager.cpp rename to AI/Nullkiller/Analyzers/ArmyManager.cpp index d40dc2a4d..488ccf13d 100644 --- a/AI/Nullkiller/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "ArmyManager.h" - +#include "../Engine/Nullkiller.h" #include "../../CCallback.h" #include "../../lib/mapObjects/MapObjects.h" @@ -31,16 +31,6 @@ public: } }; -void ArmyManager::init(CPlayerSpecificInfoCallback * CB) -{ - cb = CB; -} - -void ArmyManager::setAI(VCAI * AI) -{ - ai = AI; -} - std::vector ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const { const CCreatureSet * armies[] = { target, source }; diff --git a/AI/Nullkiller/ArmyManager.h b/AI/Nullkiller/Analyzers/ArmyManager.h similarity index 94% rename from AI/Nullkiller/ArmyManager.h rename to AI/Nullkiller/Analyzers/ArmyManager.h index 07d884640..5d3f4b5b2 100644 --- a/AI/Nullkiller/ArmyManager.h +++ b/AI/Nullkiller/Analyzers/ArmyManager.h @@ -40,8 +40,6 @@ struct ArmyUpgradeInfo class DLL_EXPORT IArmyManager //: public: IAbstractManager { public: - virtual void init(CPlayerSpecificInfoCallback * CB) = 0; - virtual void setAI(VCAI * AI) = 0; virtual void update() = 0; virtual bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const = 0; virtual ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const = 0; @@ -64,14 +62,12 @@ class DLL_EXPORT ArmyManager : public IArmyManager { private: CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback - VCAI * ai; + const Nullkiller * ai; std::map totalArmy; public: - void init(CPlayerSpecificInfoCallback * CB) override; - void setAI(VCAI * AI) override; + ArmyManager(CPlayerSpecificInfoCallback * CB, const Nullkiller * ai): cb(CB), ai(ai) {} void update() override; - bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const override; ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const override; ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const override; diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 1d1fdafe9..4585bbd71 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -12,7 +12,6 @@ #include "lib/mapping/CMap.h" //for victory conditions extern boost::thread_specific_ptr cb; -extern boost::thread_specific_ptr ai; void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) { diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 6c6318b4a..b1f21308c 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -10,9 +10,7 @@ #include "StdInc.h" #include "DangerHitMapAnalyzer.h" #include "lib/mapping/CMap.h" //for victory conditions - -extern boost::thread_specific_ptr cb; -extern boost::thread_specific_ptr ai; +#include "../Engine/Nullkiller.h" void DangerHitMapAnalyzer::updateHitMap() { @@ -21,17 +19,18 @@ void DangerHitMapAnalyzer::updateHitMap() upToDate = true; - auto mapSize = cb->getMapSize(); + auto cb = ai->cb.get(); + auto mapSize = ai->cb->getMapSize(); hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]); enemyHeroAccessibleObjects.clear(); - std::map> heroes; + std::map> heroes; - for(const CGObjectInstance * obj : ai->visitableObjs) + for(const CGObjectInstance * obj : ai->memory->visitableObjs) { if(obj->ID == Obj::HERO) { - HeroPtr hero = dynamic_cast(obj); + auto hero = dynamic_cast(obj); heroes[hero->tempOwner].push_back(hero); } @@ -43,11 +42,11 @@ void DangerHitMapAnalyzer::updateHitMap() for(auto pair : heroes) { - ai->ah->updatePaths(pair.second, false); + ai->pathfinder->updatePaths(pair.second, false); foreach_tile_pos([&](const int3 & pos) { - for(AIPath & path : ai->ah->getPathsToTile(pos)) + for(AIPath & path : ai->pathfinder->getPathInfo(pos)) { if(path.getFirstBlockedAction()) continue; diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h index 789677fef..e96fa3c33 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h @@ -10,7 +10,6 @@ #pragma once #include "../VCAI.h" -#include "../AIhelper.h" struct HitMapInfo { @@ -44,8 +43,11 @@ private: boost::multi_array hitMap; std::map> enemyHeroAccessibleObjects; bool upToDate; + const Nullkiller * ai; public: + DangerHitMapAnalyzer(const Nullkiller * ai) :ai(ai) {} + void updateHitMap(); uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) const; const HitMapNode & getObjectTreat(const CGObjectInstance * obj) const; diff --git a/AI/Nullkiller/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp similarity index 94% rename from AI/Nullkiller/HeroManager.cpp rename to AI/Nullkiller/Analyzers/HeroManager.cpp index de70b6fbd..36b08e50c 100644 --- a/AI/Nullkiller/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "HeroManager.h" - +#include "../Engine/Nullkiller.h" #include "../../CCallback.h" #include "../../lib/mapObjects/MapObjects.h" #include "../../lib/CHeroHandler.h" @@ -57,16 +57,6 @@ SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluato std::make_shared() }); -void HeroManager::init(CPlayerSpecificInfoCallback * CB) -{ - cb = CB; -} - -void HeroManager::setAI(VCAI * AI) -{ - ai = AI; -} - float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const { auto role = getHeroRole(hero); @@ -106,11 +96,11 @@ void HeroManager::update() logAi->trace("Start analysing our heroes"); std::map scores; - auto myHeroes = ai->getMyHeroes(); + auto myHeroes = cb->getHeroesInfo(); for(auto & hero : myHeroes) { - scores[hero] = evaluateFightingStrength(hero.get()); + scores[hero] = evaluateFightingStrength(hero); } std::sort(myHeroes.begin(), myHeroes.end(), [&](const HeroPtr & h1, const HeroPtr & h2) -> bool @@ -120,10 +110,10 @@ void HeroManager::update() int mainHeroCount = (myHeroes.size() + 2) / 3; - for(auto & hero : myHeroes) + for(auto hero : myHeroes) { heroRoles[hero] = (mainHeroCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT; - logAi->trace("Hero %s has role %s", hero.name, heroRoles[hero] == HeroRole::MAIN ? "main" : "scout"); + logAi->trace("Hero %s has role %s", hero->name, heroRoles[hero] == HeroRole::MAIN ? "main" : "scout"); } } diff --git a/AI/Nullkiller/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h similarity index 94% rename from AI/Nullkiller/HeroManager.h rename to AI/Nullkiller/Analyzers/HeroManager.h index 5b3bbdece..e2a26c9ef 100644 --- a/AI/Nullkiller/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -21,8 +21,6 @@ class DLL_EXPORT IHeroManager //: public: IAbstractManager { public: - virtual void init(CPlayerSpecificInfoCallback * CB) = 0; - virtual void setAI(VCAI * AI) = 0; virtual const std::map & getHeroRoles() const = 0; virtual int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const = 0; virtual HeroRole getHeroRole(const HeroPtr & hero) const = 0; @@ -55,12 +53,11 @@ private: static SecondarySkillEvaluator scountSkillsScores; CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback - VCAI * ai; + const Nullkiller * ai; std::map heroRoles; public: - void init(CPlayerSpecificInfoCallback * CB) override; - void setAI(VCAI * AI) override; + HeroManager(CPlayerSpecificInfoCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {} const std::map & getHeroRoles() const override; HeroRole getHeroRole(const HeroPtr & hero) const override; int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 5259acc9b..54017d8bf 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -14,9 +14,6 @@ #include "../Engine/Nullkiller.h" #include "lib/mapping/CMap.h" //for victory conditions -extern boost::thread_specific_ptr cb; -extern boost::thread_specific_ptr ai; - void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path, float priority) { auto & info = objects[obj]; @@ -93,12 +90,12 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons { for(auto node = path.nodes.rbegin(); node != path.nodes.rend(); node++) { - auto guardPos = cb->getGuardingCreaturePosition(node->coord); - auto blockers = cb->getVisitableObjs(node->coord); + auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord); + auto blockers = ai->cb->getVisitableObjs(node->coord); if(guardPos.valid()) { - auto guard = cb->getTopObj(cb->getGuardingCreaturePosition(node->coord)); + auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node->coord)); if(guard) { @@ -126,7 +123,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons return nullptr; } -bool shouldVisitObject(const CGObjectInstance * obj) +bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const { if(isObjectRemovable(obj)) { @@ -135,13 +132,13 @@ bool shouldVisitObject(const CGObjectInstance * obj) const int3 pos = obj->visitablePos(); - if(obj->ID != Obj::CREATURE_GENERATOR1 && vstd::contains(ai->alreadyVisited, obj) + if(obj->ID != Obj::CREATURE_GENERATOR1 && vstd::contains(ai->memory->alreadyVisited, obj) || obj->wasVisited(ai->playerID)) { return false; } - auto playerRelations = cb->getPlayerRelations(ai->playerID, obj->tempOwner); + auto playerRelations = ai->cb->getPlayerRelations(ai->playerID, obj->tempOwner); if(playerRelations != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj)) { @@ -151,12 +148,12 @@ bool shouldVisitObject(const CGObjectInstance * obj) //it may be hero visiting this obj //we don't try visiting object on which allied or owned hero stands // -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited - const CGObjectInstance * topObj = cb->getTopObj(pos); + const CGObjectInstance * topObj = ai->cb->getTopObj(pos); if(!topObj) return false; // partly visible obj but its visitable pos is not visible. - if(topObj->ID == Obj::HERO && cb->getPlayerRelations(ai->playerID, topObj->tempOwner) != PlayerRelations::ENEMIES) + if(topObj->ID == Obj::HERO && ai->cb->getPlayerRelations(ai->playerID, topObj->tempOwner) != PlayerRelations::ENEMIES) return false; else return true; //all of the following is met @@ -180,12 +177,12 @@ void ObjectClusterizer::clusterize() logAi->debug("Begin object clusterization"); - for(const CGObjectInstance * obj : ai->visitableObjs) + for(const CGObjectInstance * obj : ai->memory->visitableObjs) { if(!shouldVisitObject(obj)) continue; - auto paths = ai->ah->getPathsToTile(obj->visitablePos()); + auto paths = ai->pathfinder->getPathInfo(obj->visitablePos()); if(paths.empty()) continue; @@ -223,7 +220,7 @@ void ObjectClusterizer::clusterize() if(!vstd::contains(cluster->objects, obj)) { - float priority = ai->nullkiller->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); + float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); cluster->addObject(obj, path, priority); diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.h b/AI/Nullkiller/Analyzers/ObjectClusterizer.h index a602f3a44..be133e917 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.h +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.h @@ -51,6 +51,7 @@ private: ObjectCluster nearObjects; ObjectCluster farObjects; std::map> blockedObjects; + const Nullkiller * ai; public: void clusterize(); @@ -59,8 +60,11 @@ public: std::vector> getLockedClusters() const; const CGObjectInstance * getBlocker(const AIPath & path) const; - ObjectClusterizer() - :nearObjects(), farObjects(), blockedObjects() + ObjectClusterizer(const Nullkiller * ai) + :nearObjects(), farObjects(), blockedObjects(), ai(ai) { } + +private: + bool shouldVisitObject(const CGObjectInstance * obj) const; }; diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index 805944e7c..0a4afb019 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -10,16 +10,15 @@ #include "StdInc.h" #include "BuildingBehavior.h" #include "../VCAI.h" -#include "../AIhelper.h" #include "../AIUtility.h" #include "../Goals/BuyArmy.h" +#include "../Goals/BuildThis.h" #include "lib/mapping/CMap.h" //for victory conditions #include "lib/CPathfinder.h" #include "../Engine/Nullkiller.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp index 28c7df03a..36abe1bea 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "BuyArmyBehavior.h" #include "../VCAI.h" -#include "../AIhelper.h" #include "../AIUtility.h" #include "../Goals/BuyArmy.h" #include "../Engine/Nullkiller.h" @@ -19,7 +18,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; @@ -55,7 +53,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const { targetHero = town->visitingHero.get(); - if(ai->ah->howManyReinforcementsCanGet(targetHero, town->getUpperArmy())) + if(ai->nullkiller->armyManager->howManyReinforcementsCanGet(targetHero, town->getUpperArmy())) { tasks.push_back(sptr(VisitTile(town->visitablePos()).sethero(targetHero).setpriority(5))); @@ -63,10 +61,10 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const } }*/ - auto reinforcement = ai->ah->howManyReinforcementsCanBuy(targetHero, town); + auto reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(targetHero, town); if(reinforcement) - reinforcement = ai->ah->howManyReinforcementsCanBuy(town->getUpperArmy(), town); + reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town); if(reinforcement) { diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index b788c4d1d..c9466ee84 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -10,17 +10,16 @@ #include "StdInc.h" #include "../VCAI.h" #include "../Engine/Nullkiller.h" -#include "../AIhelper.h" #include "../Goals/Composition.h" #include "../Goals/ExecuteHeroChain.h" #include "CaptureObjectsBehavior.h" #include "../AIUtility.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" +#include "../../../lib/CModHandler.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; @@ -81,7 +80,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector auto hero = path.targetHero; auto danger = path.getTotalDanger(); - if(ai->ah->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1) + if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1) continue; auto firstBlockedAction = path.getFirstBlockedAction(); @@ -169,7 +168,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const const int3 pos = objToVisit->visitablePos(); - auto paths = ai->ah->getPathsToTile(pos); + auto paths = ai->nullkiller->pathfinder->getPathInfo(pos); std::vector> waysToVisitObj; std::shared_ptr closestWay; @@ -214,3 +213,122 @@ bool CaptureObjectsBehavior::shouldVisitObject(const CGObjectInstance * obj) con return true; } + +bool CaptureObjectsBehavior::shouldVisit(HeroPtr h, const CGObjectInstance * obj) +{ + switch(obj->ID) + { + case Obj::TOWN: + case Obj::HERO: //never visit our heroes at random + return obj->tempOwner != h->tempOwner; //do not visit our towns at random + case Obj::BORDER_GATE: + { + for(auto q : ai->myCb->getMyQuests()) + { + if(q.obj == obj) + { + return false; // do not visit guards or gates when wandering + } + } + return true; //we don't have this quest yet + } + case Obj::BORDERGUARD: //open borderguard if possible + return (dynamic_cast(obj))->wasMyColorVisited(ai->playerID); + case Obj::SEER_HUT: + case Obj::QUEST_GUARD: + { + for(auto q : ai->myCb->getMyQuests()) + { + if(q.obj == obj) + { + if(q.quest->checkQuest(h.h)) + return true; //we completed the quest + else + return false; //we can't complete this quest + } + } + return true; //we don't have this quest yet + } + case Obj::CREATURE_GENERATOR1: + { + if(obj->tempOwner != h->tempOwner) + return true; //flag just in case + + const CGDwelling * d = dynamic_cast(obj); + + for(auto level : d->creatures) + { + for(auto c : level.second) + { + if(level.first + && h->getSlotFor(CreatureID(c)) != SlotID() + && cb->getResourceAmount().canAfford(c.toCreature()->cost)) + { + return true; + } + } + } + + return false; + } + case Obj::HILL_FORT: + { + for(auto slot : h->Slots()) + { + if(slot.second->type->upgrades.size()) + return true; //TODO: check price? + } + return false; + } + case Obj::MONOLITH_ONE_WAY_ENTRANCE: + case Obj::MONOLITH_ONE_WAY_EXIT: + case Obj::MONOLITH_TWO_WAY: + case Obj::WHIRLPOOL: + return false; + case Obj::SCHOOL_OF_MAGIC: + case Obj::SCHOOL_OF_WAR: + { + if(cb->getResourceAmount(Res::GOLD) < 1000) + return false; + break; + } + case Obj::LIBRARY_OF_ENLIGHTENMENT: + if(h->level < 12) + return false; + break; + case Obj::TREE_OF_KNOWLEDGE: + { + if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::SCOUT) + return false; + + TResources myRes = cb->getResourceAmount(); + if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10) + return false; + break; + } + case Obj::MAGIC_WELL: + return h->mana < h->manaLimit(); + case Obj::PRISON: + return ai->myCb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER; + case Obj::TAVERN: + { + //TODO: make AI actually recruit heroes + //TODO: only on request + if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) + return false; + else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) + return false; + break; + } + case Obj::BOAT: + return false; + //Boats are handled by pathfinder + case Obj::EYE_OF_MAGI: + return false; //this object is useless to visit, but could be visited indefinitely + } + + if(obj->wasVisited(*h)) //it must pointer to hero instance, heroPtr calls function wasVisited(ui8 player); + return false; + + return true; +} diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h index 2dae12871..7fb5fc399 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h @@ -68,6 +68,7 @@ namespace Goals private: bool shouldVisitObject(const CGObjectInstance * obj) const; + static bool shouldVisit(HeroPtr h, const CGObjectInstance * obj); }; } diff --git a/AI/Nullkiller/Behaviors/ClusterBehavior.cpp b/AI/Nullkiller/Behaviors/ClusterBehavior.cpp index a0c9539be..3b0cbdc6a 100644 --- a/AI/Nullkiller/Behaviors/ClusterBehavior.cpp +++ b/AI/Nullkiller/Behaviors/ClusterBehavior.cpp @@ -11,7 +11,6 @@ #include "ClusterBehavior.h" #include "../VCAI.h" #include "../Engine/Nullkiller.h" -#include "../AIhelper.h" #include "../AIUtility.h" #include "../Goals/UnlockCluster.h" #include "../Goals/Composition.h" @@ -19,7 +18,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; @@ -44,7 +42,7 @@ Goals::TGoalVec ClusterBehavior::decompose() const Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr cluster) const { auto center = cluster->calculateCenter(); - auto paths = ai->ah->getPathsToTile(center->visitablePos()); + auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos()); auto blockerPos = cluster->blocker->visitablePos(); std::vector blockerPaths; diff --git a/AI/Nullkiller/Behaviors/CompleteQuestBehavior.cpp b/AI/Nullkiller/Behaviors/CompleteQuestBehavior.cpp index 70585736d..9bca7a21b 100644 --- a/AI/Nullkiller/Behaviors/CompleteQuestBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CompleteQuestBehavior.cpp @@ -11,13 +11,11 @@ #include "CompleteQuestBehavior.h" #include "CaptureObjectsBehavior.h" #include "../VCAI.h" -#include "../AIhelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 9fe4a479b..88973de0b 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -11,10 +11,10 @@ #include "DefenceBehavior.h" #include "../VCAI.h" #include "../Engine/Nullkiller.h" -#include "../AIhelper.h" #include "../AIUtility.h" #include "../Goals/BuyArmy.h" #include "../Goals/ExecuteHeroChain.h" +#include "../Goals/RecruitHero.h" #include "../Goals/DismissHero.h" #include "../Goals/ExchangeSwapTownHeroes.h" #include "lib/mapping/CMap.h" //for victory conditions @@ -22,7 +22,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; @@ -98,7 +97,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta return; } - uint64_t reinforcement = ai->ah->howManyReinforcementsCanBuy(town->getUpperArmy(), town); + uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town); if(reinforcement) { @@ -106,7 +105,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(0.5f))); } - auto paths = ai->ah->getPathsToTile(town->visitablePos()); + auto paths = ai->nullkiller->pathfinder->getPathInfo(town->visitablePos()); for(auto & treat : treats) { @@ -168,7 +167,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta { if(ai->nullkiller->isHeroLocked(existingHero) || existingHero->getArmyStrength() > hero->getArmyStrength() - || ai->ah->getHeroRole(existingHero) == HeroRole::MAIN + || ai->nullkiller->heroManager->getHeroRole(existingHero) == HeroRole::MAIN || existingHero->movement || existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1)) continue; @@ -262,7 +261,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta /*for(auto & treat : treats) { - auto paths = ai->ah->getPathsToTile(treat.hero->visitablePos()); + auto paths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos()); for(AIPath & path : paths) { diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index 185eea930..2c2b1f5c5 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "../VCAI.h" #include "../Engine/Nullkiller.h" -#include "../AIhelper.h" #include "../Goals/ExecuteHeroChain.h" #include "GatherArmyBehavior.h" #include "../AIUtility.h" @@ -19,7 +18,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; @@ -41,7 +39,7 @@ Goals::TGoalVec GatherArmyBehavior::decompose() const for(const CGHeroInstance * hero : heroes) { - if(ai->ah->getHeroRole(hero) == HeroRole::MAIN + if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN && hero->getArmyStrength() >= 300) { vstd::concatenate(tasks, deliverArmyToHero(hero)); @@ -74,7 +72,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her return tasks; } - auto paths = ai->ah->getPathsToTile(pos); + auto paths = ai->nullkiller->pathfinder->getPathInfo(pos); std::vector> waysToVisitObj; #if AI_TRACE_LEVEL >= 1 @@ -106,7 +104,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her continue; } - float armyValue = (float)ai->ah->howManyReinforcementsCanGet(hero, path.heroArmy) / hero->getArmyStrength(); + float armyValue = (float)ai->nullkiller->armyManager->howManyReinforcementsCanGet(hero, path.heroArmy) / hero->getArmyStrength(); // avoid transferring very small amount of army if(armyValue < 0.1f) @@ -166,7 +164,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) logAi->trace("Checking ways to upgrade army in town %s, %s", upgrader->getObjectName(), pos.toString()); #endif - auto paths = ai->ah->getPathsToTile(pos); + auto paths = ai->nullkiller->pathfinder->getPathInfo(pos); std::vector> waysToVisitObj; #if AI_TRACE_LEVEL >= 1 @@ -203,7 +201,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) continue; } - auto upgrade = ai->ah->calculateCreateresUpgrade(path.heroArmy, upgrader, availableResources); + auto upgrade = ai->nullkiller->armyManager->calculateCreateresUpgrade(path.heroArmy, upgrader, availableResources); auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength(); if(armyValue < 0.1f || upgrade.upgradeValue < 300) // avoid small upgrades diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 9e414bcc7..1cb1fc4b6 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "RecruitHeroBehavior.h" #include "../VCAI.h" -#include "../AIhelper.h" #include "../AIUtility.h" #include "../Goals/RecruitHero.h" #include "../Goals/ExecuteHeroChain.h" @@ -19,7 +18,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.cpp b/AI/Nullkiller/Behaviors/StartupBehavior.cpp index 25627d235..4f1cc608c 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StartupBehavior.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "StartupBehavior.h" #include "../VCAI.h" -#include "../AIhelper.h" #include "../AIUtility.h" #include "../Goals/RecruitHero.h" #include "../Goals/ExecuteHeroChain.h" @@ -22,7 +21,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; @@ -46,7 +44,7 @@ const AIPath getShortestPath(const CGTownInstance * town, const std::vectorah->getPathsToTile(town->visitablePos()); + auto paths = ai->nullkiller->pathfinder->getPathInfo(town->visitablePos()); if(paths.empty()) return nullptr; @@ -114,12 +112,12 @@ Goals::TGoalVec StartupBehavior::decompose() const startupTown = *vstd::maxElementByFun(towns, [](const CGTownInstance * town) -> float { if(town->garrisonHero) - return ai->ah->evaluateHero(town->garrisonHero.get()); + return ai->nullkiller->heroManager->evaluateHero(town->garrisonHero.get()); auto closestHero = getNearestHero(town); if(closestHero) - return ai->ah->evaluateHero(closestHero); + return ai->nullkiller->heroManager->evaluateHero(closestHero); return 0; }); @@ -132,9 +130,9 @@ Goals::TGoalVec StartupBehavior::decompose() const { if(!startupTown->visitingHero) { - if(ai->ah->howManyReinforcementsCanGet(startupTown->getUpperArmy(), closestHero) > 200) + if(ai->nullkiller->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), closestHero) > 200) { - auto paths = ai->ah->getPathsToTile(startupTown->visitablePos()); + auto paths = ai->nullkiller->pathfinder->getPathInfo(startupTown->visitablePos()); if(paths.size()) { @@ -147,22 +145,22 @@ Goals::TGoalVec StartupBehavior::decompose() const else { auto visitingHero = startupTown->visitingHero.get(); - auto visitingHeroScore = ai->ah->evaluateHero(visitingHero); + auto visitingHeroScore = ai->nullkiller->heroManager->evaluateHero(visitingHero); if(startupTown->garrisonHero) { auto garrisonHero = startupTown->garrisonHero.get(); - auto garrisonHeroScore = ai->ah->evaluateHero(garrisonHero); + auto garrisonHeroScore = ai->nullkiller->heroManager->evaluateHero(garrisonHero); if(visitingHeroScore > garrisonHeroScore - || ai->ah->getHeroRole(garrisonHero) == HeroRole::SCOUT && ai->ah->getHeroRole(visitingHero) == HeroRole::MAIN) + || ai->nullkiller->heroManager->getHeroRole(garrisonHero) == HeroRole::SCOUT && ai->nullkiller->heroManager->getHeroRole(visitingHero) == HeroRole::MAIN) { - if(canRecruitHero || ai->ah->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200) + if(canRecruitHero || ai->nullkiller->armyManager->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200) { tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero, HeroLockedReason::STARTUP).setpriority(100))); } } - else if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200) + else if(ai->nullkiller->armyManager->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200) { tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero, HeroLockedReason::STARTUP).setpriority(100))); } diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index cbe5284be..221dc6656 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -4,7 +4,6 @@ set(VCAI_SRCS Pathfinding/AIPathfinderConfig.cpp Pathfinding/AIPathfinder.cpp Pathfinding/AINodeStorage.cpp - Pathfinding/PathfindingManager.cpp Pathfinding/Actors.cpp Pathfinding/Actions/SpecialAction.cpp Pathfinding/Actions/BattleAction.cpp @@ -15,12 +14,11 @@ set(VCAI_SRCS Pathfinding/Rules/AIMovementToDestinationRule.cpp Pathfinding/Rules/AIPreviousNodeRule.cpp AIUtility.cpp - AIhelper.cpp - ArmyManager.cpp - HeroManager.cpp - MapObjectsEvaluator.cpp - FuzzyEngines.cpp - FuzzyHelper.cpp + Analyzers/ArmyManager.cpp + Analyzers/HeroManager.cpp + Engine/FuzzyEngines.cpp + Engine/FuzzyHelper.cpp + Engine/AIMemory.cpp Goals/AbstractGoal.cpp Goals/Composition.cpp Goals/BuildBoat.cpp @@ -61,7 +59,6 @@ set(VCAI_HEADERS Pathfinding/AIPathfinderConfig.h Pathfinding/AIPathfinder.h Pathfinding/AINodeStorage.h - Pathfinding/PathfindingManager.h Pathfinding/Actors.h Pathfinding/Actions/SpecialAction.h Pathfinding/Actions/BattleAction.h @@ -72,12 +69,11 @@ set(VCAI_HEADERS Pathfinding/Rules/AIMovementToDestinationRule.h Pathfinding/Rules/AIPreviousNodeRule.h AIUtility.h - AIhelper.h - ArmyManager.h - HeroManager.h - MapObjectsEvaluator.h - FuzzyEngines.h - FuzzyHelper.h + Analyzers/ArmyManager.h + Analyzers/HeroManager.h + Engine/FuzzyEngines.h + Engine/FuzzyHelper.h + Engine/AIMemory.h Goals/AbstractGoal.h Goals/CGoal.h Goals/Composition.h diff --git a/AI/Nullkiller/Engine/AIMemory.cpp b/AI/Nullkiller/Engine/AIMemory.cpp new file mode 100644 index 000000000..94551fd56 --- /dev/null +++ b/AI/Nullkiller/Engine/AIMemory.cpp @@ -0,0 +1,104 @@ +/* +* AIMemory.cpp, 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 +* +*/ +#include "StdInc.h" +#include "AIMemory.h" +#include "../../../lib/mapObjects/MapObjects.h" + +void AIMemory::removeFromMemory(const CGObjectInstance * obj) +{ + vstd::erase_if_present(visitableObjs, obj); + vstd::erase_if_present(alreadyVisited, obj); + + //TODO: Find better way to handle hero boat removal + if(auto hero = dynamic_cast(obj)) + { + if(hero->boat) + { + vstd::erase_if_present(visitableObjs, hero->boat); + vstd::erase_if_present(alreadyVisited, hero->boat); + } + } +} + +void AIMemory::removeFromMemory(ObjectIdRef obj) +{ + auto matchesId = [&](const CGObjectInstance * hlpObj) -> bool + { + return hlpObj->id == obj.id; + }; + + vstd::erase_if(visitableObjs, matchesId); + vstd::erase_if(alreadyVisited, matchesId); +} + +void AIMemory::addSubterraneanGate(const CGObjectInstance * entrance, const CGObjectInstance * exit) +{ + knownSubterraneanGates[entrance] = exit; + knownSubterraneanGates[exit] = entrance; + + logAi->trace( + "Found a pair of subterranean gates between %s and %s!", + entrance->visitablePos().toString(), + exit->visitablePos().toString()); +} + +void AIMemory::addVisitableObject(const CGObjectInstance * obj) +{ + visitableObjs.insert(obj); + + // All teleport objects seen automatically assigned to appropriate channels + auto teleportObj = dynamic_cast(obj); + if(teleportObj) + { + CGTeleport::addToChannel(knownTeleportChannels, teleportObj); + } +} + +void AIMemory::markObjectVisited(const CGObjectInstance * obj) +{ + if(!obj) + return; + + // TODO: maybe this logic belongs to CaptureObjects::shouldVisit + if(dynamic_cast(obj)) //we may want to visit it with another hero + return; + + if(dynamic_cast(obj)) //or another time + return; + + if(obj->ID == Obj::MONSTER) + return; + + alreadyVisited.insert(obj); +} + +void AIMemory::markObjectUnvisited(const CGObjectInstance * obj) +{ + vstd::erase_if_present(alreadyVisited, obj); +} + +bool AIMemory::wasVisited(const CGObjectInstance * obj) const +{ + return vstd::contains(alreadyVisited, obj); +} + +void AIMemory::removeInvisibleObjects(CCallback * cb) +{ + auto shouldBeErased = [&](const CGObjectInstance * obj) -> bool + { + if(obj) + return !cb->getObj(obj->id, false); // no verbose output needed as we check object visibility + else + return true; + }; + + vstd::erase_if(visitableObjs, shouldBeErased); + vstd::erase_if(alreadyVisited, shouldBeErased); +} \ No newline at end of file diff --git a/AI/Nullkiller/Engine/AIMemory.h b/AI/Nullkiller/Engine/AIMemory.h new file mode 100644 index 000000000..ff857a931 --- /dev/null +++ b/AI/Nullkiller/Engine/AIMemory.h @@ -0,0 +1,36 @@ +/* +* AIMemory.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 "PriorityEvaluator.h" +#include "../Analyzers/DangerHitMapAnalyzer.h" +#include "../Analyzers/BuildAnalyzer.h" +#include "../Analyzers/ArmyManager.h" +#include "../Analyzers/HeroManager.h" +#include "../Analyzers/ObjectClusterizer.h" +#include "../Goals/AbstractGoal.h" + +class AIMemory +{ +public: + std::set visitableObjs; + std::set alreadyVisited; + std::map> knownTeleportChannels; + std::map knownSubterraneanGates; + + void removeFromMemory(const CGObjectInstance * obj); + void removeFromMemory(ObjectIdRef obj); + void addSubterraneanGate(const CGObjectInstance * entrance, const CGObjectInstance * exit); + void addVisitableObject(const CGObjectInstance * obj); + void markObjectVisited(const CGObjectInstance * obj); + void markObjectUnvisited(const CGObjectInstance * obj); + bool wasVisited(const CGObjectInstance * obj) const; + void removeInvisibleObjects(CCallback * cb); +}; diff --git a/AI/Nullkiller/FuzzyEngines.cpp b/AI/Nullkiller/Engine/FuzzyEngines.cpp similarity index 98% rename from AI/Nullkiller/FuzzyEngines.cpp rename to AI/Nullkiller/Engine/FuzzyEngines.cpp index 597629505..1e2410034 100644 --- a/AI/Nullkiller/FuzzyEngines.cpp +++ b/AI/Nullkiller/Engine/FuzzyEngines.cpp @@ -9,17 +9,15 @@ */ #include "StdInc.h" #include "FuzzyEngines.h" -#include "Goals/Goals.h" +#include "../Goals/Goals.h" #include "../../lib/mapObjects/MapObjects.h" #include "VCAI.h" -#include "MapObjectsEvaluator.h" #define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; engineBase::engineBase() { diff --git a/AI/Nullkiller/FuzzyEngines.h b/AI/Nullkiller/Engine/FuzzyEngines.h similarity index 100% rename from AI/Nullkiller/FuzzyEngines.h rename to AI/Nullkiller/Engine/FuzzyEngines.h diff --git a/AI/Nullkiller/FuzzyHelper.cpp b/AI/Nullkiller/Engine/FuzzyHelper.cpp similarity index 79% rename from AI/Nullkiller/FuzzyHelper.cpp rename to AI/Nullkiller/Engine/FuzzyHelper.cpp index 421f74477..1526cb8c3 100644 --- a/AI/Nullkiller/FuzzyHelper.cpp +++ b/AI/Nullkiller/Engine/FuzzyHelper.cpp @@ -12,13 +12,7 @@ #include "../../lib/mapObjects/CommonConstructors.h" #include "Goals/Goals.h" -#include "VCAI.h" - -FuzzyHelper * fh; - -extern boost::thread_specific_ptr ai; -extern boost::thread_specific_ptr cb; - +#include "Nullkiller.h" ui64 FuzzyHelper::estimateBankDanger(const CBank * bank) { @@ -39,14 +33,9 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank) } -ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor) +ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, bool checkGuards) { - return evaluateDanger(tile, visitor, ai.get()); -} - -ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai, bool checkGuards) -{ - auto cb = ai->myCb; + auto cb = ai->cb.get(); const TerrainTile * t = cb->getTile(tile, false); if(!t) //we can know about guard but can't check its tile (the edge of fow) return 190000000; //MUCH @@ -66,7 +55,7 @@ ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, co if(const CGObjectInstance * dangerousObject = vstd::backOrNull(visitableObjects)) { - objectDanger = evaluateDanger(dangerousObject, ai); //unguarded objects can also be dangerous or unhandled + objectDanger = evaluateDanger(dangerousObject); //unguarded objects can also be dangerous or unhandled if(objectDanger) { //TODO: don't downcast objects AI shouldn't know about! @@ -80,15 +69,18 @@ ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, co if(dangerousObject->ID == Obj::SUBTERRANEAN_GATE) { //check guard on the other side of the gate - auto it = ai->knownSubterraneanGates.find(dangerousObject); - if(it != ai->knownSubterraneanGates.end()) + auto it = ai->memory->knownSubterraneanGates.find(dangerousObject); + if(it != ai->memory->knownSubterraneanGates.end()) { auto guards = cb->getGuardingCreatures(it->second->visitablePos()); + for(auto cre : guards) { - float tacticalAdvantage = tacticalAdvantageEngine.getTacticalAdvantage(visitor, dynamic_cast(cre)); + float tacticalAdvantage = tacticalAdvantageEngine.getTacticalAdvantage( + visitor, + dynamic_cast(cre)); - vstd::amax(guardDanger, evaluateDanger(cre, ai) * tacticalAdvantage); + vstd::amax(guardDanger, evaluateDanger(cre) * tacticalAdvantage); } } } @@ -101,7 +93,7 @@ ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, co { float tacticalAdvantage = tacticalAdvantageEngine.getTacticalAdvantage(visitor, dynamic_cast(cre)); - vstd::amax(guardDanger, evaluateDanger(cre, ai) * tacticalAdvantage); //we are interested in strongest monster around + vstd::amax(guardDanger, evaluateDanger(cre) * tacticalAdvantage); //we are interested in strongest monster around } } @@ -109,9 +101,9 @@ ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, co return std::max(objectDanger, guardDanger); } -ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai) +ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj) { - auto cb = ai->myCb; + auto cb = ai->cb.get(); if(obj->tempOwner < PlayerColor::PLAYER_LIMIT && cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES) //owned or allied objects don't pose any threat return 0; @@ -126,7 +118,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai) case Obj::ARTIFACT: case Obj::RESOURCE: { - if(!vstd::contains(ai->alreadyVisited, obj)) + if(!vstd::contains(ai->memory->alreadyVisited, obj)) { return 0; } diff --git a/AI/Nullkiller/FuzzyHelper.h b/AI/Nullkiller/Engine/FuzzyHelper.h similarity index 71% rename from AI/Nullkiller/FuzzyHelper.h rename to AI/Nullkiller/Engine/FuzzyHelper.h index ea805d3d0..f9bfcf23a 100644 --- a/AI/Nullkiller/FuzzyHelper.h +++ b/AI/Nullkiller/Engine/FuzzyHelper.h @@ -11,15 +11,19 @@ #include "FuzzyEngines.h" class CBank; +class Nullkiller; class DLL_EXPORT FuzzyHelper { -public: +private: + const Nullkiller * ai; TacticalAdvantageEngine tacticalAdvantageEngine; +public: + FuzzyHelper(const Nullkiller * ai) : ai(ai), tacticalAdvantageEngine() {} + ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class? - ui64 evaluateDanger(const CGObjectInstance * obj, const VCAI * ai); - ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai, bool checkGuards = true); - ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor); + ui64 evaluateDanger(const CGObjectInstance * obj); + ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor, bool checkGuards = true); }; diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 25c716493..a403e87b0 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "Nullkiller.h" #include "../VCAI.h" -#include "../AIhelper.h" #include "../Behaviors/CaptureObjectsBehavior.h" #include "../Behaviors/RecruitHeroBehavior.h" #include "../Behaviors/BuyArmyBehavior.h" @@ -28,10 +27,22 @@ using namespace Goals; Nullkiller::Nullkiller() { - priorityEvaluator.reset(new PriorityEvaluator()); - dangerHitMap.reset(new DangerHitMapAnalyzer()); + memory.reset(new AIMemory()); +} + +void Nullkiller::init(std::shared_ptr cb, PlayerColor playerID) +{ + this->cb = cb; + this->playerID = playerID; + + priorityEvaluator.reset(new PriorityEvaluator(this)); + dangerHitMap.reset(new DangerHitMapAnalyzer(this)); buildAnalyzer.reset(new BuildAnalyzer()); - objectClusterizer.reset(new ObjectClusterizer()); + objectClusterizer.reset(new ObjectClusterizer(this)); + dangerEvaluator.reset(new FuzzyHelper(this)); + pathfinder.reset(new AIPathfinder(cb.get(), this)); + armyManager.reset(new ArmyManager(cb.get(), this)); + heroManager.reset(new HeroManager(cb.get(), this)); } Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const @@ -130,6 +141,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior) const void Nullkiller::resetAiState() { + playerID = ai->playerID; lockedHeroes.clear(); dangerHitMap->reset(); } @@ -138,11 +150,11 @@ void Nullkiller::updateAiState() { activeHero = nullptr; - ai->validateVisitableObjs(); + memory->removeInvisibleObjects(cb.get()); dangerHitMap->updateHitMap(); // TODO: move to hero manager - auto activeHeroes = ai->getMyHeroes(); + auto activeHeroes = cb->getHeroesInfo(); vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool { @@ -151,8 +163,9 @@ void Nullkiller::updateAiState() return lockedReason == HeroLockedReason::DEFENCE; }); - ai->ah->updatePaths(activeHeroes, true); - ai->ah->update(); + pathfinder->updatePaths(activeHeroes, true); + heroManager->update(); + armyManager->update(); objectClusterizer->clusterize(); buildAnalyzer->update(); diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 2d6451f40..407f0da1c 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -10,8 +10,12 @@ #pragma once #include "PriorityEvaluator.h" +#include "FuzzyHelper.h" +#include "AIMemory.h" #include "../Analyzers/DangerHitMapAnalyzer.h" #include "../Analyzers/BuildAnalyzer.h" +#include "../Analyzers/ArmyManager.h" +#include "../Analyzers/HeroManager.h" #include "../Analyzers/ObjectClusterizer.h" #include "../Goals/AbstractGoal.h" @@ -41,8 +45,16 @@ public: std::unique_ptr buildAnalyzer; std::unique_ptr objectClusterizer; std::unique_ptr priorityEvaluator; + std::unique_ptr pathfinder; + std::unique_ptr heroManager; + std::unique_ptr armyManager; + std::unique_ptr memory; + std::unique_ptr dangerEvaluator; + PlayerColor playerID; + std::shared_ptr cb; Nullkiller(); + void init(std::shared_ptr cb, PlayerColor playerID); void makeTurn(); bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; } bool isHeroLocked(const CGHeroInstance * hero) const; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 0532d986f..b0b8eeee1 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -19,25 +19,17 @@ #include "../../../lib/VCMI_Lib.h" #include "../../../CCallback.h" #include "../../../lib/filesystem/Filesystem.h" -#include "../VCAI.h" -#include "../AIhelper.h" #include "../Engine/Nullkiller.h" #include "../Goals/ExecuteHeroChain.h" #include "../Goals/UnlockCluster.h" +#include "../Goals/BuildThis.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 -struct BankConfig; -class CBankInfo; -class Engine; -class InputVariable; -class CGTownInstance; - extern boost::thread_specific_ptr cb; -extern boost::thread_specific_ptr ai; -EvaluationContext::EvaluationContext() +EvaluationContext::EvaluationContext(const Nullkiller * ai) : movementCost(0.0), manaCost(0), danger(0), @@ -50,7 +42,8 @@ EvaluationContext::EvaluationContext() armyLossPersentage(0), heroRole(HeroRole::SCOUT), turn(0), - strategicalValue(0) + strategicalValue(0), + evaluator(ai) { } @@ -194,7 +187,11 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art) return statsValue > classValue ? statsValue : classValue; } -uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army, bool checkGold) +uint64_t RewardEvaluator::getArmyReward( + const CGObjectInstance * target, + const CGHeroInstance * hero, + const CCreatureSet * army, + bool checkGold) const { const float enemyArmyEliminationRewardRatio = 0.5f; @@ -206,7 +203,7 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h case Obj::TOWN: return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000; case Obj::HILL_FORT: - return ai->ah->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeValue; + return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeValue; case Obj::CREATURE_BANK: return getCreatureBankArmyReward(target, hero); case Obj::CREATURE_GENERATOR1: @@ -232,7 +229,7 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h } } -int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) +int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const { if(!target) return 0; @@ -240,7 +237,7 @@ int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, co switch(target->ID) { case Obj::HILL_FORT: - return ai->ah->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeCost[Res::GOLD]; + return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeCost[Res::GOLD]; case Obj::SCHOOL_OF_MAGIC: case Obj::SCHOOL_OF_WAR: return 1000; @@ -256,11 +253,9 @@ int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, co } } -float getStrategicalValue(const CGObjectInstance * target); - -float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) +float RewardEvaluator::getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const { - auto objectsUnderTreat = ai->nullkiller->dangerHitMap->getOneTurnAccessibleObjects(enemy); + auto objectsUnderTreat = ai->dangerHitMap->getOneTurnAccessibleObjects(enemy); float objectValue = 0; for(auto obj : objectsUnderTreat) @@ -271,10 +266,10 @@ float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) return objectValue / 2.0f + enemy->level / 15.0f; } -float getResourceRequirementStrength(int resType) +float RewardEvaluator::getResourceRequirementStrength(int resType) const { - TResources requiredResources = ai->nullkiller->buildAnalyzer->getResourcesRequiredNow(); - TResources dailyIncome = ai->nullkiller->buildAnalyzer->getDailyIncome(); + TResources requiredResources = ai->buildAnalyzer->getResourcesRequiredNow(); + TResources dailyIncome = ai->buildAnalyzer->getDailyIncome(); if(requiredResources[resType] == 0) return 0; @@ -287,10 +282,10 @@ float getResourceRequirementStrength(int resType) return std::min(ratio, 1.0f); } -float getTotalResourceRequirementStrength(int resType) +float RewardEvaluator::getTotalResourceRequirementStrength(int resType) const { - TResources requiredResources = ai->nullkiller->buildAnalyzer->getTotalResourcesRequired(); - TResources dailyIncome = ai->nullkiller->buildAnalyzer->getDailyIncome(); + TResources requiredResources = ai->buildAnalyzer->getTotalResourcesRequired(); + TResources dailyIncome = ai->buildAnalyzer->getDailyIncome(); if(requiredResources[resType] == 0) return 0; @@ -302,7 +297,7 @@ float getTotalResourceRequirementStrength(int resType) return std::min(ratio, 1.0f); } -float getStrategicalValue(const CGObjectInstance * target) +float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const { if(!target) return 0; @@ -310,7 +305,9 @@ float getStrategicalValue(const CGObjectInstance * target) switch(target->ID) { case Obj::MINE: - return target->subID == Res::GOLD ? 0.5f : 0.02f * getTotalResourceRequirementStrength(target->subID) + 0.02f * getResourceRequirementStrength(target->subID); + return target->subID == Res::GOLD + ? 0.5f + : 0.02f * getTotalResourceRequirementStrength(target->subID) + 0.02f * getResourceRequirementStrength(target->subID); case Obj::RESOURCE: return target->subID == Res::GOLD ? 0 : 0.1f * getResourceRequirementStrength(target->subID); @@ -330,7 +327,7 @@ float getStrategicalValue(const CGObjectInstance * target) } } -float evaluateWitchHutSkillScore(const CGWitchHut * hut, const CGHeroInstance * hero, HeroRole role) +float RewardEvaluator::evaluateWitchHutSkillScore(const CGWitchHut * hut, const CGHeroInstance * hero, HeroRole role) const { if(!hut->wasVisited(hero->tempOwner)) return role == HeroRole::SCOUT ? 2 : 0; @@ -341,12 +338,12 @@ float evaluateWitchHutSkillScore(const CGWitchHut * hut, const CGHeroInstance * || hero->secSkills.size() >= GameConstants::SKILL_PER_HERO) return 0; - auto score = ai->ah->evaluateSecSkill(skill, hero); + auto score = ai->heroManager->evaluateSecSkill(skill, hero); return score >= 2 ? (role == HeroRole::MAIN ? 10 : 4) : score; } -float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) +float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const { const float enemyHeroEliminationSkillRewardRatio = 0.5f; @@ -397,7 +394,7 @@ int32_t getArmyCost(const CArmedInstance * army) } /// Gets aproximated reward in gold. Daily income is multiplied by 5 -int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) +int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const { if(!target) return 0; @@ -469,7 +466,7 @@ public: for(auto pair : costsPerHero) { - auto role = ai->ah->getHeroRole(pair.first); + auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(pair.first); evaluationContext.movementCostByRole[role] += pair.second; } @@ -482,12 +479,12 @@ public: auto army = path.heroArmy; vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength()); - evaluationContext.heroRole = ai->ah->getHeroRole(heroPtr); - evaluationContext.goldReward += getGoldReward(target, hero); - evaluationContext.armyReward += getArmyReward(target, hero, army, checkGold); - evaluationContext.skillReward += getSkillReward(target, hero, evaluationContext.heroRole); - evaluationContext.strategicalValue += getStrategicalValue(target); - evaluationContext.goldCost += getGoldCost(target, hero, army); + evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroPtr); + evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero); + evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold); + evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, evaluationContext.heroRole); + evaluationContext.strategicalValue += evaluationContext.evaluator.getStrategicalValue(target); + evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army); vstd::amax(evaluationContext.turn, path.turn()); } }; @@ -504,7 +501,7 @@ public: std::shared_ptr cluster = clusterGoal.getCluster(); auto hero = clusterGoal.hero.get(); - auto role = ai->ah->getHeroRole(clusterGoal.hero); + auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(clusterGoal.hero); std::vector> objects(cluster->objects.begin(), cluster->objects.end()); @@ -522,11 +519,11 @@ public: bool checkGold = objInfo.second.danger == 0; auto army = hero; - evaluationContext.goldReward += getGoldReward(target, hero) / boost; - evaluationContext.armyReward += getArmyReward(target, hero, army, checkGold) / boost; - evaluationContext.skillReward += getSkillReward(target, hero, role) / boost; - evaluationContext.strategicalValue += getStrategicalValue(target) / boost; - evaluationContext.goldCost += getGoldCost(target, hero, army) / boost; + evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero) / boost; + evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold) / boost; + evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, role) / boost; + evaluationContext.strategicalValue += evaluationContext.evaluator.getStrategicalValue(target) / boost; + evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army) / boost; evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost; evaluationContext.movementCost += objInfo.second.movementCost / boost; @@ -565,27 +562,34 @@ public: if(bi.baseCreatureID == bi.creatureID) { evaluationContext.strategicalValue += 0.5f + 0.1f * bi.creatureLevel / (float)bi.prerequisitesCount; - evaluationContext.armyReward += ai->ah->evaluateStackPower(bi.creatureID.toCreature(), bi.creatureGrows); + evaluationContext.armyReward += evaluationContext.evaluator.ai->armyManager->evaluateStackPower(bi.creatureID.toCreature(), bi.creatureGrows); } else { - auto creaturesToUpgrade = ai->ah->getTotalCreaturesAvailable(bi.baseCreatureID); - auto upgradedPower = ai->ah->evaluateStackPower(bi.creatureID.toCreature(), creaturesToUpgrade.count); - evaluationContext.strategicalValue += 0.05f * bi.creatureLevel / (float)bi.prerequisitesCount; - - if(!ai->nullkiller->buildAnalyzer->hasAnyBuilding(buildThis.town->alignment, bi.id)) - evaluationContext.armyReward += upgradedPower - creaturesToUpgrade.power; + evaluationContext.armyReward += evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi); } } else { - evaluationContext.strategicalValue += ai->nullkiller->buildAnalyzer->getGoldPreasure() * evaluationContext.goldReward / 2200.0f; + evaluationContext.strategicalValue += evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure() * evaluationContext.goldReward / 2200.0f; } } }; -PriorityEvaluator::PriorityEvaluator() +uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const +{ + if(ai->buildAnalyzer->hasAnyBuilding(town->alignment, bi.id)) + return 0; + + auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID); + auto upgradedPower = ai->armyManager->evaluateStackPower(bi.creatureID.toCreature(), creaturesToUpgrade.count); + + return upgradedPower - creaturesToUpgrade.power; +} + +PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai) + :ai(ai) { initVisitTile(); evaluationContextBuilders.push_back(std::make_shared()); @@ -596,7 +600,7 @@ PriorityEvaluator::PriorityEvaluator() EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const { Goals::TGoalVec parts; - EvaluationContext context; + EvaluationContext context(ai); if(goal->goalType == Goals::COMPOSITION) { @@ -652,8 +656,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) rewardTypeVariable->setValue(rewardType); closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); strategicalValueVariable->setValue(evaluationContext.strategicalValue); - goldPreasureVariable->setValue(ai->nullkiller->buildAnalyzer->getGoldPreasure()); - goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->nullkiller->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f)); + goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure()); + goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f)); turnVariable->setValue(evaluationContext.turn); engine->process(); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index a000522f3..a6968999c 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -9,7 +9,28 @@ */ #pragma once #include "fl/Headers.h" -#include "../Goals/Goals.h" +#include "../Goals/CGoal.h" + +class BuildingInfo; + +class RewardEvaluator +{ +public: + const Nullkiller * ai; + + RewardEvaluator(const Nullkiller * ai) : ai(ai) {} + + uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army, bool checkGold) const; + int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const; + float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const; + float getResourceRequirementStrength(int resType) const; + float getStrategicalValue(const CGObjectInstance * target) const; + float getTotalResourceRequirementStrength(int resType) const; + float evaluateWitchHutSkillScore(const CGWitchHut * hut, const CGHeroInstance * hero, HeroRole role) const; + float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const; + int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const; + uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const; +}; struct DLL_EXPORT EvaluationContext { @@ -26,8 +47,9 @@ struct DLL_EXPORT EvaluationContext float strategicalValue; HeroRole heroRole; uint8_t turn; + RewardEvaluator evaluator; - EvaluationContext(); + EvaluationContext(const Nullkiller * ai); }; class IEvaluationContextBuilder @@ -36,16 +58,20 @@ public: virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal goal) const = 0; }; +class Nullkiller; + class PriorityEvaluator { public: - PriorityEvaluator(); + PriorityEvaluator(const Nullkiller * ai); ~PriorityEvaluator(); void initVisitTile(); float evaluate(Goals::TSubgoal task); private: + const Nullkiller * ai; + fl::Engine * engine; fl::InputVariable * armyLossPersentageVariable; fl::InputVariable * heroRoleVariable; diff --git a/AI/Nullkiller/Goals/AbstractGoal.cpp b/AI/Nullkiller/Goals/AbstractGoal.cpp index d6fcdb583..4dbebb01b 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.cpp +++ b/AI/Nullkiller/Goals/AbstractGoal.cpp @@ -10,15 +10,12 @@ #include "StdInc.h" #include "AbstractGoal.h" #include "../VCAI.h" -#include "../AIhelper.h" -#include "../FuzzyHelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/AdventureSpellCast.cpp b/AI/Nullkiller/Goals/AdventureSpellCast.cpp index 628f5e865..91abbd1db 100644 --- a/AI/Nullkiller/Goals/AdventureSpellCast.cpp +++ b/AI/Nullkiller/Goals/AdventureSpellCast.cpp @@ -10,14 +10,11 @@ #include "StdInc.h" #include "AdventureSpellCast.h" #include "../VCAI.h" -#include "../FuzzyHelper.h" -#include "../AIhelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/BuildBoat.cpp b/AI/Nullkiller/Goals/BuildBoat.cpp index 84a56ff6b..83207d75c 100644 --- a/AI/Nullkiller/Goals/BuildBoat.cpp +++ b/AI/Nullkiller/Goals/BuildBoat.cpp @@ -10,15 +10,12 @@ #include "StdInc.h" #include "BuildBoat.h" #include "../VCAI.h" -#include "../FuzzyHelper.h" -#include "../AIhelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" #include "../Behaviors/CaptureObjectsBehavior.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/BuildThis.cpp b/AI/Nullkiller/Goals/BuildThis.cpp index 388b3bcf2..cd80454f5 100644 --- a/AI/Nullkiller/Goals/BuildThis.cpp +++ b/AI/Nullkiller/Goals/BuildThis.cpp @@ -11,8 +11,6 @@ #include "BuildThis.h" #include "../VCAI.h" #include "../AIUtility.h" -#include "../AIhelper.h" -#include "../FuzzyHelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" @@ -20,7 +18,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/BuyArmy.cpp b/AI/Nullkiller/Goals/BuyArmy.cpp index 0710c7f34..8ccb7f187 100644 --- a/AI/Nullkiller/Goals/BuyArmy.cpp +++ b/AI/Nullkiller/Goals/BuyArmy.cpp @@ -9,14 +9,13 @@ */ #include "StdInc.h" #include "BuyArmy.h" -#include "../FuzzyHelper.h" -#include "../AIhelper.h" #include "../../../lib/mapObjects/CGTownInstance.h" +#include "VCAI.h" +#include "Engine/Nullkiller.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; @@ -37,7 +36,7 @@ void BuyArmy::accept(VCAI * ai) auto upgradeSuccessfull = ai->makePossibleUpgrades(town); - auto armyToBuy = ai->ah->getArmyAvailableToBuy(town->getUpperArmy(), town); + auto armyToBuy = ai->nullkiller->armyManager->getArmyAvailableToBuy(town->getUpperArmy(), town); if(armyToBuy.empty()) { diff --git a/AI/Nullkiller/Goals/CGoal.h b/AI/Nullkiller/Goals/CGoal.h index abccf442f..1e10ddcb2 100644 --- a/AI/Nullkiller/Goals/CGoal.h +++ b/AI/Nullkiller/Goals/CGoal.h @@ -10,7 +10,6 @@ #pragma once #include "AbstractGoal.h" -#include "../FuzzyHelper.h" #include "../VCAI.h" struct HeroPtr; @@ -92,13 +91,6 @@ namespace Goals { } - ///Visitor pattern - //TODO: make accept work for std::shared_ptr... somehow - virtual void accept(VCAI * ai) override //unhandled goal will report standard error - { - ai->tryRealize(*((T *)this)); - } - T & setpriority(float p) { ITask::priority = p; @@ -134,5 +126,10 @@ namespace Goals { return "Invalid"; } + + virtual void accept(VCAI * ai) override + { + throw cannotFulfillGoalException("Can not fulfill Invalid goal!"); + } }; } diff --git a/AI/Nullkiller/Goals/CollectRes.cpp b/AI/Nullkiller/Goals/CollectRes.cpp index 7c0553e38..94d048cc1 100644 --- a/AI/Nullkiller/Goals/CollectRes.cpp +++ b/AI/Nullkiller/Goals/CollectRes.cpp @@ -11,8 +11,6 @@ #include "Goals.h" #include "../VCAI.h" #include "../AIUtility.h" -#include "../AIhelper.h" -#include "../FuzzyHelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" @@ -20,7 +18,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/Composition.cpp b/AI/Nullkiller/Goals/Composition.cpp index ed7334095..9e5c62c26 100644 --- a/AI/Nullkiller/Goals/Composition.cpp +++ b/AI/Nullkiller/Goals/Composition.cpp @@ -11,8 +11,6 @@ #include "Composition.h" #include "../VCAI.h" #include "../AIUtility.h" -#include "../AIhelper.h" -#include "../FuzzyHelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" @@ -20,7 +18,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/DigAtTile.cpp b/AI/Nullkiller/Goals/DigAtTile.cpp index 2f6c8e75c..e079b511c 100644 --- a/AI/Nullkiller/Goals/DigAtTile.cpp +++ b/AI/Nullkiller/Goals/DigAtTile.cpp @@ -15,7 +15,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/DismissHero.cpp b/AI/Nullkiller/Goals/DismissHero.cpp index 1b73b8e0e..967f0e65f 100644 --- a/AI/Nullkiller/Goals/DismissHero.cpp +++ b/AI/Nullkiller/Goals/DismissHero.cpp @@ -10,14 +10,11 @@ #include "StdInc.h" #include "DismissHero.h" #include "../VCAI.h" -#include "../FuzzyHelper.h" -#include "../AIhelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp index 72c56141f..ad5b40765 100644 --- a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp +++ b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp @@ -11,15 +11,12 @@ #include "ExchangeSwapTownHeroes.h" #include "ExecuteHeroChain.h" #include "../VCAI.h" -#include "../FuzzyHelper.h" -#include "../AIhelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" #include "../Engine/Nullkiller.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index 14c9afb10..cac1bf018 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -10,15 +10,12 @@ #include "StdInc.h" #include "ExecuteHeroChain.h" #include "../VCAI.h" -#include "../FuzzyHelper.h" -#include "../AIhelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" #include "../Engine/Nullkiller.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/GatherTroops.cpp b/AI/Nullkiller/Goals/GatherTroops.cpp index c34dcfc6d..05324a1fe 100644 --- a/AI/Nullkiller/Goals/GatherTroops.cpp +++ b/AI/Nullkiller/Goals/GatherTroops.cpp @@ -11,8 +11,6 @@ #include "Goals.h" #include "../VCAI.h" #include "../AIUtility.h" -#include "../AIhelper.h" -#include "../FuzzyHelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" @@ -20,7 +18,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/GetArtOfType.cpp b/AI/Nullkiller/Goals/GetArtOfType.cpp index 5900fcbe0..5d90f00ad 100644 --- a/AI/Nullkiller/Goals/GetArtOfType.cpp +++ b/AI/Nullkiller/Goals/GetArtOfType.cpp @@ -15,7 +15,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/RecruitHero.cpp b/AI/Nullkiller/Goals/RecruitHero.cpp index 479b1221f..6969eab70 100644 --- a/AI/Nullkiller/Goals/RecruitHero.cpp +++ b/AI/Nullkiller/Goals/RecruitHero.cpp @@ -11,8 +11,6 @@ #include "Goals.h" #include "../VCAI.h" #include "../AIUtility.h" -#include "../AIhelper.h" -#include "../FuzzyHelper.h" #include "../../../lib/mapping/CMap.h" //for victory conditions #include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" @@ -20,7 +18,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/Goals/UnlockCluster.cpp b/AI/Nullkiller/Goals/UnlockCluster.cpp index 315f51881..66ddb89f1 100644 --- a/AI/Nullkiller/Goals/UnlockCluster.cpp +++ b/AI/Nullkiller/Goals/UnlockCluster.cpp @@ -15,7 +15,6 @@ extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; using namespace Goals; diff --git a/AI/Nullkiller/MapObjectsEvaluator.cpp b/AI/Nullkiller/MapObjectsEvaluator.cpp deleted file mode 100644 index 8532146cd..000000000 --- a/AI/Nullkiller/MapObjectsEvaluator.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "StdInc.h" -#include "MapObjectsEvaluator.h" -#include "../../lib/GameConstants.h" -#include "../../lib/VCMI_Lib.h" -#include "../../lib/CCreatureHandler.h" -#include "../../lib/CHeroHandler.h" -#include "../../lib/mapObjects/CGHeroInstance.h" -#include "../../lib/mapObjects/CGTownInstance.h" -#include "../../lib/mapObjects/MiscObjects.h" -#include "../../lib/CRandomGenerator.h" -#include "../../lib/spells/CSpellHandler.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 //some default handling when aiValue not found, objects that require advanced properties (unavailable from handler) get their value calculated in getObjectValue - { - objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; - } - } - } - } -} - -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(); -} - -boost::optional MapObjectsEvaluator::getObjectValue(const CGObjectInstance * obj) const -{ - if(obj->ID == Obj::HERO) - { - //special case handling: in-game heroes have hero ID as object subID, but when reading configs available hero object subID's are hero classes - auto hero = dynamic_cast(obj); - return getObjectValue(obj->ID, hero->type->heroClass->id); - } - else if(obj->ID == Obj::PRISON) - { - //special case: in-game prison subID is captured hero ID, but config has one subID with index 0 for normal prison - use that one - return getObjectValue(obj->ID, 0); - } - else if(obj->ID == Obj::CREATURE_GENERATOR1 || obj->ID == Obj::CREATURE_GENERATOR4) - { - auto dwelling = dynamic_cast(obj); - int aiValue = 0; - for(auto & creLevel : dwelling->creatures) - { - for(auto & creatureID : creLevel.second) - { - auto creature = VLC->creh->creatures[creatureID]; - aiValue += (creature->AIValue * creature->growth); - } - } - return aiValue; - } - else if(obj->ID == Obj::ARTIFACT) - { - auto artifactObject = dynamic_cast(obj); - switch(artifactObject->storedArtifact->artType->aClass) - { - case CArtifact::EartClass::ART_TREASURE: - return 2000; - case CArtifact::EartClass::ART_MINOR: - return 5000; - case CArtifact::EartClass::ART_MAJOR: - return 10000; - case CArtifact::EartClass::ART_RELIC: - return 20000; - case CArtifact::EartClass::ART_SPECIAL: - return 20000; - default: - return 0; //invalid artifact class - } - } - else if(obj->ID == Obj::SPELL_SCROLL) - { - auto scrollObject = dynamic_cast(obj); - auto spell = scrollObject->storedArtifact->getGivenSpellID().toSpell(); - if(spell) - { - switch(spell->getLevel()) - { - case 0: return 0; //scroll with creature ability? Let's assume it is useless - case 1: return 1000; - case 2: return 2000; - case 3: return 5000; - case 4: return 10000; - case 5: return 20000; - default: logAi->warn("AI detected spell scroll with spell level %s", spell->getLevel()); - } - } - else - logAi->warn("AI found spell scroll with invalid spell ID: %s", scrollObject->storedArtifact->getGivenSpellID()); - } - - return getObjectValue(obj->ID, obj->subID); -} - -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/Nullkiller/MapObjectsEvaluator.h b/AI/Nullkiller/MapObjectsEvaluator.h deleted file mode 100644 index 35a76d304..000000000 --- a/AI/Nullkiller/MapObjectsEvaluator.h +++ /dev/null @@ -1,26 +0,0 @@ -/* -* 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; - boost::optional getObjectValue(const CGObjectInstance * obj) const; - void addObjectData(int primaryID, int secondaryID, int value); - void removeObjectData(int primaryID, int secondaryID); -}; - diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 9c7cc7d82..f4973eaff 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -22,11 +22,11 @@ /// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations) boost::multi_array nodes; -AINodeStorage::AINodeStorage(const int3 & Sizes) - : sizes(Sizes) +AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes) + : sizes(Sizes), ai(ai), cb(ai->cb.get()) { nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]); - dangerEvaluator.reset(new FuzzyHelper()); + dangerEvaluator.reset(new FuzzyHelper(ai)); } AINodeStorage::~AINodeStorage() = default; @@ -614,17 +614,14 @@ const std::set AINodeStorage::getAllHeroes() const return heroes; } -void AINodeStorage::setHeroes(std::vector heroes, const VCAI * _ai) +void AINodeStorage::setHeroes(std::vector heroes) { - cb = _ai->myCb.get(); - ai = _ai; - playerID = ai->playerID; for(auto & hero : heroes) { uint64_t mask = 1 << actors.size(); - auto actor = std::make_shared(hero.get(), mask, ai); + auto actor = std::make_shared(hero, mask, ai); if(hero->tempOwner != ai->playerID) { diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 9c9266e6f..11b505042 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -16,7 +16,7 @@ #include "../../../lib/CPathfinder.h" #include "../../../lib/mapObjects/CGHeroInstance.h" #include "../AIUtility.h" -#include "../FuzzyHelper.h" +#include "../Engine/FuzzyHelper.h" #include "../Goals/AbstractGoal.h" #include "Actions/SpecialAction.h" #include "Actors.h" @@ -107,7 +107,7 @@ private: int3 sizes; const CPlayerSpecificInfoCallback * cb; - const VCAI * ai; + const Nullkiller * ai; std::unique_ptr dangerEvaluator; std::vector> actors; std::vector heroChain; @@ -121,7 +121,7 @@ public: /// more than 1 chain layer for each hero allows us to have more than 1 path to each tile so we can chose more optimal one. static const int NUM_CHAINS = 10 * GameConstants::MAX_HEROES_PER_PLAYER; - AINodeStorage(const int3 & sizes); + AINodeStorage(const Nullkiller * ai, const int3 & sizes); ~AINodeStorage(); void initialize(const PathfinderOptions & options, const CGameState * gs) override; @@ -176,7 +176,7 @@ public: boost::optional getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor); std::vector getChainInfo(const int3 & pos, bool isOnLand) const; bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const; - void setHeroes(std::vector heroes, const VCAI * ai); + void setHeroes(std::vector heroes); void setTownsAndDwellings( const std::vector & towns, const std::set & visitableObjs); @@ -188,7 +188,7 @@ public: uint64_t evaluateDanger(const int3 & tile, const CGHeroInstance * hero, bool checkGuards) const { - return dangerEvaluator->evaluateDanger(tile, hero, ai, checkGuards); + return dangerEvaluator->evaluateDanger(tile, hero, checkGuards); } uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp index cc3ee65be..5c471f036 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp @@ -12,10 +12,11 @@ #include "AIPathfinderConfig.h" #include "../../../CCallback.h" #include "../../../lib/mapping/CMap.h" +#include "../Engine/Nullkiller.h" std::shared_ptr AIPathfinder::storage; -AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai) +AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai) :cb(cb), ai(ai) { } @@ -43,22 +44,22 @@ std::vector AIPathfinder::getPathInfo(const int3 & tile) const return storage->getChainInfo(tile, !tileInfo->isWater()); } -void AIPathfinder::updatePaths(std::vector heroes, bool useHeroChain) +void AIPathfinder::updatePaths(std::vector heroes, bool useHeroChain) { if(!storage) { - storage = std::make_shared(cb->getMapSize()); + storage.reset(new AINodeStorage(ai, cb->getMapSize())); } logAi->debug("Recalculate all paths"); int pass = 0; storage->clear(); - storage->setHeroes(heroes, ai); + storage->setHeroes(heroes); if(useHeroChain) { - storage->setTownsAndDwellings(cb->getTownsInfo(), ai->visitableObjs); + storage->setTownsAndDwellings(cb->getTownsInfo(), ai->memory->visitableObjs); } auto config = std::make_shared(cb, ai, storage); diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.h b/AI/Nullkiller/Pathfinding/AIPathfinder.h index b32c7d2d9..1e6cfa9b8 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.h +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.h @@ -12,19 +12,20 @@ #include "AINodeStorage.h" #include "../AIUtility.h" -#include "../VCAI.h" + +class Nullkiller; class AIPathfinder { private: static std::shared_ptr storage; CPlayerSpecificInfoCallback * cb; - VCAI * ai; + Nullkiller * ai; public: - AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai); + AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai); std::vector getPathInfo(const int3 & tile) const; bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const; - void updatePaths(std::vector heroes, bool useHeroChain = false); + void updatePaths(std::vector heroes, bool useHeroChain = false); void init(); }; diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp index c976fd88a..c34a05d05 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp @@ -13,12 +13,13 @@ #include "Rules/AIMovementAfterDestinationRule.h" #include "Rules/AIMovementToDestinationRule.h" #include "Rules/AIPreviousNodeRule.h" +#include "../Engine//Nullkiller.h" namespace AIPathfinding { std::vector> makeRuleset( CPlayerSpecificInfoCallback * cb, - VCAI * ai, + Nullkiller * ai, std::shared_ptr nodeStorage) { std::vector> rules = { @@ -35,7 +36,7 @@ namespace AIPathfinding AIPathfinderConfig::AIPathfinderConfig( CPlayerSpecificInfoCallback * cb, - VCAI * ai, + Nullkiller * ai, std::shared_ptr nodeStorage) :PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage) { diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h index 98e295225..0b196e7a2 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h @@ -11,7 +11,8 @@ #pragma once #include "AINodeStorage.h" -#include "../VCAI.h" + +class Nullkiller; namespace AIPathfinding { @@ -24,7 +25,7 @@ namespace AIPathfinding public: AIPathfinderConfig( CPlayerSpecificInfoCallback * cb, - VCAI * ai, + Nullkiller * ai, std::shared_ptr nodeStorage); virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index e9f3e8644..22687f45d 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "Actors.h" #include "../VCAI.h" -#include "../AIhelper.h" +#include "../Engine/Nullkiller.h" #include "../../../CCallback.h" #include "../../../lib/mapping/CMap.h" #include "../../../lib/mapObjects/MapObjects.h" @@ -72,7 +72,7 @@ std::string ObjectActor::toString() const return object->getObjectName() + " at " + object->visitablePos().toString(); } -HeroActor::HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const VCAI * ai) +HeroActor::HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const Nullkiller * ai) :ChainActor(hero, chainMask) { exchangeMap = new HeroExchangeMap(this, ai); @@ -83,7 +83,7 @@ HeroActor::HeroActor( const ChainActor * carrier, const ChainActor * other, const CCreatureSet * army, - const VCAI * ai) + const Nullkiller * ai) :ChainActor(carrier, other, army) { exchangeMap = new HeroExchangeMap(this, ai); @@ -167,7 +167,7 @@ bool HeroExchangeMap::canExchange(const ChainActor * other) if(result) { - TResources resources = ai->myCb->getResourceAmount(); + TResources resources = ai->cb->getResourceAmount(); if(!resources.canAfford(actor->armyCost + other->armyCost)) { @@ -181,7 +181,7 @@ bool HeroExchangeMap::canExchange(const ChainActor * other) return; } - auto upgradeInfo = ai->ah->calculateCreateresUpgrade( + auto upgradeInfo = ai->armyManager->calculateCreateresUpgrade( actor->creatureSet, other->getActorObject(), resources - actor->armyCost - other->armyCost); @@ -189,7 +189,7 @@ bool HeroExchangeMap::canExchange(const ChainActor * other) uint64_t reinforcment = upgradeInfo.upgradeValue; if(other->creatureSet->Slots().size()) - reinforcment += ai->ah->howManyReinforcementsCanGet(actor->creatureSet, other->creatureSet); + reinforcment += ai->armyManager->howManyReinforcementsCanGet(actor->creatureSet, other->creatureSet); #if PATHFINDER_TRACE_LEVEL >= 2 logAi->trace( @@ -240,7 +240,7 @@ HeroActor * HeroExchangeMap::exchange(const ChainActor * other) result = exchangeMap.at(other); else { - TResources availableResources = ai->myCb->getResourceAmount() - actor->armyCost - other->armyCost; + TResources availableResources = ai->cb->getResourceAmount() - actor->armyCost - other->armyCost; CCreatureSet * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources); CCreatureSet * newArmy; @@ -270,7 +270,7 @@ HeroActor * HeroExchangeMap::exchange(const ChainActor * other) CCreatureSet * HeroExchangeMap::tryUpgrade(const CCreatureSet * army, const CGObjectInstance * upgrader, TResources resources) const { - auto upgradeInfo = ai->ah->calculateCreateresUpgrade(army, upgrader, resources); + auto upgradeInfo = ai->armyManager->calculateCreateresUpgrade(army, upgrader, resources); if(!upgradeInfo.upgradeValue) return nullptr; @@ -290,7 +290,7 @@ CCreatureSet * HeroExchangeMap::tryUpgrade(const CCreatureSet * army, const CGOb CCreatureSet * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const { CCreatureSet * target = new HeroExchangeArmy(); - auto bestArmy = ai->ah->getBestArmy(army1, army2); + auto bestArmy = ai->armyManager->getBestArmy(army1, army2); for(auto & slotInfo : bestArmy) { diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index 966cead95..75e700969 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -16,7 +16,7 @@ #include "Actions/SpecialAction.h" class HeroActor; -class VCAI; +class Nullkiller; class HeroExchangeArmy : public CCreatureSet { @@ -72,10 +72,10 @@ private: const HeroActor * actor; std::map exchangeMap; std::map canExchangeCache; - const VCAI * ai; + const Nullkiller * ai; public: - HeroExchangeMap(const HeroActor * actor, const VCAI * ai) + HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai) :actor(actor), ai(ai) { } @@ -105,8 +105,8 @@ public: std::shared_ptr exchangeAction; // chain flags, can be combined meaning hero exchange and so on - HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const VCAI * ai); - HeroActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * army, const VCAI * ai); + HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const Nullkiller * ai); + HeroActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * army, const Nullkiller * ai); virtual bool canExchange(const ChainActor * other) const override; diff --git a/AI/Nullkiller/Pathfinding/PathfindingManager.h b/AI/Nullkiller/Pathfinding/PathfindingManager.h deleted file mode 100644 index 39e126f91..000000000 --- a/AI/Nullkiller/Pathfinding/PathfindingManager.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -* PathfindingManager.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 "../VCAI.h" -#include "AINodeStorage.h" - -class DLL_EXPORT IPathfindingManager -{ -public: - virtual ~IPathfindingManager() = default; - virtual void init(CPlayerSpecificInfoCallback * CB) = 0; - virtual void setAI(VCAI * AI) = 0; - - virtual void updatePaths(std::vector heroes, bool useHeroChain = false) = 0; - virtual std::vector getPathsToTile(const HeroPtr & hero, const int3 & tile) const = 0; - virtual std::vector getPathsToTile(const int3 & tile) const = 0; -}; - -class DLL_EXPORT PathfindingManager : public IPathfindingManager -{ - friend class AIhelper; - -private: - CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback - VCAI * ai; - std::unique_ptr pathfinder; - -public: - PathfindingManager() = default; - PathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI * AI = nullptr); //for tests only - - std::vector getPathsToTile(const HeroPtr & hero, const int3 & tile) const override; - std::vector getPathsToTile(const int3 & tile) const override; - void updatePaths(std::vector heroes, bool useHeroChain = false) override; - - STRONG_INLINE - bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const - { - return pathfinder->isTileAccessible(hero, tile); - } - -private: - void init(CPlayerSpecificInfoCallback * CB) override; - void setAI(VCAI * AI) override; - - Goals::TGoalVec findPaths( - crint3 dest, - bool allowGatherArmy, - HeroPtr hero, - const std::function goalFactory) const; - - Goals::TSubgoal clearWayTo(HeroPtr hero, int3 firstTileToGet) const; -}; diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index dec8a8123..1e91f5bc4 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -9,10 +9,11 @@ */ #include "StdInc.h" #include "AILayerTransitionRule.h" +#include "../../Engine/Nullkiller.h" namespace AIPathfinding { - AILayerTransitionRule::AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr nodeStorage) + AILayerTransitionRule::AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr nodeStorage) :cb(cb), ai(ai), nodeStorage(nodeStorage) { setup(); @@ -54,7 +55,7 @@ namespace AIPathfinding shipyards.push_back(t); } - for(const CGObjectInstance * obj : ai->visitableObjs) + for(const CGObjectInstance * obj : ai->memory->visitableObjs) { if(obj->ID != Obj::TOWN) //towns were handled in the previous loop { diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h index 6cbfd8ea8..722ab6421 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h @@ -23,13 +23,13 @@ namespace AIPathfinding { private: CPlayerSpecificInfoCallback * cb; - VCAI * ai; + Nullkiller * ai; std::map> virtualBoats; std::shared_ptr nodeStorage; std::map> summonableVirtualBoats; public: - AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr nodeStorage); + AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr nodeStorage); virtual void process( const PathNodeInfo & source, diff --git a/AI/Nullkiller/SectorMap.cpp b/AI/Nullkiller/SectorMap.cpp deleted file mode 100644 index 7840be9ed..000000000 --- a/AI/Nullkiller/SectorMap.cpp +++ /dev/null @@ -1,431 +0,0 @@ -/* -* SectorMap.cpp, 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 -* -*/ - -#include "StdInc.h" -#include "SectorMap.h" -#include "VCAI.h" - -#include "../../CCallback.h" -#include "../../lib/mapping/CMap.h" -#include "../../lib/mapObjects/MapObjects.h" -#include "../../lib/CPathfinder.h" -#include "../../lib/CGameState.h" - -extern boost::thread_specific_ptr cb; -extern boost::thread_specific_ptr ai; - -SectorMap::SectorMap() -{ - update(); -} - -SectorMap::SectorMap(HeroPtr h) -{ - update(); - makeParentBFS(h->visitablePos()); -} - -bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t) -{ - if (t->blocked && !t->visitable) - { - sec = NOT_AVAILABLE; - return true; - } - - return false; -} - -bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos) -{ - return markIfBlocked(sec, pos, getTile(pos)); -} - -void SectorMap::update() -{ - visibleTiles = cb->getAllVisibleTiles(); - auto shape = visibleTiles->shape(); - sector.resize(boost::extents[shape[0]][shape[1]][shape[2]]); - - clear(); - int curSector = 3; //0 is invisible, 1 is not explored - - CCallback * cbp = cb.get(); //optimization - foreach_tile_pos([&](crint3 pos) - { - if (retrieveTile(pos) == NOT_CHECKED) - { - if (!markIfBlocked(retrieveTile(pos), pos)) - exploreNewSector(pos, curSector++, cbp); - } - }); - valid = true; -} - -SectorMap::TSectorID & SectorMap::retrieveTileN(SectorMap::TSectorArray & a, const int3 & pos) -{ - return a[pos.x][pos.y][pos.z]; -} - -const SectorMap::TSectorID & SectorMap::retrieveTileN(const SectorMap::TSectorArray & a, const int3 & pos) -{ - return a[pos.x][pos.y][pos.z]; -} - -void SectorMap::clear() -{ - //TODO: rotate to [z][x][y] - auto fow = cb->getVisibilityMap(); - //TODO: any magic to automate this? will need array->array conversion - //std::transform(fow.begin(), fow.end(), sector.begin(), [](const ui8 &f) -> unsigned short - //{ - // return f; //type conversion - //}); - auto width = fow.size(); - auto height = fow.front().size(); - auto depth = fow.front().front().size(); - for (size_t x = 0; x < width; x++) - { - for (size_t y = 0; y < height; y++) - { - for (size_t z = 0; z < depth; z++) - sector[x][y][z] = fow[x][y][z]; - } - } - valid = false; -} - -void SectorMap::exploreNewSector(crint3 pos, int num, CCallback * cbp) -{ - Sector & s = infoOnSectors[num]; - s.id = num; - s.water = getTile(pos)->isWater(); - - std::queue toVisit; - toVisit.push(pos); - while (!toVisit.empty()) - { - int3 curPos = toVisit.front(); - toVisit.pop(); - TSectorID & sec = retrieveTile(curPos); - if (sec == NOT_CHECKED) - { - const TerrainTile * t = getTile(curPos); - if (!markIfBlocked(sec, curPos, t)) - { - if (t->isWater() == s.water) //sector is only-water or only-land - { - sec = num; - s.tiles.push_back(curPos); - foreach_neighbour(cbp, curPos, [&](CCallback * cbp, crint3 neighPos) - { - if (retrieveTile(neighPos) == NOT_CHECKED) - { - toVisit.push(neighPos); - //parent[neighPos] = curPos; - } - const TerrainTile * nt = getTile(neighPos); - if (nt && nt->isWater() != s.water && canBeEmbarkmentPoint(nt, s.water)) - { - s.embarkmentPoints.push_back(neighPos); - } - }); - - if (t->visitable) - { - auto obj = t->visitableObjects.front(); - if (cb->getObj(obj->id, false)) // FIXME: we have to filter invisible objcts like events, but probably TerrainTile shouldn't be used in SectorMap at all - s.visitableObjs.push_back(obj); - } - } - } - } - } - - vstd::removeDuplicates(s.embarkmentPoints); -} - -void SectorMap::write(crstring fname) -{ - std::ofstream out(fname); - for (int k = 0; k < cb->getMapSize().z; k++) - { - for (int j = 0; j < cb->getMapSize().y; j++) - { - for (int i = 0; i < cb->getMapSize().x; i++) - { - out << (int)sector[i][j][k] << '\t'; - } - out << std::endl; - } - out << std::endl; - } -} - -/* -this functions returns one target tile or invalid tile. We will use it to poll possible destinations -For ship construction etc, another function (goal?) is needed -*/ -int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst) -{ - int3 ret(-1, -1, -1); - - int sourceSector = retrieveTile(h->visitablePos()); - int destinationSector = retrieveTile(dst); - - const Sector * src = &infoOnSectors[sourceSector]; - const Sector * dest = &infoOnSectors[destinationSector]; - - if (sourceSector != destinationSector) //use ships, shipyards etc.. - { - if (ai->isAccessibleForHero(dst, h)) //pathfinder can find a way using ships and gates if tile is not blocked by objects - return dst; - - std::map preds; - std::queue sectorQueue; - sectorQueue.push(src); - while (!sectorQueue.empty()) - { - const Sector * s = sectorQueue.front(); - sectorQueue.pop(); - - for (int3 ep : s->embarkmentPoints) - { - Sector * neigh = &infoOnSectors[retrieveTile(ep)]; - //preds[s].push_back(neigh); - if (!preds[neigh]) - { - preds[neigh] = s; - sectorQueue.push(neigh); - } - } - } - - if (!preds[dest]) - { - //write("test.txt"); - - return ret; - //throw cannotFulfillGoalException(boost::str(boost::format("Cannot find connection between sectors %d and %d") % src->id % dst->id)); - } - - std::vector toTraverse; - toTraverse.push_back(dest); - while (toTraverse.back() != src) - { - toTraverse.push_back(preds[toTraverse.back()]); - } - - if (preds[dest]) - { - //TODO: would be nice to find sectors in loop - const Sector * sectorToReach = toTraverse.at(toTraverse.size() - 2); - - if (!src->water && sectorToReach->water) //embark - { - //embark on ship -> look for an EP with a boat - auto firstEP = boost::find_if(src->embarkmentPoints, [=](crint3 pos) -> bool - { - const TerrainTile * t = getTile(pos); - if (t && t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT) - { - if (retrieveTile(pos) == sectorToReach->id) - return true; - } - return false; - }); - - if (firstEP != src->embarkmentPoints.end()) - { - return *firstEP; - } - else - { - //we need to find a shipyard with an access to the desired sector's EP - //TODO what about Summon Boat spell? - std::vector shipyards; - for (const CGTownInstance * t : cb->getTownsInfo()) - { - if (t->hasBuilt(BuildingID::SHIPYARD)) - shipyards.push_back(t); - } - - for (const CGObjectInstance * obj : ai->getFlaggedObjects()) - { - if (obj->ID != Obj::TOWN) //towns were handled in the previous loop - { - if (const IShipyard * shipyard = IShipyard::castFrom(obj)) - shipyards.push_back(shipyard); - } - } - - shipyards.erase(boost::remove_if(shipyards, [=](const IShipyard * shipyard) -> bool - { - return shipyard->shipyardStatus() != 0 || retrieveTile(shipyard->bestLocation()) != sectorToReach->id; - }), shipyards.end()); - - if (!shipyards.size()) - { - //TODO consider possibility of building shipyard in a town - return ret; - - //throw cannotFulfillGoalException("There is no known shipyard!"); - } - - //we have only shipyards that possibly can build ships onto the appropriate EP - auto ownedGoodShipyard = boost::find_if(shipyards, [](const IShipyard * s) -> bool - { - return s->o->tempOwner == ai->playerID; - }); - - if (ownedGoodShipyard != shipyards.end()) - { - const IShipyard * s = *ownedGoodShipyard; - TResources shipCost; - s->getBoatCost(shipCost); - if (cb->getResourceAmount().canAfford(shipCost)) - { - int3 ret = s->bestLocation(); - cb->buildBoat(s); //TODO: move actions elsewhere - return ret; - } - else - { - //TODO gather res - return ret; - - //throw cannotFulfillGoalException("Not enough resources to build a boat"); - } - } - else - { - //TODO pick best shipyard to take over - return shipyards.front()->o->visitablePos(); - } - } - } - else if (src->water && !sectorToReach->water) - { - //TODO - //disembark - return ret; - } - else //use subterranean gates - not needed since gates are now handled via Pathfinder - { - return ret; - //throw cannotFulfillGoalException("Land-land and water-water inter-sector transitions are not implemented!"); - } - } - else - { - return ret; - //throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?"); - } - } - else //tiles are in same sector - { - return findFirstVisitableTile(h, dst); - } -} - -int3 SectorMap::findFirstVisitableTile(HeroPtr h, crint3 dst) -{ - int3 ret(-1, -1, -1); - int3 curtile = dst; - - while (curtile != h->visitablePos()) - { - auto topObj = cb->getTopObj(curtile); - if (topObj && topObj->ID == Obj::HERO && topObj != h.h) - { - if (cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES) - { - logAi->warn("Another allied hero stands in our way"); - return ret; - } - } - if (ai->myCb->getPathsInfo(h.get())->getPathInfo(curtile)->reachable()) - { - return curtile; - } - else - { - auto i = parent.find(curtile); - if (i != parent.end()) - { - assert(curtile != i->second); - curtile = i->second; - } - else - { - return ret; - //throw cannotFulfillGoalException("Unreachable tile in sector? Should not happen!"); - } - } - } - return ret; -} - -void SectorMap::makeParentBFS(crint3 source) -{ - parent.clear(); - - int mySector = retrieveTile(source); - std::queue toVisit; - toVisit.push(source); - while (!toVisit.empty()) - { - int3 curPos = toVisit.front(); - toVisit.pop(); - TSectorID & sec = retrieveTile(curPos); - assert(sec == mySector); //consider only tiles from the same sector - UNUSED(sec); - - foreach_neighbour(curPos, [&](crint3 neighPos) - { - if (retrieveTile(neighPos) == mySector && !vstd::contains(parent, neighPos)) - { - if (cb->canMoveBetween(curPos, neighPos)) - { - toVisit.push(neighPos); - parent[neighPos] = curPos; - } - } - }); - } -} - -SectorMap::TSectorID & SectorMap::retrieveTile(crint3 pos) -{ - return retrieveTileN(sector, pos); -} - -TerrainTile * SectorMap::getTile(crint3 pos) const -{ - //out of bounds access should be handled by boost::multi_array - //still we cached this array to avoid any checks - return visibleTiles->operator[](pos.x)[pos.y][pos.z]; -} - -std::vector SectorMap::getNearbyObjs(HeroPtr h, bool sectorsAround) -{ - const Sector * heroSector = &infoOnSectors[retrieveTile(h->visitablePos())]; - if (sectorsAround) - { - std::vector ret; - for (auto embarkPoint : heroSector->embarkmentPoints) - { - const Sector * embarkSector = &infoOnSectors[retrieveTile(embarkPoint)]; - range::copy(embarkSector->visitableObjs, std::back_inserter(ret)); - } - return ret; - } - return heroSector->visitableObjs; -} \ No newline at end of file diff --git a/AI/Nullkiller/SectorMap.h b/AI/Nullkiller/SectorMap.h deleted file mode 100644 index 14c8a75ca..000000000 --- a/AI/Nullkiller/SectorMap.h +++ /dev/null @@ -1,70 +0,0 @@ -/* -* SectorMap.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 "AIUtility.h" - -enum -{ - NOT_VISIBLE = 0, - NOT_CHECKED = 1, - NOT_AVAILABLE -}; - -struct SectorMap -{ - //a sector is set of tiles that would be mutually reachable if all visitable objs would be passable (incl monsters) - struct Sector - { - int id; - std::vector tiles; - std::vector embarkmentPoints; //tiles of other sectors onto which we can (dis)embark - std::vector visitableObjs; - bool water; //all tiles of sector are land or water - Sector() - { - id = -1; - water = false; - } - }; - - typedef unsigned short TSectorID; //smaller than int to allow -1 value. Max number of sectors 65K should be enough for any proper map. - typedef boost::multi_array TSectorArray; - - bool valid; //some kind of lazy eval - std::map parent; - TSectorArray sector; - //std::vector>> pathfinderSector; - - std::map infoOnSectors; - std::shared_ptr> visibleTiles; - - SectorMap(); - SectorMap(HeroPtr h); - void update(); - void clear(); - void exploreNewSector(crint3 pos, int num, CCallback * cbp); - void write(crstring fname); - - bool markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t); - bool markIfBlocked(TSectorID & sec, crint3 pos); - TSectorID & retrieveTile(crint3 pos); - TSectorID & retrieveTileN(TSectorArray & vectors, const int3 & pos); - const TSectorID & retrieveTileN(const TSectorArray & vectors, const int3 & pos); - TerrainTile * getTile(crint3 pos) const; - std::vector getNearbyObjs(HeroPtr h, bool sectorsAround); - - void makeParentBFS(crint3 source); - - int3 firstTileToGet(HeroPtr h, crint3 dst); //if h wants to reach tile dst, which tile he should visit to clear the way? - int3 findFirstVisitableTile(HeroPtr h, crint3 dst); -}; diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index 0d8110511..1a3c9d11d 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -9,7 +9,6 @@ */ #include "StdInc.h" #include "VCAI.h" -#include "FuzzyHelper.h" #include "Goals/Goals.h" #include "../../lib/UnlockGuard.h" @@ -23,10 +22,8 @@ #include "../../lib/serializer/BinarySerializer.h" #include "../../lib/serializer/BinaryDeserializer.h" -#include "AIhelper.h" #include "Engine/Nullkiller.h" -extern FuzzyHelper * fh; class CGVisitableOPW; @@ -68,14 +65,11 @@ VCAI::VCAI() makingTurn = nullptr; destinationTeleport = ObjectInstanceID(); destinationTeleportPos = int3(-1); - - ah = new AIhelper(); - ah->setAI(this); + nullkiller.reset(new Nullkiller()); } VCAI::~VCAI() { - delete ah; LOG_TRACE(logAi); finish(); } @@ -109,9 +103,7 @@ void VCAI::heroMoved(const TryMoveHero & details) { if(o1->ID == Obj::SUBTERRANEAN_GATE && o1->ID == o2->ID) // We need to only add subterranean gates in knownSubterraneanGates. Used for features not yet ported to use teleport channels { - knownSubterraneanGates[o1] = o2; - knownSubterraneanGates[o2] = o1; - logAi->debug("Found a pair of subterranean gates between %s and %s!", from.toString(), to.toString()); + nullkiller->memory->addSubterraneanGate(o1, o2); } } } @@ -238,12 +230,7 @@ void VCAI::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * vi if(start && visitedObj) //we can end visit with null object, anyway { - markObjectVisited(visitedObj); - //TODO: what if we visited one-time visitable object that was reserved by another hero (shouldn't, but..) - if (visitedObj->ID == Obj::HERO) - { - visitedHeroes[visitor].insert(HeroPtr(dynamic_cast(visitedObj))); - } + nullkiller->memory->markObjectVisited(visitedObj); } status.heroVisit(visitedObj, start); @@ -259,8 +246,6 @@ void VCAI::heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * to { LOG_TRACE(logAi); NET_EVENT_HANDLER; - //buildArmyIn(town); - //moveCreaturesToHero(town); } void VCAI::tileHidden(const std::unordered_set & pos) @@ -268,7 +253,7 @@ void VCAI::tileHidden(const std::unordered_set & pos) LOG_TRACE(logAi); NET_EVENT_HANDLER; - validateVisitableObjs(); + nullkiller->memory->removeInvisibleObjects(myCb.get()); clearPathsInfo(); } @@ -360,22 +345,7 @@ void VCAI::objectRemoved(const CGObjectInstance * obj) LOG_TRACE(logAi); NET_EVENT_HANDLER; - vstd::erase_if_present(visitableObjs, obj); - vstd::erase_if_present(alreadyVisited, obj); - - //TODO: Find better way to handle hero boat removal - if(auto hero = dynamic_cast(obj)) - { - if(hero->boat) - { - vstd::erase_if_present(visitableObjs, hero->boat); - vstd::erase_if_present(alreadyVisited, hero->boat); - } - } - - //TODO - //there are other places where CGObjectinstance ptrs are stored... - // + nullkiller->memory->removeFromMemory(obj); if(obj->ID == Obj::HERO && obj->tempOwner == playerID) { @@ -398,8 +368,6 @@ void VCAI::playerBonusChanged(const Bonus & bonus, bool gain) void VCAI::heroCreated(const CGHeroInstance * h) { LOG_TRACE(logAi); - if(h->visitedTown) - townVisitsThisWeek[HeroPtr(h)].insert(h->visitedTown); NET_EVENT_HANDLER; } @@ -480,10 +448,10 @@ void VCAI::objectPropertyChanged(const SetObjectProperty * sop) if(relations == PlayerRelations::ENEMIES) { //we want to visit objects owned by oppponents - addVisitableObj(obj); // TODO: Remove once save compatability broken. In past owned objects were removed from this set - vstd::erase_if_present(alreadyVisited, obj); + //addVisitableObj(obj); // TODO: Remove once save compatability broken. In past owned objects were removed from this set + nullkiller->memory->markObjectUnvisited(obj); } - else if(relations == PlayerRelations::SAME_PLAYER && obj->ID == Obj::TOWN && nullkiller) + else if(relations == PlayerRelations::SAME_PLAYER && obj->ID == Obj::TOWN) { // reevaluate defence for a new town nullkiller->dangerHitMap->reset(); @@ -523,20 +491,12 @@ void VCAI::init(std::shared_ptr CB) myCb = CB; cbc = CB; - ah->init(CB.get()); - - NET_EVENT_HANDLER; //sets ah->rm->cb + NET_EVENT_HANDLER; playerID = *myCb->getMyColor(); myCb->waitTillRealize = true; myCb->unlockGsWhenWaiting = true; - if(!fh) - fh = new FuzzyHelper(); - - //if(playerID.getStr(false) == "blue") - { - nullkiller.reset(new Nullkiller()); - } + nullkiller->init(CB, playerID); retrieveVisitableObjs(); } @@ -554,7 +514,7 @@ void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID); NET_EVENT_HANDLER; status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->name % hero->level)); - requestActionASAP([=](){ answerQuery(queryID, ah->selectBestSkill(hero, skills)); }); + requestActionASAP([=](){ answerQuery(queryID, nullkiller->heroManager->selectBestSkill(hero, skills)); }); } void VCAI::commanderGotLevel(const CCommanderInstance * commander, std::vector skills, QueryID queryID) @@ -589,7 +549,7 @@ void VCAI::showBlockingDialog(const std::string & text, const std::vectorevaluateDanger(target, hero.get()) / (float)hero->getTotalStrength(); + auto ratio = (float)nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()) / (float)hero->getTotalStrength(); bool dangerUnknown = ratio == 0; bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT); @@ -615,7 +575,7 @@ void VCAI::showBlockingDialog(const std::string & text, const std::vectorgetHeroRole(hero) != HeroRole::MAIN) + && nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN) { sel = 1; // for now lets pick gold from a chest. } @@ -626,15 +586,13 @@ void VCAI::showBlockingDialog(const std::string & text, const std::vectorpassability = TeleportChannel::IMPASSABLE; + nullkiller->memory->knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE; } else if(destinationTeleport != ObjectInstanceID() && destinationTeleportPos.valid()) { @@ -700,12 +658,8 @@ void VCAI::saveGame(BinarySerializer & h, const int version) { LOG_TRACE_PARAMS(logAi, "version '%i'", version); NET_EVENT_HANDLER; - validateVisitableObjs(); + nullkiller->memory->removeInvisibleObjects(myCb.get()); - #if 0 - //disabled due to issue 2890 - registerGoals(h); - #endif // 0 CAdventureAI::saveGame(h, version); serializeInternal(h, version); } @@ -760,7 +714,6 @@ void VCAI::makeTurn() if(cb->getDate(Date::DAY_OF_WEEK) == 1) { - townVisitsThisWeek.clear(); std::vector objs; retrieveVisitableObjs(objs, true); @@ -769,14 +722,11 @@ void VCAI::makeTurn() if(isWeeklyRevisitable(obj)) { addVisitableObj(obj); - vstd::erase_if_present(alreadyVisited, obj); + nullkiller->memory->markObjectUnvisited(obj); } } } - markHeroAbleToExplore(primaryHero()); - visitedHeroes.clear(); - if(cb->getDate(Date::DAY) == 1) { retrieveVisitableObjs(); @@ -807,18 +757,6 @@ void VCAI::makeTurn() endTurn(); } -std::vector VCAI::getMyHeroes() const -{ - std::vector ret; - - for(auto h : cb->getHeroesInfo()) - { - ret.push_back(h); - } - - return ret; -} - void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) { LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString()); @@ -832,17 +770,8 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) { makePossibleUpgrades(h.get()); - if(!nullkiller || !h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero)) - moveCreaturesToHero(h->visitedTown); - - townVisitsThisWeek[h].insert(h->visitedTown); - - if(!ai->nullkiller) - { - ah->update(); - } - - if(ah->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() && cb->getResourceAmount(Res::GOLD) >= GameConstants::SPELLBOOK_GOLD_COST) + if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() + && cb->getResourceAmount(Res::GOLD) >= GameConstants::SPELLBOOK_GOLD_COST) { if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1)) cb->buyArtifact(h.get(), ArtifactID::SPELLBOOK); @@ -867,7 +796,7 @@ void VCAI::pickBestCreatures(const CArmedInstance * destinationArmy, const CArme { const CArmedInstance * armies[] = {destinationArmy, source}; - auto bestArmy = ah->getSortedSlots(destinationArmy, source); + auto bestArmy = nullkiller->armyManager->getSortedSlots(destinationArmy, source); //foreach best type -> iterate over slots in both armies and if it's the appropriate type, send it to the slot where it belongs for(SlotID i = SlotID(0); i.getNum() < bestArmy.size() && i.validSlot(); i.advance(1)) //i-th strongest creature type will go to i-th slot @@ -886,7 +815,7 @@ void VCAI::pickBestCreatures(const CArmedInstance * destinationArmy, const CArme && source->stacksCount() == 1 && (!destinationArmy->hasStackAtSlot(i) || destinationArmy->getCreature(i) == targetCreature)) { - auto weakest = ah->getWeakestCreature(bestArmy); + auto weakest = nullkiller->armyManager->getWeakestCreature(bestArmy); if(weakest->creature == targetCreature) { @@ -1040,54 +969,6 @@ void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruit } } -bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, boost::optional movementCostLimit) -{ - int3 op = obj->visitablePos(); - auto paths = ah->getPathsToTile(h, op); - - for(const auto & path : paths) - { - if(movementCostLimit && movementCostLimit.get() < path.movementCost()) - return false; - - if(isGoodForVisit(obj, h, path)) - return true; - } - - return false; -} - -bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath & path) const -{ - const int3 pos = obj->visitablePos(); - const int3 targetPos = path.firstTileToGet(); - if (!targetPos.valid()) - return false; - if (!isTileNotReserved(h.get(), targetPos)) - return false; - if (obj->wasVisited(playerID)) - return false; - if (cb->getPlayerRelations(playerID, obj->tempOwner) != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj)) - return false; // Otherwise we flag or get weekly resources / creatures - if (!isSafeToVisit(h, pos)) - return false; - if (!shouldVisit(h, obj)) - return false; - if (vstd::contains(alreadyVisited, obj)) - return false; - if (vstd::contains(reservedObjs, obj)) - return false; - - // TODO: looks extra if we already have AIPath - //if (!isAccessibleForHero(targetPos, h)) - // return false; - - const CGObjectInstance * topObj = cb->getVisitableObjs(obj->visitablePos()).back(); //it may be hero visiting this obj - //we don't try visiting object on which allied or owned hero stands - // -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited - return !(topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES); //all of the following is met -} - bool VCAI::isTileNotReserved(const CGHeroInstance * h, int3 t) const { if(t.valid()) @@ -1150,19 +1031,6 @@ void VCAI::waitTillFree() status.waitTillFree(); } -void VCAI::markObjectVisited(const CGObjectInstance * obj) -{ - if(!obj) - return; - if(dynamic_cast(obj)) //we may want to visit it with another hero - return; - if(dynamic_cast(obj)) //or another time - return; - if(obj->ID == Obj::MONSTER) - return; - alreadyVisited.insert(obj); -} - void VCAI::markHeroUnableToExplore(HeroPtr h) { heroesUnableToExplore.insert(h); @@ -1180,40 +1048,6 @@ void VCAI::clearPathsInfo() heroesUnableToExplore.clear(); } -void VCAI::validateVisitableObjs() -{ - std::string errorMsg; - auto shouldBeErased = [&](const CGObjectInstance * obj) -> bool - { - if(obj) - return !cb->getObj(obj->id, false); // no verbose output needed as we check object visibility - else - return true; - }; - - //errorMsg is captured by ref so lambda will take the new text - errorMsg = " shouldn't be on the visitable objects list!"; - vstd::erase_if(visitableObjs, shouldBeErased); - - //FIXME: how comes our own heroes become inaccessible? - vstd::erase_if(reservedHeroesMap, [](std::pair> hp) -> bool - { - return !hp.first.get(true); - }); - for(auto & p : reservedHeroesMap) - { - errorMsg = " shouldn't be on list for hero " + p.first->name + "!"; - vstd::erase_if(p.second, shouldBeErased); - } - - errorMsg = " shouldn't be on the reserved objs list!"; - vstd::erase_if(reservedObjs, shouldBeErased); - - //TODO overkill, hidden object should not be removed. However, we can't know if hidden object is erased from game. - errorMsg = " shouldn't be on the already visited objs list!"; - vstd::erase_if(alreadyVisited, shouldBeErased); -} - void VCAI::retrieveVisitableObjs(std::vector & out, bool includeOwned) const { foreach_tile_pos([&](const int3 & pos) @@ -1241,7 +1075,7 @@ void VCAI::retrieveVisitableObjs() std::vector VCAI::getFlaggedObjects() const { std::vector ret; - for(const CGObjectInstance * obj : visitableObjs) + for(const CGObjectInstance * obj : nullkiller->memory->visitableObjs) { if(obj->tempOwner == playerID) ret.push_back(obj); @@ -1254,45 +1088,14 @@ void VCAI::addVisitableObj(const CGObjectInstance * obj) if(obj->ID == Obj::EVENT) return; - visitableObjs.insert(obj); - - // All teleport objects seen automatically assigned to appropriate channels - auto teleportObj = dynamic_cast(obj); - if(teleportObj) - CGTeleport::addToChannel(knownTeleportChannels, teleportObj); + nullkiller->memory->addVisitableObject(obj); if(obj->ID == Obj::HERO && cb->getPlayerRelations(obj->tempOwner, playerID) == PlayerRelations::ENEMIES) { - if(nullkiller) nullkiller->dangerHitMap->reset(); + nullkiller->dangerHitMap->reset(); } } -const CGObjectInstance * VCAI::lookForArt(int aid) const -{ - for(const CGObjectInstance * obj : ai->visitableObjs) - { - if(obj->ID == Obj::ARTIFACT && obj->subID == aid) - return obj; - } - - return nullptr; - - //TODO what if more than one artifact is available? return them all or some slection criteria -} - -bool VCAI::isAccessible(const int3 & pos) const -{ - //TODO precalculate for speed - - for(const CGHeroInstance * h : cb->getHeroesInfo()) - { - if(isAccessibleForHero(pos, h)) - return true; - } - - return false; -} - HeroPtr VCAI::getHeroWithGrail() const { for(const CGHeroInstance * h : cb->getHeroesInfo()) @@ -1303,34 +1106,6 @@ HeroPtr VCAI::getHeroWithGrail() const return nullptr; } -const CGObjectInstance * VCAI::getUnvisitedObj(const std::function & predicate) -{ - //TODO smarter definition of unvisited - for(const CGObjectInstance * obj : visitableObjs) - { - if(predicate(obj) && !vstd::contains(alreadyVisited, obj)) - return obj; - } - return nullptr; -} - -bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies) const -{ - // Don't visit tile occupied by allied hero - if(!includeAllies) - { - for(auto obj : cb->getVisitableObjs(pos)) - { - if(obj->ID == Obj::HERO && cb->getPlayerRelations(ai->playerID, obj->tempOwner) != PlayerRelations::ENEMIES) - { - if(obj != h.get()) - return false; - } - } - } - return cb->getPathsInfo(h.get())->getPathInfo(pos)->reachable(); -} - bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) { if(h->inTownGarrison && h->visitedTown) @@ -1363,6 +1138,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) assert(cb->getVisitableObjs(dst).size() > 1); //there's no point in revisiting tile where there is no visitable object cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true)); afterMovementCheck(); // TODO: is it feasible to hero get killed there if game work properly? + // If revisiting, teleport probing is never done, and so the entries into the list would remain unused and uncleared + teleportChannelProbingList.clear(); // not sure if AI can currently reconsider to attack bank while staying on it. Check issue 2084 on mantis for more information. ret = true; } @@ -1456,7 +1233,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) doTeleportMovement(destTeleportObj->id, nextCoord); if(teleportChannelProbingList.size()) doChannelProbing(); - markObjectVisited(destTeleportObj); //FIXME: Monoliths are not correctly visited + nullkiller->memory->markObjectVisited(destTeleportObj); //FIXME: Monoliths are not correctly visited continue; } @@ -1596,17 +1373,6 @@ void VCAI::tryRealize(Goals::Trade & g) //trade } } -void VCAI::tryRealize(Goals::Invalid & g) -{ - throw cannotFulfillGoalException("I don't know how to fulfill this!"); -} - -void VCAI::tryRealize(Goals::AbstractGoal & g) -{ - logAi->debug("Attempting realizing goal with code %s", g.toString()); - throw cannotFulfillGoalException("Unknown type of goal !"); -} - const CGTownInstance * VCAI::findTownWithTavern() const { for(const CGTownInstance * t : cb->getTownsInfo()) @@ -1616,15 +1382,6 @@ const CGTownInstance * VCAI::findTownWithTavern() const return nullptr; } -HeroPtr VCAI::primaryHero() const -{ - auto hs = cb->getHeroesInfo(); - if (hs.empty()) - return nullptr; - else - return *boost::max_element(hs, compareHeroStrength); -} - void VCAI::endTurn() { logAi->info("Player %d (%s) ends turn", playerID, playerID.getStr()); @@ -1663,9 +1420,9 @@ void VCAI::recruitHero(const CGTownInstance * t, bool throwing) if(heroes[1]->getTotalStrength() > hero->getTotalStrength()) hero = heroes[1]; } - cb->recruitHero(t, hero); - ai->ah->update(); + cb->recruitHero(t, hero); + nullkiller->heroManager->update(); if(t->visitingHero) moveHeroToTile(t->visitablePos(), t->visitingHero.get()); @@ -1750,18 +1507,9 @@ void VCAI::validateObject(const CGObjectInstance * obj) void VCAI::validateObject(ObjectIdRef obj) { - auto matchesId = [&](const CGObjectInstance * hlpObj) -> bool - { - return hlpObj->id == obj.id; - }; if(!obj) { - vstd::erase_if(visitableObjs, matchesId); - - for(auto & p : reservedHeroesMap) - vstd::erase_if(p.second, matchesId); - - vstd::erase_if(reservedObjs, matchesId); + nullkiller->memory->removeFromMemory(obj); } } @@ -1918,150 +1666,4 @@ void AIStatus::setChannelProbing(bool ongoing) bool AIStatus::channelProbing() { return ongoingChannelProbing; -} - - - -bool isWeeklyRevisitable(const CGObjectInstance * obj) -{ - //TODO: allow polling of remaining creatures in dwelling - if(dynamic_cast(obj)) // ensures future compatibility, unlike IDs - return true; - if(dynamic_cast(obj)) - return true; - if(dynamic_cast(obj)) //banks tend to respawn often in mods - return true; - - switch(obj->ID) - { - case Obj::STABLES: - case Obj::MAGIC_WELL: - case Obj::HILL_FORT: - return true; - case Obj::BORDER_GATE: - case Obj::BORDERGUARD: - return (dynamic_cast(obj))->wasMyColorVisited(ai->playerID); //FIXME: they could be revisited sooner than in a week - } - return false; -} - -bool shouldVisit(HeroPtr h, const CGObjectInstance * obj) -{ - switch(obj->ID) - { - case Obj::TOWN: - case Obj::HERO: //never visit our heroes at random - return obj->tempOwner != h->tempOwner; //do not visit our towns at random - case Obj::BORDER_GATE: - { - for(auto q : ai->myCb->getMyQuests()) - { - if(q.obj == obj) - { - return false; // do not visit guards or gates when wandering - } - } - return true; //we don't have this quest yet - } - case Obj::BORDERGUARD: //open borderguard if possible - return (dynamic_cast(obj))->wasMyColorVisited(ai->playerID); - case Obj::SEER_HUT: - case Obj::QUEST_GUARD: - { - for(auto q : ai->myCb->getMyQuests()) - { - if(q.obj == obj) - { - if(q.quest->checkQuest(h.h)) - return true; //we completed the quest - else - return false; //we can't complete this quest - } - } - return true; //we don't have this quest yet - } - case Obj::CREATURE_GENERATOR1: - { - if(obj->tempOwner != h->tempOwner) - return true; //flag just in case - - const CGDwelling * d = dynamic_cast(obj); - - for(auto level : d->creatures) - { - for(auto c : level.second) - { - if(level.first - && h->getSlotFor(CreatureID(c)) != SlotID() - && cb->getResourceAmount().canAfford(c.toCreature()->cost)) - { - return true; - } - } - } - - return false; - } - case Obj::HILL_FORT: - { - for(auto slot : h->Slots()) - { - if(slot.second->type->upgrades.size()) - return true; //TODO: check price? - } - return false; - } - case Obj::MONOLITH_ONE_WAY_ENTRANCE: - case Obj::MONOLITH_ONE_WAY_EXIT: - case Obj::MONOLITH_TWO_WAY: - case Obj::WHIRLPOOL: - return false; - case Obj::SCHOOL_OF_MAGIC: - case Obj::SCHOOL_OF_WAR: - { - if (cb->getResourceAmount(Res::GOLD) < 1000) - return false; - break; - } - case Obj::LIBRARY_OF_ENLIGHTENMENT: - if(h->level < 12) - return false; - break; - case Obj::TREE_OF_KNOWLEDGE: - { - if(ai->ah->getHeroRole(h) == HeroRole::SCOUT) - return false; - - TResources myRes = cb->getResourceAmount(); - if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10) - return false; - break; - } - case Obj::MAGIC_WELL: - return h->mana < h->manaLimit(); - case Obj::PRISON: - return ai->myCb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER; - case Obj::TAVERN: - { - //TODO: make AI actually recruit heroes - //TODO: only on request - if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) - return false; - else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) - return false; - break; - } - case Obj::BOAT: - return false; - //Boats are handled by pathfinder - case Obj::EYE_OF_MAGI: - return false; //this object is useless to visit, but could be visited indefinitely - } - - if(obj->wasVisited(*h)) //it must pointer to hero instance, heroPtr calls function wasVisited(ui8 player); - return false; - - return true; -} - - +} \ No newline at end of file diff --git a/AI/Nullkiller/VCAI.h b/AI/Nullkiller/VCAI.h index aa75b7a94..78d3d712c 100644 --- a/AI/Nullkiller/VCAI.h +++ b/AI/Nullkiller/VCAI.h @@ -26,7 +26,6 @@ struct QuestInfo; -class AIhelper; class Nullkiller; class AIStatus @@ -75,18 +74,10 @@ public: class DLL_EXPORT VCAI : public CAdventureAI { public: - - friend class FuzzyHelper; - friend class ResourceManager; - friend class BuildingManager; - - std::map> knownTeleportChannels; - std::map knownSubterraneanGates; ObjectInstanceID destinationTeleport; int3 destinationTeleportPos; std::vector teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored //std::vector visitedThisWeek; //only OPWs - std::map> townVisitsThisWeek; std::set invalidPathHeroes; //FIXME, just a workaround std::map lockedHeroes; //TODO: allow non-elementar objectives @@ -94,8 +85,6 @@ public: std::set heroesUnableToExplore; //these heroes will not be polled for exploration in current state of game //sets are faster to search, also do not contain duplicates - std::set visitableObjs; - std::set alreadyVisited; std::set reservedObjs; //to be visited by specific hero std::map> visitedHeroes; //visited this turn //FIXME: this is just bug workaround @@ -110,7 +99,6 @@ private: public: ObjectInstanceID selectedObject; - AIhelper * ah; std::unique_ptr nullkiller; VCAI(); @@ -119,8 +107,6 @@ public: //TODO: use only smart pointers? void tryRealize(Goals::DigAtTile & g); void tryRealize(Goals::Trade & g); - void tryRealize(Goals::Invalid & g); - void tryRealize(Goals::AbstractGoal & g); bool isTileNotReserved(const CGHeroInstance * h, int3 t) const; //the tile is not occupied by allied hero and the object is not reserved @@ -192,8 +178,6 @@ public: void endTurn(); void recruitHero(const CGTownInstance * t, bool throwing = false); - bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, boost::optional movementCostLimit = boost::none); - bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath & path) const; //void recruitCreatures(const CGTownInstance * t); void recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter); void pickBestCreatures(const CArmedInstance * army, const CArmedInstance * source); //called when we can't find a slot for new stack @@ -209,7 +193,6 @@ public: void waitTillFree(); void addVisitableObj(const CGObjectInstance * obj); - void markObjectVisited(const CGObjectInstance * obj); void markHeroUnableToExplore(HeroPtr h); void markHeroAbleToExplore(HeroPtr h); @@ -218,25 +201,15 @@ public: void validateObject(const CGObjectInstance * obj); //checks if object is still visible and if not, removes references to it void validateObject(ObjectIdRef obj); //checks if object is still visible and if not, removes references to it - void validateVisitableObjs(); void retrieveVisitableObjs(std::vector & out, bool includeOwned = false) const; void retrieveVisitableObjs(); virtual std::vector getFlaggedObjects() const; - const CGObjectInstance * lookForArt(int aid) const; - bool isAccessible(const int3 & pos) const; HeroPtr getHeroWithGrail() const; - const CGObjectInstance * getUnvisitedObj(const std::function & predicate); - bool isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies = false) const; - //optimization - use one SM for every hero call - const CGTownInstance * findTownWithTavern() const; bool canRecruitAnyHero(const CGTownInstance * t = NULL) const; - std::vector getMyHeroes() const; - HeroPtr primaryHero() const; - void requestSent(const CPackForServer * pack, int requestID) override; void answerQuery(QueryID queryID, int selection); //special function that can be called ONLY from game events handling thread and will send request ASAP @@ -271,10 +244,12 @@ public: template void serializeInternal(Handler & h, const int version) { - h & knownTeleportChannels; - h & knownSubterraneanGates; + std::map> ignore; + + h & nullkiller->memory->knownTeleportChannels; + h & nullkiller->memory->knownSubterraneanGates; h & destinationTeleport; - h & townVisitsThisWeek; + h & ignore; #if 0 //disabled due to issue 2890 @@ -318,8 +293,8 @@ public: #endif h & reservedHeroesMap; //FIXME: cannot instantiate abstract class - h & visitableObjs; - h & alreadyVisited; + h & nullkiller->memory->visitableObjs; + h & nullkiller->memory->alreadyVisited; h & reservedObjs; if (version < 788 && !h.saving) {