mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
Merge pull request #1627 from vcmi/nkai-defence-fix
NKAI improve defense and some fixes
This commit is contained in:
@@ -359,6 +359,11 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj)
|
|||||||
{
|
{
|
||||||
lostHero(cb->getHero(obj->id)); //we can promote, since objectRemoved is called just before actual deletion
|
lostHero(cb->getHero(obj->id)); //we can promote, since objectRemoved is called just before actual deletion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(obj->ID == Obj::HERO && cb->getPlayerRelations(obj->tempOwner, playerID) == PlayerRelations::ENEMIES)
|
||||||
|
{
|
||||||
|
nullkiller->dangerHitMap->reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIGateway::showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor)
|
void AIGateway::showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor)
|
||||||
@@ -580,27 +585,38 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
|||||||
requestActionASAP([=]()
|
requestActionASAP([=]()
|
||||||
{
|
{
|
||||||
//yes&no -> always answer yes, we are a brave AI :)
|
//yes&no -> always answer yes, we are a brave AI :)
|
||||||
auto answer = 1;
|
bool answer = true;
|
||||||
auto objects = cb->getVisitableObjs(target);
|
auto objects = cb->getVisitableObjs(target);
|
||||||
|
|
||||||
if(hero.validAndSet() && target.valid() && objects.size())
|
if(hero.validAndSet() && target.valid() && objects.size())
|
||||||
{
|
{
|
||||||
auto objType = objects.front()->ID;
|
auto topObj = objects.front()->id == hero->id ? objects.back() : objects.front();
|
||||||
|
auto objType = topObj->ID; // top object should be our hero
|
||||||
|
auto goalObjectID = nullkiller->getTargetObject();
|
||||||
|
auto ratio = (float)nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()) / (float)hero->getTotalStrength();
|
||||||
|
|
||||||
if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE)
|
answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
|
||||||
|
logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio);
|
||||||
|
|
||||||
|
if(cb->getObj(goalObjectID, false))
|
||||||
|
{
|
||||||
|
logAi->trace("AI expected %s", cb->getObj(goalObjectID, false)->getObjectName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(objType == Obj::BORDERGUARD || objType == Obj::QUEST_GUARD)
|
||||||
|
{
|
||||||
|
answer = true;
|
||||||
|
}
|
||||||
|
else if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE)
|
||||||
{
|
{
|
||||||
auto ratio = (float)nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()) / (float)hero->getTotalStrength();
|
|
||||||
bool dangerUnknown = ratio == 0;
|
bool dangerUnknown = ratio == 0;
|
||||||
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
|
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
|
||||||
|
|
||||||
logAi->trace("Guarded object query hook: %s by %s danger ratio %f", target.toString(), hero.name, ratio);
|
answer = !dangerUnknown && !dangerTooHigh;
|
||||||
|
|
||||||
if(text.find("guarded") != std::string::npos && (dangerUnknown || dangerTooHigh))
|
|
||||||
answer = 0; // no
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
answerQuery(askID, answer);
|
answerQuery(askID, answer ? 1 : 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -1332,7 +1348,10 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
|||||||
if(auto visitedObject = vstd::frontOrNull(cb->getVisitableObjs(h->visitablePos()))) //we stand on something interesting
|
if(auto visitedObject = vstd::frontOrNull(cb->getVisitableObjs(h->visitablePos()))) //we stand on something interesting
|
||||||
{
|
{
|
||||||
if(visitedObject != *h)
|
if(visitedObject != *h)
|
||||||
|
{
|
||||||
performObjectInteraction(visitedObject, h);
|
performObjectInteraction(visitedObject, h);
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(h) //we could have lost hero after last move
|
if(h) //we could have lost hero after last move
|
||||||
|
@@ -14,6 +14,8 @@
|
|||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
|
HitMapInfo HitMapInfo::NoTreat;
|
||||||
|
|
||||||
void DangerHitMapAnalyzer::updateHitMap()
|
void DangerHitMapAnalyzer::updateHitMap()
|
||||||
{
|
{
|
||||||
if(upToDate)
|
if(upToDate)
|
||||||
@@ -47,6 +49,9 @@ void DangerHitMapAnalyzer::updateHitMap()
|
|||||||
|
|
||||||
for(auto pair : heroes)
|
for(auto pair : heroes)
|
||||||
{
|
{
|
||||||
|
if(!pair.first.isValidPlayer())
|
||||||
|
continue;
|
||||||
|
|
||||||
if(ai->cb->getPlayerRelations(ai->playerID, pair.first) != PlayerRelations::ENEMIES)
|
if(ai->cb->getPlayerRelations(ai->playerID, pair.first) != PlayerRelations::ENEMIES)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@@ -16,10 +16,17 @@ namespace NKAI
|
|||||||
|
|
||||||
struct HitMapInfo
|
struct HitMapInfo
|
||||||
{
|
{
|
||||||
|
static HitMapInfo NoTreat;
|
||||||
|
|
||||||
uint64_t danger;
|
uint64_t danger;
|
||||||
uint8_t turn;
|
uint8_t turn;
|
||||||
HeroPtr hero;
|
HeroPtr hero;
|
||||||
|
|
||||||
|
HitMapInfo()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
danger = 0;
|
danger = 0;
|
||||||
@@ -33,6 +40,8 @@ struct HitMapNode
|
|||||||
HitMapInfo maximumDanger;
|
HitMapInfo maximumDanger;
|
||||||
HitMapInfo fastestDanger;
|
HitMapInfo fastestDanger;
|
||||||
|
|
||||||
|
HitMapNode() = default;
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
maximumDanger.reset();
|
maximumDanger.reset();
|
||||||
|
@@ -92,37 +92,6 @@ float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const
|
|||||||
return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f;
|
return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::vector<const CGHeroInstance *>> clusterizeHeroes(CCallback * cb, std::vector<const CGHeroInstance *> heroes)
|
|
||||||
{
|
|
||||||
std::vector<std::vector<const CGHeroInstance *>> clusters;
|
|
||||||
|
|
||||||
for(auto hero : heroes)
|
|
||||||
{
|
|
||||||
auto paths = cb->getPathsInfo(hero);
|
|
||||||
std::vector<const CGHeroInstance *> newCluster = {hero};
|
|
||||||
|
|
||||||
for(auto cluster = clusters.begin(); cluster != clusters.end();)
|
|
||||||
{
|
|
||||||
auto hero = std::find_if(cluster->begin(), cluster->end(), [&](const CGHeroInstance * h) -> bool
|
|
||||||
{
|
|
||||||
return paths->getNode(h->visitablePos())->turns < SCOUT_TURN_DISTANCE_LIMIT;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(hero != cluster->end())
|
|
||||||
{
|
|
||||||
vstd::concatenate(newCluster, *cluster);
|
|
||||||
clusters.erase(cluster);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
cluster++;
|
|
||||||
}
|
|
||||||
|
|
||||||
clusters.push_back(newCluster);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clusters;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HeroManager::update()
|
void HeroManager::update()
|
||||||
{
|
{
|
||||||
logAi->trace("Start analysing our heroes");
|
logAi->trace("Start analysing our heroes");
|
||||||
@@ -140,7 +109,7 @@ void HeroManager::update()
|
|||||||
return scores.at(h1) > scores.at(h2);
|
return scores.at(h1) > scores.at(h2);
|
||||||
};
|
};
|
||||||
|
|
||||||
int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 100 + 1);
|
int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 50 + 1);
|
||||||
|
|
||||||
std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
|
std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
|
||||||
|
|
||||||
@@ -149,27 +118,6 @@ void HeroManager::update()
|
|||||||
heroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
|
heroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(auto cluster : clusterizeHeroes(cb, myHeroes))
|
|
||||||
{
|
|
||||||
std::sort(cluster.begin(), cluster.end(), scoreSort);
|
|
||||||
|
|
||||||
auto localMainCountMax = (cluster.size() + 2) / 3;
|
|
||||||
|
|
||||||
for(auto hero : cluster)
|
|
||||||
{
|
|
||||||
if(heroRoles[hero] != HeroRole::MAIN)
|
|
||||||
{
|
|
||||||
heroRoles[hero] = HeroRole::MAIN;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
localMainCountMax--;
|
|
||||||
|
|
||||||
if(localMainCountMax == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto hero : myHeroes)
|
for(auto hero : myHeroes)
|
||||||
{
|
{
|
||||||
logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroRoles[hero] == HeroRole::MAIN ? "main" : "scout");
|
logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroRoles[hero] == HeroRole::MAIN ? "main" : "scout");
|
||||||
|
@@ -271,7 +271,7 @@ void ObjectClusterizer::clusterize()
|
|||||||
if(!shouldVisit(ai, path.targetHero, obj))
|
if(!shouldVisit(ai, path.targetHero, obj))
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName());
|
logAi->trace("Hero %s does not need to visit %s", path.targetHero->getObjectName(), obj->getObjectName());
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -285,7 +285,7 @@ void ObjectClusterizer::clusterize()
|
|||||||
if(vstd::contains(heroesProcessed, path.targetHero))
|
if(vstd::contains(heroesProcessed, path.targetHero))
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Hero %s is already processed.", path.targetHero->name);
|
logAi->trace("Hero %s is already processed.", path.targetHero->getObjectName());
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@@ -70,7 +70,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
|||||||
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.getHeroStrength());
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
|||||||
auto hero = path.targetHero;
|
auto hero = path.targetHero;
|
||||||
auto danger = path.getTotalDanger();
|
auto danger = path.getTotalDanger();
|
||||||
|
|
||||||
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1)
|
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && path.exchangeCount > 1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto firstBlockedAction = path.getFirstBlockedAction();
|
auto firstBlockedAction = path.getFirstBlockedAction();
|
||||||
@@ -113,7 +113,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
|||||||
"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
|
"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
|
||||||
isSafe ? "safe" : "not safe",
|
isSafe ? "safe" : "not safe",
|
||||||
objToVisit ? objToVisit->getObjectName() : path.targetTile().toString(),
|
objToVisit ? objToVisit->getObjectName() : path.targetTile().toString(),
|
||||||
hero->name,
|
hero->getObjectName(),
|
||||||
path.getHeroStrength(),
|
path.getHeroStrength(),
|
||||||
danger,
|
danger,
|
||||||
path.getTotalArmyLoss());
|
path.getTotalArmyLoss());
|
||||||
@@ -126,8 +126,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
|||||||
|
|
||||||
sharedPtr.reset(newWay);
|
sharedPtr.reset(newWay);
|
||||||
|
|
||||||
if(!closestWay || closestWay->movementCost() > path.movementCost())
|
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
|
||||||
|
|
||||||
|
if(heroRole == HeroRole::SCOUT
|
||||||
|
&& (!closestWay || closestWay->movementCost() > path.movementCost()))
|
||||||
|
{
|
||||||
closestWay = &path;
|
closestWay = &path;
|
||||||
|
}
|
||||||
|
|
||||||
if(!ai->nullkiller->arePathHeroesLocked(path))
|
if(!ai->nullkiller->arePathHeroesLocked(path))
|
||||||
{
|
{
|
||||||
@@ -137,11 +142,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(closestWay || waysToVisitObj.empty());
|
if(closestWay)
|
||||||
for(auto way : waysToVisitObj)
|
|
||||||
{
|
{
|
||||||
way->closestWayRatio
|
for(auto way : waysToVisitObj)
|
||||||
= closestWay->movementCost() / way->getPath().movementCost();
|
{
|
||||||
|
way->closestWayRatio
|
||||||
|
= closestWay->movementCost() / way->getPath().movementCost();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tasks;
|
return tasks;
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
#include "../Goals/RecruitHero.h"
|
#include "../Goals/RecruitHero.h"
|
||||||
#include "../Goals/DismissHero.h"
|
#include "../Goals/DismissHero.h"
|
||||||
#include "../Goals/Composition.h"
|
#include "../Goals/Composition.h"
|
||||||
|
#include "../Goals/CaptureObject.h"
|
||||||
#include "../Markers/DefendTown.h"
|
#include "../Markers/DefendTown.h"
|
||||||
#include "../Goals/ExchangeSwapTownHeroes.h"
|
#include "../Goals/ExchangeSwapTownHeroes.h"
|
||||||
#include "lib/mapping/CMap.h" //for victory conditions
|
#include "lib/mapping/CMap.h" //for victory conditions
|
||||||
@@ -29,6 +30,8 @@ namespace NKAI
|
|||||||
extern boost::thread_specific_ptr<CCallback> cb;
|
extern boost::thread_specific_ptr<CCallback> cb;
|
||||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
extern boost::thread_specific_ptr<AIGateway> ai;
|
||||||
|
|
||||||
|
const double TREAT_IGNORE_RATIO = 0.5;
|
||||||
|
|
||||||
using namespace Goals;
|
using namespace Goals;
|
||||||
|
|
||||||
std::string DefenceBehavior::toString() const
|
std::string DefenceBehavior::toString() const
|
||||||
@@ -53,7 +56,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
logAi->trace("Evaluating defence for %s", town->getNameTranslated());
|
logAi->trace("Evaluating defence for %s", town->getNameTranslated());
|
||||||
|
|
||||||
auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
|
auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
|
||||||
auto treats = { treatNode.fastestDanger, treatNode.maximumDanger };
|
auto treats = { treatNode.maximumDanger, treatNode.fastestDanger };
|
||||||
|
|
||||||
if(!treatNode.fastestDanger.hero)
|
if(!treatNode.fastestDanger.hero)
|
||||||
{
|
{
|
||||||
@@ -68,12 +71,17 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
{
|
{
|
||||||
if(!ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
|
if(!ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
|
||||||
{
|
{
|
||||||
if(!town->visitingHero && cb->getHeroesInfo().size() < GameConstants::MAX_HEROES_PER_PLAYER)
|
if(!town->visitingHero && cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
|
||||||
{
|
{
|
||||||
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
|
logAi->trace(
|
||||||
}
|
"Extracting hero %s from garrison of town %s",
|
||||||
|
town->garrisonHero->getNameTranslated(),
|
||||||
|
town->getNameTranslated());
|
||||||
|
|
||||||
return;
|
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
@@ -113,22 +121,37 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
if(town->visitingHero && path.getHeroStrength() < town->visitingHero->getHeroStrength())
|
if(town->visitingHero && path.getHeroStrength() < town->visitingHero->getHeroStrength())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if(path.getHeroStrength() > treat.danger)
|
if(treat.hero.validAndSet()
|
||||||
|
&& treat.turn <= 1
|
||||||
|
&& (treat.danger == treatNode.maximumDanger.danger || treat.turn < treatNode.maximumDanger.turn)
|
||||||
|
&& isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
|
||||||
{
|
{
|
||||||
if((path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
|
Composition composition;
|
||||||
|| (path.exchangeCount == 1 && path.turn() < treat.turn)
|
|
||||||
|
composition.addNext(DefendTown(town, treat, path)).addNext(CaptureObject(treat.hero.get()));
|
||||||
|
|
||||||
|
tasks.push_back(Goals::sptr(composition));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool treatIsWeak = path.getHeroStrength() / treat.danger > TREAT_IGNORE_RATIO;
|
||||||
|
bool needToSaveGrowth = treat.turn == 0 && dayOfWeek == 7;
|
||||||
|
|
||||||
|
if(treatIsWeak && !needToSaveGrowth)
|
||||||
|
{
|
||||||
|
if((path.exchangeCount == 1 && path.turn() < treat.turn)
|
||||||
|| path.turn() < treat.turn - 1
|
|| path.turn() < treat.turn - 1
|
||||||
|| (path.turn() < treat.turn && treat.turn >= 2))
|
|| (path.turn() < treat.turn && treat.turn >= 2))
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"Hero %s can eliminate danger for town %s using path %s.",
|
"Hero %s can eliminate danger for town %s using path %s.",
|
||||||
path.targetHero->name,
|
path.targetHero->getObjectName(),
|
||||||
town->name,
|
town->getObjectName(),
|
||||||
path.toString());
|
path.toString());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
treatIsUnderControl = true;
|
treatIsUnderControl = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +175,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
if(cb->getHeroesInfo().size() < ALLOWED_ROAMING_HEROES)
|
if(cb->getHeroesInfo().size() < ALLOWED_ROAMING_HEROES)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Hero %s can be recruited to defend %s", hero->name, town->name);
|
logAi->trace("Hero %s can be recruited to defend %s", hero->getObjectName(), town->getObjectName());
|
||||||
#endif
|
#endif
|
||||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(1)));
|
tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(1)));
|
||||||
continue;
|
continue;
|
||||||
@@ -202,7 +225,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"Hero %s can defend town with force %lld in %s turns, cost: %f, path: %s",
|
"Hero %s can defend town with force %lld in %s turns, cost: %f, path: %s",
|
||||||
path.targetHero->name,
|
path.targetHero->getObjectName(),
|
||||||
path.getHeroStrength(),
|
path.getHeroStrength(),
|
||||||
std::to_string(path.turn()),
|
std::to_string(path.turn()),
|
||||||
path.movementCost(),
|
path.movementCost(),
|
||||||
@@ -212,8 +235,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Defer defence of %s by %s because he has enough time to reach the town next trun",
|
logAi->trace("Defer defence of %s by %s because he has enough time to reach the town next trun",
|
||||||
town->name,
|
town->getObjectName(),
|
||||||
path.targetHero->name);
|
path.targetHero->getObjectName());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
defferedPaths[path.targetHero].push_back(i);
|
defferedPaths[path.targetHero].push_back(i);
|
||||||
@@ -225,8 +248,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Put %s to garrison of town %s",
|
logAi->trace("Put %s to garrison of town %s",
|
||||||
path.targetHero->name,
|
path.targetHero->getObjectName(),
|
||||||
town->name);
|
town->getObjectName());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// dismiss creatures we are not able to pick to be able to hide in garrison
|
// dismiss creatures we are not able to pick to be able to hide in garrison
|
||||||
@@ -249,8 +272,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Can not move %s to defend town %s. Path is locked.",
|
logAi->trace("Can not move %s to defend town %s. Path is locked.",
|
||||||
path.targetHero->name,
|
path.targetHero->getObjectName(),
|
||||||
town->name);
|
town->getObjectName());
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
@@ -277,8 +300,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Move %s to defend town %s",
|
logAi->trace("Move %s to defend town %s",
|
||||||
path.targetHero->name,
|
path.targetHero->getObjectName(),
|
||||||
town->name);
|
town->getObjectName());
|
||||||
#endif
|
#endif
|
||||||
Composition composition;
|
Composition composition;
|
||||||
|
|
||||||
|
@@ -45,8 +45,7 @@ Goals::TGoalVec GatherArmyBehavior::decompose() const
|
|||||||
|
|
||||||
for(const CGHeroInstance * hero : heroes)
|
for(const CGHeroInstance * hero : heroes)
|
||||||
{
|
{
|
||||||
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN
|
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN)
|
||||||
&& hero->getArmyStrength() >= 300)
|
|
||||||
{
|
{
|
||||||
vstd::concatenate(tasks, deliverArmyToHero(hero));
|
vstd::concatenate(tasks, deliverArmyToHero(hero));
|
||||||
}
|
}
|
||||||
@@ -70,13 +69,6 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
|||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
|
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
|
||||||
#endif
|
#endif
|
||||||
if(ai->nullkiller->isHeroLocked(hero))
|
|
||||||
{
|
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
|
||||||
logAi->trace("Skipping locked hero %s, %s", hero->getObjectName(), pos.toString());
|
|
||||||
#endif
|
|
||||||
return tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
|
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
|
||||||
|
|
||||||
@@ -92,6 +84,14 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
|||||||
|
|
||||||
if(path.containsHero(hero)) continue;
|
if(path.containsHero(hero)) continue;
|
||||||
|
|
||||||
|
if(path.turn() == 0 && hero->inTownGarrison)
|
||||||
|
{
|
||||||
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
|
logAi->trace("Skipping garnisoned hero %s, %s", hero->getObjectName(), pos.toString());
|
||||||
|
#endif
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
@@ -124,22 +124,40 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
|||||||
// avoid trying to move bigger army to the weaker one.
|
// avoid trying to move bigger army to the weaker one.
|
||||||
if(armyValue > 1)
|
if(armyValue > 1)
|
||||||
{
|
{
|
||||||
|
bool hasOtherMainInPath = false;
|
||||||
|
|
||||||
|
for(auto node : path.nodes)
|
||||||
|
{
|
||||||
|
if(!node.targetHero) continue;
|
||||||
|
|
||||||
|
auto heroRole = ai->nullkiller->heroManager->getHeroRole(node.targetHero);
|
||||||
|
|
||||||
|
if(heroRole == HeroRole::MAIN)
|
||||||
|
{
|
||||||
|
hasOtherMainInPath = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasOtherMainInPath)
|
||||||
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Army value is too large.");
|
logAi->trace("Army value is too large.");
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto danger = path.getTotalDanger();
|
auto danger = path.getTotalDanger();
|
||||||
|
|
||||||
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
|
"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
|
||||||
isSafe ? "safe" : "not safe",
|
isSafe ? "safe" : "not safe",
|
||||||
hero->name,
|
hero->getObjectName(),
|
||||||
path.targetHero->name,
|
path.targetHero->getObjectName(),
|
||||||
path.getHeroStrength(),
|
path.getHeroStrength(),
|
||||||
danger,
|
danger,
|
||||||
path.getTotalArmyLoss());
|
path.getTotalArmyLoss());
|
||||||
@@ -194,7 +212,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
|||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Path found %s", path.toString());
|
logAi->trace("Path found %s", path.toString());
|
||||||
#endif
|
#endif
|
||||||
if(upgrader->visitingHero != path.targetHero)
|
if(upgrader->visitingHero && upgrader->visitingHero.get() != path.targetHero)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Ignore path. Town has visiting hero.");
|
logAi->trace("Ignore path. Town has visiting hero.");
|
||||||
@@ -219,7 +237,10 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
|
||||||
|
|
||||||
|
if(heroRole == HeroRole::SCOUT
|
||||||
|
&& ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
||||||
@@ -228,10 +249,22 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
|
auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
|
||||||
|
|
||||||
|
if(ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN)
|
||||||
|
{
|
||||||
|
upgrade.upgradeValue +=
|
||||||
|
ai->nullkiller->armyManager->howManyReinforcementsCanGet(path.targetHero, path.heroArmy, upgrader);
|
||||||
|
}
|
||||||
|
|
||||||
auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
|
auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
|
||||||
|
|
||||||
if(armyValue < 0.1f || upgrade.upgradeValue < 300) // avoid small upgrades
|
if(armyValue < 0.25f || upgrade.upgradeValue < 300) // avoid small upgrades
|
||||||
|
{
|
||||||
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
|
logAi->trace("Ignore path. Army value is too small (%f)", armyValue);
|
||||||
|
#endif
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto danger = path.getTotalDanger();
|
auto danger = path.getTotalDanger();
|
||||||
|
|
||||||
@@ -242,7 +275,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
|||||||
"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
|
"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
|
||||||
isSafe ? "safe" : "not safe",
|
isSafe ? "safe" : "not safe",
|
||||||
upgrader->getObjectName(),
|
upgrader->getObjectName(),
|
||||||
path.targetHero->name,
|
path.targetHero->getObjectName(),
|
||||||
path.getHeroStrength(),
|
path.getHeroStrength(),
|
||||||
danger,
|
danger,
|
||||||
path.getTotalArmyLoss());
|
path.getTotalArmyLoss());
|
||||||
|
@@ -115,8 +115,20 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
|||||||
{
|
{
|
||||||
case Obj::TOWN:
|
case Obj::TOWN:
|
||||||
{
|
{
|
||||||
const CGTownInstance * cre = dynamic_cast<const CGTownInstance *>(obj);
|
const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
|
||||||
return cre->getUpperArmy()->getArmyStrength();
|
auto danger = town->getUpperArmy()->getArmyStrength();
|
||||||
|
|
||||||
|
if(danger || town->visitingHero)
|
||||||
|
{
|
||||||
|
auto fortLevel = town->fortLevel();
|
||||||
|
|
||||||
|
if(fortLevel == CGTownInstance::EFortLevel::CASTLE)
|
||||||
|
danger += 10000;
|
||||||
|
else if(fortLevel == CGTownInstance::EFortLevel::CITADEL)
|
||||||
|
danger += 4000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return danger;
|
||||||
}
|
}
|
||||||
case Obj::ARTIFACT:
|
case Obj::ARTIFACT:
|
||||||
case Obj::RESOURCE:
|
case Obj::RESOURCE:
|
||||||
|
@@ -131,6 +131,7 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
|||||||
auto start = std::chrono::high_resolution_clock::now();
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
activeHero = nullptr;
|
activeHero = nullptr;
|
||||||
|
setTargetObject(-1);
|
||||||
|
|
||||||
if(!fast)
|
if(!fast)
|
||||||
{
|
{
|
||||||
@@ -188,7 +189,7 @@ bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
|
|||||||
if(getHeroLockedReason(path.targetHero) == HeroLockedReason::STARTUP)
|
if(getHeroLockedReason(path.targetHero) == HeroLockedReason::STARTUP)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->name, path.toString());
|
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -200,7 +201,7 @@ bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
|
|||||||
if(lockReason != HeroLockedReason::NOT_LOCKED)
|
if(lockReason != HeroLockedReason::NOT_LOCKED)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->name, path.toString());
|
logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -221,6 +222,7 @@ void Nullkiller::makeTurn()
|
|||||||
boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
|
boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
|
||||||
|
|
||||||
const int MAX_DEPTH = 10;
|
const int MAX_DEPTH = 10;
|
||||||
|
const float FAST_TASK_MINIMAL_PRIORITY = 0.7;
|
||||||
|
|
||||||
resetAiState();
|
resetAiState();
|
||||||
|
|
||||||
@@ -240,12 +242,12 @@ void Nullkiller::makeTurn()
|
|||||||
|
|
||||||
bestTask = choseBestTask(fastTasks);
|
bestTask = choseBestTask(fastTasks);
|
||||||
|
|
||||||
if(bestTask->priority >= 1)
|
if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY)
|
||||||
{
|
{
|
||||||
executeTask(bestTask);
|
executeTask(bestTask);
|
||||||
updateAiState(i, true);
|
updateAiState(i, true);
|
||||||
}
|
}
|
||||||
} while(bestTask->priority >= 1);
|
} while(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY);
|
||||||
|
|
||||||
Goals::TTaskVec bestTasks = {
|
Goals::TTaskVec bestTasks = {
|
||||||
bestTask,
|
bestTask,
|
||||||
@@ -272,21 +274,16 @@ void Nullkiller::makeTurn()
|
|||||||
if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
|
if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
|
||||||
useHeroChain = false;
|
useHeroChain = false;
|
||||||
|
|
||||||
if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY
|
if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
|
||||||
&& scanDepth != ScanDepth::FULL)
|
&& scanDepth == ScanDepth::FULL)
|
||||||
{
|
{
|
||||||
if(heroRole == HeroRole::MAIN || bestTask->priority < MIN_PRIORITY)
|
useHeroChain = false;
|
||||||
{
|
scanDepth = ScanDepth::SMALL;
|
||||||
useHeroChain = false;
|
|
||||||
|
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"Goal %s has too low priority %f so increasing scan depth",
|
"Goal %s has too low priority %f so increasing scan depth",
|
||||||
bestTask->toString(),
|
bestTask->toString(),
|
||||||
bestTask->priority);
|
bestTask->priority);
|
||||||
scanDepth = (ScanDepth)((int)scanDepth + 1);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(bestTask->priority < MIN_PRIORITY)
|
if(bestTask->priority < MIN_PRIORITY)
|
||||||
@@ -317,10 +314,12 @@ void Nullkiller::executeTask(Goals::TTask task)
|
|||||||
}
|
}
|
||||||
catch(cannotFulfillGoalException & e)
|
catch(cannotFulfillGoalException & e)
|
||||||
{
|
{
|
||||||
logAi->debug("Failed to realize subgoal of type %s, I will stop.", taskDescr);
|
logAi->error("Failed to realize subgoal of type %s, I will stop.", taskDescr);
|
||||||
logAi->debug("The error message was: %s", e.what());
|
logAi->error("The error message was: %s", e.what());
|
||||||
|
|
||||||
throw;
|
#if NKAI_TRACE_LEVEL == 0
|
||||||
|
throw; // will be recatched and AI turn ended
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ namespace NKAI
|
|||||||
|
|
||||||
const float MAX_GOLD_PEASURE = 0.3f;
|
const float MAX_GOLD_PEASURE = 0.3f;
|
||||||
const float MIN_PRIORITY = 0.01f;
|
const float MIN_PRIORITY = 0.01f;
|
||||||
const float NEXT_SCAN_MIN_PRIORITY = 0.4f;
|
const float SMALL_SCAN_MIN_PRIORITY = 0.4f;
|
||||||
|
|
||||||
enum class HeroLockedReason
|
enum class HeroLockedReason
|
||||||
{
|
{
|
||||||
@@ -39,11 +39,9 @@ enum class HeroLockedReason
|
|||||||
|
|
||||||
enum class ScanDepth
|
enum class ScanDepth
|
||||||
{
|
{
|
||||||
SMALL = 0,
|
FULL = 0,
|
||||||
|
|
||||||
MEDIUM = 1,
|
SMALL = 1
|
||||||
|
|
||||||
FULL = 2
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Nullkiller
|
class Nullkiller
|
||||||
@@ -51,6 +49,7 @@ class Nullkiller
|
|||||||
private:
|
private:
|
||||||
const CGHeroInstance * activeHero;
|
const CGHeroInstance * activeHero;
|
||||||
int3 targetTile;
|
int3 targetTile;
|
||||||
|
ObjectInstanceID targetObject;
|
||||||
std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
|
std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
|
||||||
ScanDepth scanDepth;
|
ScanDepth scanDepth;
|
||||||
TResources lockedResources;
|
TResources lockedResources;
|
||||||
@@ -79,6 +78,8 @@ public:
|
|||||||
HeroPtr getActiveHero() { return activeHero; }
|
HeroPtr getActiveHero() { return activeHero; }
|
||||||
HeroLockedReason getHeroLockedReason(const CGHeroInstance * hero) const;
|
HeroLockedReason getHeroLockedReason(const CGHeroInstance * hero) const;
|
||||||
int3 getTargetTile() const { return targetTile; }
|
int3 getTargetTile() const { return targetTile; }
|
||||||
|
ObjectInstanceID getTargetObject() const { return targetObject; }
|
||||||
|
void setTargetObject(int objid) { targetObject = ObjectInstanceID(objid); }
|
||||||
void setActive(const CGHeroInstance * hero, int3 tile) { activeHero = hero; targetTile = tile; }
|
void setActive(const CGHeroInstance * hero, int3 tile) { activeHero = hero; targetTile = tile; }
|
||||||
void lockHero(const CGHeroInstance * hero, HeroLockedReason lockReason) { lockedHeroes[hero] = lockReason; }
|
void lockHero(const CGHeroInstance * hero, HeroLockedReason lockReason) { lockedHeroes[hero] = lockReason; }
|
||||||
void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); }
|
void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); }
|
||||||
|
@@ -17,10 +17,12 @@
|
|||||||
#include "../../../lib/CPathfinder.h"
|
#include "../../../lib/CPathfinder.h"
|
||||||
#include "../../../lib/CGameStateFwd.h"
|
#include "../../../lib/CGameStateFwd.h"
|
||||||
#include "../../../lib/VCMI_Lib.h"
|
#include "../../../lib/VCMI_Lib.h"
|
||||||
|
#include "../../../lib/StartInfo.h"
|
||||||
#include "../../../CCallback.h"
|
#include "../../../CCallback.h"
|
||||||
#include "../../../lib/filesystem/Filesystem.h"
|
#include "../../../lib/filesystem/Filesystem.h"
|
||||||
#include "../Goals/ExecuteHeroChain.h"
|
#include "../Goals/ExecuteHeroChain.h"
|
||||||
#include "../Goals/BuildThis.h"
|
#include "../Goals/BuildThis.h"
|
||||||
|
#include "../Goals/ExchangeSwapTownHeroes.h"
|
||||||
#include "../Markers/UnlockCluster.h"
|
#include "../Markers/UnlockCluster.h"
|
||||||
#include "../Markers/HeroExchange.h"
|
#include "../Markers/HeroExchange.h"
|
||||||
#include "../Markers/ArmyUpgrade.h"
|
#include "../Markers/ArmyUpgrade.h"
|
||||||
@@ -79,6 +81,12 @@ void PriorityEvaluator::initVisitTile()
|
|||||||
value = engine->getOutputVariable("Value");
|
value = engine->getOutputVariable("Value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isAnotherAi(const CGObjectInstance * obj, const CPlayerSpecificInfoCallback & cb)
|
||||||
|
{
|
||||||
|
return obj->getOwner().isValidPlayer()
|
||||||
|
&& cb.getStartInfo()->getIthPlayersSettings(obj->getOwner()).isControlledByAI();
|
||||||
|
}
|
||||||
|
|
||||||
int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero)
|
int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero)
|
||||||
{
|
{
|
||||||
auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
|
auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
|
||||||
@@ -86,11 +94,17 @@ int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, cons
|
|||||||
if(relations != PlayerRelations::ENEMIES)
|
if(relations != PlayerRelations::ENEMIES)
|
||||||
return 0; // if we already own it, no additional reward will be received by just visiting it
|
return 0; // if we already own it, no additional reward will be received by just visiting it
|
||||||
|
|
||||||
auto town = cb->getTown(target->id);
|
auto booster = isAnotherAi(target, *cb) ? 1 : 2;
|
||||||
auto isNeutral = target->tempOwner == PlayerColor::NEUTRAL;
|
|
||||||
auto isProbablyDeveloped = !isNeutral && town->hasFort();
|
|
||||||
|
|
||||||
return isProbablyDeveloped ? 1500 : 500;
|
auto town = cb->getTown(target->id);
|
||||||
|
auto fortLevel = town->fortLevel();
|
||||||
|
|
||||||
|
if(town->hasCapitol()) return booster * 2000;
|
||||||
|
|
||||||
|
// probably well developed town will have city hall
|
||||||
|
if(fortLevel == CGTownInstance::CASTLE) return booster * 750;
|
||||||
|
|
||||||
|
return booster * (town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL ? booster * 500 : 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
|
TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
|
||||||
@@ -238,7 +252,17 @@ uint64_t RewardEvaluator::getArmyReward(
|
|||||||
switch(target->ID)
|
switch(target->ID)
|
||||||
{
|
{
|
||||||
case Obj::TOWN:
|
case Obj::TOWN:
|
||||||
return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
|
{
|
||||||
|
auto town = dynamic_cast<const CGTownInstance *>(target);
|
||||||
|
auto fortLevel = town->fortLevel();
|
||||||
|
auto booster = isAnotherAi(town, *ai->cb) ? 1 : 2;
|
||||||
|
|
||||||
|
if(fortLevel < CGTownInstance::CITADEL)
|
||||||
|
return town->hasFort() ? booster * 500 : 0;
|
||||||
|
else
|
||||||
|
return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000);
|
||||||
|
}
|
||||||
|
|
||||||
case Obj::HILL_FORT:
|
case Obj::HILL_FORT:
|
||||||
return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
|
return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
|
||||||
case Obj::CREATURE_BANK:
|
case Obj::CREATURE_BANK:
|
||||||
@@ -374,12 +398,21 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
case Obj::TOWN:
|
case Obj::TOWN:
|
||||||
|
{
|
||||||
if(ai->buildAnalyzer->getDevelopmentInfo().empty())
|
if(ai->buildAnalyzer->getDevelopmentInfo().empty())
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return dynamic_cast<const CGTownInstance *>(target)->hasFort()
|
auto town = dynamic_cast<const CGTownInstance *>(target);
|
||||||
? (target->tempOwner == PlayerColor::NEUTRAL ? 0.8f : 1.0f)
|
auto fortLevel = town->fortLevel();
|
||||||
: 0.7f;
|
auto booster = isAnotherAi(town, *ai->cb) ? 0.3 : 1;
|
||||||
|
|
||||||
|
if(town->hasCapitol()) return 1;
|
||||||
|
|
||||||
|
if(fortLevel < CGTownInstance::CITADEL)
|
||||||
|
return booster * (town->hasFort() ? 0.6 : 0.4);
|
||||||
|
else
|
||||||
|
return booster * (fortLevel == CGTownInstance::CASTLE ? 0.9 : 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
case Obj::HERO:
|
case Obj::HERO:
|
||||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||||
@@ -448,22 +481,17 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t RewardEvaluator::getEnemyHeroDanger(const int3 & tile, uint8_t turn) const
|
const HitMapInfo & RewardEvaluator::getEnemyHeroDanger(const int3 & tile, uint8_t turn) const
|
||||||
{
|
{
|
||||||
auto & treatNode = ai->dangerHitMap->getTileTreat(tile);
|
auto & treatNode = ai->dangerHitMap->getTileTreat(tile);
|
||||||
|
|
||||||
if(treatNode.maximumDanger.danger == 0)
|
if(treatNode.maximumDanger.danger == 0)
|
||||||
return 0;
|
return HitMapInfo::NoTreat;
|
||||||
|
|
||||||
if(treatNode.maximumDanger.turn <= turn)
|
if(treatNode.maximumDanger.turn <= turn)
|
||||||
return treatNode.maximumDanger.danger;
|
return treatNode.maximumDanger;
|
||||||
|
|
||||||
return treatNode.fastestDanger.turn <= turn ? treatNode.fastestDanger.danger : 0;
|
return treatNode.fastestDanger.turn <= turn ? treatNode.fastestDanger : HitMapInfo::NoTreat;
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t RewardEvaluator::getEnemyHeroDanger(const AIPath & path) const
|
|
||||||
{
|
|
||||||
return getEnemyHeroDanger(path.targetTile(), path.turn());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t getArmyCost(const CArmedInstance * army)
|
int32_t getArmyCost(const CArmedInstance * army)
|
||||||
@@ -561,9 +589,29 @@ public:
|
|||||||
uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
|
uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
|
||||||
|
|
||||||
evaluationContext.armyReward += upgradeValue;
|
evaluationContext.armyReward += upgradeValue;
|
||||||
|
evaluationContext.strategicalValue += upgradeValue / armyUpgrade.hero->getTotalStrength();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uint8_t turn, uint64_t ourStrength)
|
||||||
|
{
|
||||||
|
HitMapInfo enemyDanger = evaluationContext.evaluator.getEnemyHeroDanger(tile, turn);
|
||||||
|
|
||||||
|
if(enemyDanger.danger)
|
||||||
|
{
|
||||||
|
auto dangerRatio = enemyDanger.danger / (double)ourStrength;
|
||||||
|
auto enemyHero = evaluationContext.evaluator.ai->cb->getObj(enemyDanger.hero.hid, false);
|
||||||
|
bool isAI = enemyHero && isAnotherAi(enemyHero, *evaluationContext.evaluator.ai->cb);
|
||||||
|
|
||||||
|
if(isAI)
|
||||||
|
{
|
||||||
|
dangerRatio *= 1.5; // lets make AI bit more afraid of other AI.
|
||||||
|
}
|
||||||
|
|
||||||
|
vstd::amax(evaluationContext.enemyHeroDangerRatio, dangerRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DefendTownEvaluator : public IEvaluationContextBuilder
|
class DefendTownEvaluator : public IEvaluationContextBuilder
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
@@ -596,7 +644,7 @@ public:
|
|||||||
auto armyIncome = townArmyIncome(town);
|
auto armyIncome = townArmyIncome(town);
|
||||||
auto dailyIncome = town->dailyIncome()[Res::GOLD];
|
auto dailyIncome = town->dailyIncome()[Res::GOLD];
|
||||||
|
|
||||||
auto strategicalValue = std::sqrt(armyIncome / 20000.0f) + dailyIncome / 10000.0f;
|
auto strategicalValue = std::sqrt(armyIncome / 20000.0f) + dailyIncome / 3000.0f;
|
||||||
|
|
||||||
float multiplier = 1;
|
float multiplier = 1;
|
||||||
|
|
||||||
@@ -607,9 +655,7 @@ public:
|
|||||||
evaluationContext.goldReward += dailyIncome * 5 * multiplier;
|
evaluationContext.goldReward += dailyIncome * 5 * multiplier;
|
||||||
evaluationContext.strategicalValue += strategicalValue * multiplier;
|
evaluationContext.strategicalValue += strategicalValue * multiplier;
|
||||||
vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
|
vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
|
||||||
|
addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
|
||||||
auto enemyDanger = evaluationContext.evaluator.getEnemyHeroDanger(town->visitablePos(), defendTown.getTurn());
|
|
||||||
vstd::amax(evaluationContext.enemyHeroDangerRatio, enemyDanger / (double)defendTown.getDefenceStrength());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -665,7 +711,7 @@ public:
|
|||||||
|
|
||||||
vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength());
|
vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength());
|
||||||
evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroPtr);
|
evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroPtr);
|
||||||
vstd::amax(evaluationContext.enemyHeroDangerRatio, evaluationContext.evaluator.getEnemyHeroDanger(path) / (double)path.getHeroStrength());
|
addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
|
||||||
vstd::amax(evaluationContext.turn, path.turn());
|
vstd::amax(evaluationContext.turn, path.turn());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -719,6 +765,27 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||||
|
{
|
||||||
|
if(task->goalType != Goals::EXCHANGE_SWAP_TOWN_HEROES)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Goals::ExchangeSwapTownHeroes & swapCommand = dynamic_cast<Goals::ExchangeSwapTownHeroes &>(*task);
|
||||||
|
const CGHeroInstance * garrisonHero = swapCommand.getGarrisonHero();
|
||||||
|
|
||||||
|
if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
|
||||||
|
{
|
||||||
|
auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
|
||||||
|
|
||||||
|
evaluationContext.movementCost += garrisonHero->movement;
|
||||||
|
evaluationContext.movementCostByRole[defenderRole] += garrisonHero->movement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
|
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -783,6 +850,7 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
|
|||||||
evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
|
evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
|
||||||
evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
|
evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
|
||||||
evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
|
evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
|
||||||
|
evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
|
||||||
}
|
}
|
||||||
|
|
||||||
EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
|
EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
|
||||||
|
@@ -23,6 +23,7 @@ namespace NKAI
|
|||||||
|
|
||||||
class BuildingInfo;
|
class BuildingInfo;
|
||||||
class Nullkiller;
|
class Nullkiller;
|
||||||
|
struct HitMapInfo;
|
||||||
|
|
||||||
class RewardEvaluator
|
class RewardEvaluator
|
||||||
{
|
{
|
||||||
@@ -41,8 +42,7 @@ public:
|
|||||||
float getSkillReward(const CGObjectInstance * target, 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;
|
int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const;
|
||||||
uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
|
uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const;
|
||||||
uint64_t getEnemyHeroDanger(const AIPath & path) const;
|
const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const;
|
||||||
uint64_t getEnemyHeroDanger(const int3 & tile, uint8_t turn) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DLL_EXPORT EvaluationContext
|
struct DLL_EXPORT EvaluationContext
|
||||||
|
@@ -83,7 +83,10 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai)
|
|||||||
|
|
||||||
cb->swapGarrisonHero(town);
|
cb->swapGarrisonHero(town);
|
||||||
|
|
||||||
ai->nullkiller->lockHero(garrisonHero, lockingReason);
|
if(lockingReason != HeroLockedReason::NOT_LOCKED)
|
||||||
|
{
|
||||||
|
ai->nullkiller->lockHero(garrisonHero, lockingReason);
|
||||||
|
}
|
||||||
|
|
||||||
if(town->visitingHero && town->visitingHero != garrisonHero)
|
if(town->visitingHero && town->visitingHero != garrisonHero)
|
||||||
{
|
{
|
||||||
|
@@ -32,6 +32,9 @@ namespace Goals
|
|||||||
void accept(AIGateway * ai) override;
|
void accept(AIGateway * ai) override;
|
||||||
std::string toString() const override;
|
std::string toString() const override;
|
||||||
virtual bool operator==(const ExchangeSwapTownHeroes & other) const override;
|
virtual bool operator==(const ExchangeSwapTownHeroes & other) const override;
|
||||||
|
|
||||||
|
const CGHeroInstance * getGarrisonHero() const { return garrisonHero; }
|
||||||
|
HeroLockedReason getLockingReason() const { return lockingReason; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -52,6 +52,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
|||||||
logAi->debug("Executing hero chain towards %s. Path %s", targetName, chainPath.toString());
|
logAi->debug("Executing hero chain towards %s. Path %s", targetName, chainPath.toString());
|
||||||
|
|
||||||
ai->nullkiller->setActive(chainPath.targetHero, tile);
|
ai->nullkiller->setActive(chainPath.targetHero, tile);
|
||||||
|
ai->nullkiller->setTargetObject(objid);
|
||||||
|
|
||||||
std::set<int> blockedIndexes;
|
std::set<int> blockedIndexes;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user