diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index 4a7212a13..b38d728e0 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -101,16 +101,16 @@ const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const if(h) { auto obj = cb->getObj(hid); - const bool owned = obj && obj->tempOwner == ai->playerID; + //const bool owned = obj && obj->tempOwner == ai->playerID; - if(doWeExpectNull && !owned) + if(doWeExpectNull && !obj) { return nullptr; } else { assert(obj); - assert(owned); + //assert(owned); } } diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 118315fee..b123afc06 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -9,6 +9,7 @@ */ #include "StdInc.h" #include "../VCAI.h" +#include "../Engine/Nullkiller.h" #include "../AIhelper.h" #include "../Goals/ExecuteHeroChain.h" #include "CaptureObjectsBehavior.h" @@ -47,6 +48,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() continue; const int3 pos = objToVisit->visitablePos(); + auto paths = ai->ah->getPathsToTile(pos); std::vector> waysToVisitObj; std::shared_ptr closestWay; @@ -61,6 +63,14 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() logAi->trace("Path found %s", path.toString()); #endif + if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path)) + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace("Ignore path. Target hero can be killed by enemy"); +#endif + continue; + } + if(!shouldVisit(path.targetHero, objToVisit)) continue; diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index 37bd936da..d159394ec 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -55,6 +55,7 @@ set(VCAI_SRCS Goals/ExecuteHeroChain.cpp Engine/Nullkiller.cpp Engine/PriorityEvaluator.cpp + Engine/DangerHitMapAnalyzer.cpp Behaviors/Behavior.cpp Behaviors/CaptureObjectsBehavior.cpp Behaviors/RecruitHeroBehavior.cpp @@ -117,6 +118,7 @@ set(VCAI_HEADERS Goals/Goals.h Engine/Nullkiller.h Engine/PriorityEvaluator.h + Engine/DangerHitMapAnalyzer.h Behaviors/Behavior.h Behaviors/CaptureObjectsBehavior.h Behaviors/RecruitHeroBehavior.h diff --git a/AI/Nullkiller/Engine/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Engine/DangerHitMapAnalyzer.cpp new file mode 100644 index 000000000..9dc8ef00e --- /dev/null +++ b/AI/Nullkiller/Engine/DangerHitMapAnalyzer.cpp @@ -0,0 +1,65 @@ +#include "StdInc.h" +#include "DangerHitMapAnalyzer.h" + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; + +void DangerHitMapAnalyzer::updateHitMap() +{ + auto mapSize = cb->getMapSize(); + hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]); + + std::map> heroes; + + for(const CGObjectInstance * obj : ai->visitableObjs) + { + if(obj->ID == Obj::HERO) + { + HeroPtr hero = dynamic_cast(obj); + + heroes[hero->tempOwner].push_back(hero); + } + } + + foreach_tile_pos([&](const int3 & pos){ + hitMap[pos.x][pos.y][pos.z].reset(); + }); + + for(auto pair : heroes) + { + ai->ah->updatePaths(pair.second, false); + + foreach_tile_pos([&](const int3 & pos){ + for(AIPath & path : ai->ah->getPathsToTile(pos)) + { + auto tileDanger = path.getHeroStrength(); + auto turn = path.turn(); + auto & node = hitMap[pos.x][pos.y][pos.z]; + + if(tileDanger > node.maximumDanger.danger + || tileDanger == node.maximumDanger.danger && node.maximumDanger.turn > turn) + { + node.maximumDanger.danger = tileDanger; + node.maximumDanger.turn = turn; + } + + if(turn < node.fastestDanger.turn + || turn == node.fastestDanger.turn && node.fastestDanger.danger < tileDanger) + { + node.fastestDanger.danger = tileDanger; + node.fastestDanger.turn = turn; + } + } + }); + } +} + +uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & path) const +{ + int3 tile = path.targetTile(); + int turn = path.turn(); + const HitMapNode & info = hitMap[tile.x][tile.y][tile.z]; + + return info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger) + || info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger); +} \ No newline at end of file diff --git a/AI/Nullkiller/Engine/DangerHitMapAnalyzer.h b/AI/Nullkiller/Engine/DangerHitMapAnalyzer.h new file mode 100644 index 000000000..a54e4a597 --- /dev/null +++ b/AI/Nullkiller/Engine/DangerHitMapAnalyzer.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../VCAI.h" +#include "../AIHelper.h" + +struct HitMapInfo +{ + uint64_t danger; + uint8_t turn; + + void reset() + { + danger = 0; + turn = 255; + } +}; + +struct HitMapNode +{ + HitMapInfo maximumDanger; + HitMapInfo fastestDanger; + + void reset() + { + maximumDanger.reset(); + fastestDanger.reset(); + } +}; + +class DangerHitMapAnalyzer +{ +private: + boost::multi_array hitMap; + +public: + void updateHitMap(); + uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) const; +}; \ No newline at end of file diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 1d576f0bb..7ff92d8ac 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -13,12 +13,12 @@ extern boost::thread_specific_ptr ai; Nullkiller::Nullkiller() { priorityEvaluator.reset(new PriorityEvaluator()); + dangerHitMap.reset(new DangerHitMapAnalyzer()); } Goals::TSubgoal Nullkiller::choseBestTask(Goals::TGoalVec tasks) { - Goals::TSubgoal bestTask = *vstd::maxElementByFun(tasks, [](Goals::TSubgoal goal) -> float - { + Goals::TSubgoal bestTask = *vstd::maxElementByFun(tasks, [](Goals::TSubgoal goal) -> float{ return goal->priority; }); @@ -53,6 +53,8 @@ Goals::TSubgoal Nullkiller::choseBestTask(Behavior & behavior) void Nullkiller::resetAiState() { lockedHeroes.clear(); + + dangerHitMap->updateHitMap(); } void Nullkiller::updateAiState() diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index a135ce7f4..f728463c7 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -1,6 +1,7 @@ #pragma once #include "PriorityEvaluator.h" +#include "DangerHitMapAnalyzer.h" #include "../Goals/AbstractGoal.h" #include "../Behaviors/Behavior.h" @@ -12,6 +13,8 @@ private: std::set lockedHeroes; public: + std::unique_ptr dangerHitMap; + Nullkiller(); void makeTurn(); bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 39696f9ad..76dcb477f 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -33,9 +33,10 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta //TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline int3 pos; - const PlayerColor player = ai->playerID; + const PlayerColor player = playerID; + const PlayerColor fowPlayer = ai->playerID; const int3 sizes = gs->getMapSize(); - const auto & fow = static_cast(gs)->getPlayerTeam(player)->fogOfWarMap; + const auto & fow = static_cast(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap; //make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching) const bool useFlying = options.useFlying; @@ -478,10 +479,13 @@ void AINodeStorage::setHeroes(std::vector heroes, const VCAI * _ai) cb = _ai->myCb.get(); ai = _ai; + playerID = ai->playerID; + for(auto & hero : heroes) { uint64_t mask = 1 << actors.size(); + playerID = hero->tempOwner; actors.push_back(std::make_shared(hero.get(), mask, ai)); } } @@ -824,6 +828,17 @@ float AIPath::movementCost() const return 0.0; } +uint8_t AIPath::turn() const +{ + if(nodes.size()) + { + return nodes.front().turns; + } + + // TODO: boost:optional? + return 0; +} + uint64_t AIPath::getHeroStrength() const { return targetHero->getFightingStrength() * heroArmy->getArmyStrength(); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 1559de4dc..18f87a6d8 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -34,7 +34,7 @@ struct AIPathNode : public CGPathNode struct AIPathNodeInfo { float cost; - int turns; + uint8_t turns; int3 coord; uint64_t danger; const CGHeroInstance * targetHero; @@ -66,6 +66,8 @@ struct AIPath float movementCost() const; + uint8_t turn() const; + uint64_t getHeroStrength() const; std::string toString(); @@ -91,6 +93,7 @@ private: std::vector heroChain; bool heroChainPass; // true if we need to calculate hero chain int heroChainTurn; + PlayerColor playerID; 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.