1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-25 21:38:59 +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

@ -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);
}
}

@ -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<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
std::shared_ptr<ExecuteHeroChain> 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;

@ -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

@ -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);
}

@ -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;
};

@ -13,12 +13,12 @@ extern boost::thread_specific_ptr<VCAI> 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()

@ -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<HeroPtr> lockedHeroes;
public:
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
Nullkiller();
void makeTurn();
bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; }

@ -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<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)
const bool useFlying = options.useFlying;
@ -478,10 +479,13 @@ void AINodeStorage::setHeroes(std::vector<HeroPtr> 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<HeroActor>(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();

@ -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<CGPathNode *> 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.