1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

AI: danger hitmap using visible enemy heroes

This commit is contained in:
Andrii Danylchenko
2021-05-15 21:57:36 +03:00
committed by Andrii Danylchenko
parent 04bf6f536d
commit 4bf653f596
9 changed files with 146 additions and 8 deletions

View File

@@ -101,16 +101,16 @@ const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
if(h) if(h)
{ {
auto obj = cb->getObj(hid); 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; return nullptr;
} }
else else
{ {
assert(obj); assert(obj);
assert(owned); //assert(owned);
} }
} }

View File

@@ -9,6 +9,7 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "../VCAI.h" #include "../VCAI.h"
#include "../Engine/Nullkiller.h"
#include "../AIhelper.h" #include "../AIhelper.h"
#include "../Goals/ExecuteHeroChain.h" #include "../Goals/ExecuteHeroChain.h"
#include "CaptureObjectsBehavior.h" #include "CaptureObjectsBehavior.h"
@@ -47,6 +48,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
continue; continue;
const int3 pos = objToVisit->visitablePos(); const int3 pos = objToVisit->visitablePos();
auto paths = ai->ah->getPathsToTile(pos); auto paths = ai->ah->getPathsToTile(pos);
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj; std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
std::shared_ptr<ExecuteHeroChain> closestWay; std::shared_ptr<ExecuteHeroChain> closestWay;
@@ -61,6 +63,14 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
logAi->trace("Path found %s", path.toString()); logAi->trace("Path found %s", path.toString());
#endif #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)) if(!shouldVisit(path.targetHero, objToVisit))
continue; continue;

View File

@@ -55,6 +55,7 @@ set(VCAI_SRCS
Goals/ExecuteHeroChain.cpp Goals/ExecuteHeroChain.cpp
Engine/Nullkiller.cpp Engine/Nullkiller.cpp
Engine/PriorityEvaluator.cpp Engine/PriorityEvaluator.cpp
Engine/DangerHitMapAnalyzer.cpp
Behaviors/Behavior.cpp Behaviors/Behavior.cpp
Behaviors/CaptureObjectsBehavior.cpp Behaviors/CaptureObjectsBehavior.cpp
Behaviors/RecruitHeroBehavior.cpp Behaviors/RecruitHeroBehavior.cpp
@@ -117,6 +118,7 @@ set(VCAI_HEADERS
Goals/Goals.h Goals/Goals.h
Engine/Nullkiller.h Engine/Nullkiller.h
Engine/PriorityEvaluator.h Engine/PriorityEvaluator.h
Engine/DangerHitMapAnalyzer.h
Behaviors/Behavior.h Behaviors/Behavior.h
Behaviors/CaptureObjectsBehavior.h Behaviors/CaptureObjectsBehavior.h
Behaviors/RecruitHeroBehavior.h Behaviors/RecruitHeroBehavior.h

View File

@@ -0,0 +1,65 @@
#include "StdInc.h"
#include "DangerHitMapAnalyzer.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
void DangerHitMapAnalyzer::updateHitMap()
{
auto mapSize = cb->getMapSize();
hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
std::map<PlayerColor, std::vector<HeroPtr>> heroes;
for(const CGObjectInstance * obj : ai->visitableObjs)
{
if(obj->ID == Obj::HERO)
{
HeroPtr hero = dynamic_cast<const CGHeroInstance *>(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);
}

View File

@@ -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<HitMapNode, 3> hitMap;
public:
void updateHitMap();
uint64_t enemyCanKillOurHeroesAlongThePath(const AIPath & path) const;
};

View File

@@ -13,12 +13,12 @@ extern boost::thread_specific_ptr<VCAI> ai;
Nullkiller::Nullkiller() Nullkiller::Nullkiller()
{ {
priorityEvaluator.reset(new PriorityEvaluator()); priorityEvaluator.reset(new PriorityEvaluator());
dangerHitMap.reset(new DangerHitMapAnalyzer());
} }
Goals::TSubgoal Nullkiller::choseBestTask(Goals::TGoalVec tasks) 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; return goal->priority;
}); });
@@ -53,6 +53,8 @@ Goals::TSubgoal Nullkiller::choseBestTask(Behavior & behavior)
void Nullkiller::resetAiState() void Nullkiller::resetAiState()
{ {
lockedHeroes.clear(); lockedHeroes.clear();
dangerHitMap->updateHitMap();
} }
void Nullkiller::updateAiState() void Nullkiller::updateAiState()

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "PriorityEvaluator.h" #include "PriorityEvaluator.h"
#include "DangerHitMapAnalyzer.h"
#include "../Goals/AbstractGoal.h" #include "../Goals/AbstractGoal.h"
#include "../Behaviors/Behavior.h" #include "../Behaviors/Behavior.h"
@@ -12,6 +13,8 @@ private:
std::set<HeroPtr> lockedHeroes; std::set<HeroPtr> lockedHeroes;
public: public:
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
Nullkiller(); Nullkiller();
void makeTurn(); void makeTurn();
bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; } bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; }

View File

@@ -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 //TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
int3 pos; int3 pos;
const PlayerColor player = ai->playerID; const PlayerColor player = playerID;
const PlayerColor fowPlayer = ai->playerID;
const int3 sizes = gs->getMapSize(); const int3 sizes = gs->getMapSize();
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(player)->fogOfWarMap; const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching) //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; const bool useFlying = options.useFlying;
@@ -478,10 +479,13 @@ void AINodeStorage::setHeroes(std::vector<HeroPtr> heroes, const VCAI * _ai)
cb = _ai->myCb.get(); cb = _ai->myCb.get();
ai = _ai; ai = _ai;
playerID = ai->playerID;
for(auto & hero : heroes) for(auto & hero : heroes)
{ {
uint64_t mask = 1 << actors.size(); uint64_t mask = 1 << actors.size();
playerID = hero->tempOwner;
actors.push_back(std::make_shared<HeroActor>(hero.get(), mask, ai)); actors.push_back(std::make_shared<HeroActor>(hero.get(), mask, ai));
} }
} }
@@ -824,6 +828,17 @@ float AIPath::movementCost() const
return 0.0; 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 uint64_t AIPath::getHeroStrength() const
{ {
return targetHero->getFightingStrength() * heroArmy->getArmyStrength(); return targetHero->getFightingStrength() * heroArmy->getArmyStrength();

View File

@@ -34,7 +34,7 @@ struct AIPathNode : public CGPathNode
struct AIPathNodeInfo struct AIPathNodeInfo
{ {
float cost; float cost;
int turns; uint8_t turns;
int3 coord; int3 coord;
uint64_t danger; uint64_t danger;
const CGHeroInstance * targetHero; const CGHeroInstance * targetHero;
@@ -66,6 +66,8 @@ struct AIPath
float movementCost() const; float movementCost() const;
uint8_t turn() const;
uint64_t getHeroStrength() const; uint64_t getHeroStrength() const;
std::string toString(); std::string toString();
@@ -91,6 +93,7 @@ private:
std::vector<CGPathNode *> heroChain; std::vector<CGPathNode *> heroChain;
bool heroChainPass; // true if we need to calculate hero chain bool heroChainPass; // true if we need to calculate hero chain
int heroChainTurn; int heroChainTurn;
PlayerColor playerID;
public: 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. /// 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.