Merge branch 'develop' of https://github.com/vcmi/vcmi into develop
@ -1,2 +1,11 @@
|
||||
/*
|
||||
* StdInc.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
|
||||
*
|
||||
*/
|
||||
// Creates the precompiled header
|
||||
#include "StdInc.h"
|
||||
|
@ -1,3 +1,12 @@
|
||||
/*
|
||||
* StdInc.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 "../../Global.h"
|
||||
|
@ -566,7 +566,7 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
|
||||
myCb->waitTillRealize = true;
|
||||
myCb->unlockGsWhenWaiting = true;
|
||||
|
||||
nullkiller->init(CB, playerID);
|
||||
nullkiller->init(CB, this);
|
||||
|
||||
retrieveVisitableObjs();
|
||||
}
|
||||
@ -828,7 +828,7 @@ void AIGateway::makeTurn()
|
||||
{
|
||||
for(const CGObjectInstance * obj : nullkiller->memory->visitableObjs)
|
||||
{
|
||||
if(isWeeklyRevisitable(obj))
|
||||
if(isWeeklyRevisitable(nullkiller.get(), obj))
|
||||
{
|
||||
nullkiller->memory->markObjectUnvisited(obj);
|
||||
}
|
||||
@ -1152,6 +1152,11 @@ void AIGateway::retrieveVisitableObjs()
|
||||
{
|
||||
for(const CGObjectInstance * obj : myCb->getVisitableObjs(pos, false))
|
||||
{
|
||||
if(!obj->appearance)
|
||||
{
|
||||
logAi->error("Bad!");
|
||||
}
|
||||
|
||||
addVisitableObj(obj);
|
||||
}
|
||||
});
|
||||
|
@ -163,15 +163,7 @@ bool HeroPtr::operator==(const HeroPtr & rhs) const
|
||||
return h == rhs.get(true);
|
||||
}
|
||||
|
||||
bool CDistanceSorter::operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const
|
||||
{
|
||||
const CGPathNode * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
|
||||
const CGPathNode * rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
|
||||
|
||||
return ln->getCost() < rn->getCost();
|
||||
}
|
||||
|
||||
bool isSafeToVisit(HeroPtr h, const CCreatureSet * heroArmy, uint64_t dangerStrength)
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet * heroArmy, uint64_t dangerStrength)
|
||||
{
|
||||
const ui64 heroStrength = h->getFightingStrength() * heroArmy->getArmyStrength();
|
||||
|
||||
@ -183,9 +175,9 @@ bool isSafeToVisit(HeroPtr h, const CCreatureSet * heroArmy, uint64_t dangerStre
|
||||
return true; //there's no danger
|
||||
}
|
||||
|
||||
bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength)
|
||||
bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength)
|
||||
{
|
||||
return isSafeToVisit(h, h.get(), dangerStrength);
|
||||
return isSafeToVisit(h, h, dangerStrength);
|
||||
}
|
||||
|
||||
bool isObjectRemovable(const CGObjectInstance * obj)
|
||||
@ -235,11 +227,6 @@ bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj)
|
||||
return isObjectPassable(obj, ai->playerID, ai->cb->getPlayerRelations(obj->tempOwner, ai->playerID));
|
||||
}
|
||||
|
||||
bool isObjectPassable(const CGObjectInstance * obj)
|
||||
{
|
||||
return isObjectPassable(obj, ai->playerID, ai->myCb->getPlayerRelations(obj->tempOwner, ai->playerID));
|
||||
}
|
||||
|
||||
// Pathfinder internal helper
|
||||
bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations objectRelations)
|
||||
{
|
||||
@ -285,7 +272,7 @@ creInfo infoFromDC(const dwellingContent & dc)
|
||||
return ci;
|
||||
}
|
||||
|
||||
bool compareHeroStrength(HeroPtr h1, HeroPtr h2)
|
||||
bool compareHeroStrength(const CGHeroInstance * h1, const CGHeroInstance * h2)
|
||||
{
|
||||
return h1->getTotalStrength() < h2->getTotalStrength();
|
||||
}
|
||||
@ -306,7 +293,7 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
|
||||
return art1->getPrice() > art2->getPrice();
|
||||
}
|
||||
|
||||
bool isWeeklyRevisitable(const CGObjectInstance * obj)
|
||||
bool isWeeklyRevisitable(const Nullkiller * ai, const CGObjectInstance * obj)
|
||||
{
|
||||
if(!obj)
|
||||
return false;
|
||||
|
@ -50,7 +50,6 @@
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using namespace tbb;
|
||||
|
||||
using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
|
||||
|
||||
@ -71,7 +70,6 @@ extern const float SAFE_ATTACK_CONSTANT;
|
||||
extern const int GOLD_RESERVE;
|
||||
|
||||
extern thread_local CCallback * cb;
|
||||
extern thread_local AIGateway * ai;
|
||||
|
||||
enum HeroRole
|
||||
{
|
||||
@ -115,11 +113,11 @@ public:
|
||||
bool validAndSet() const;
|
||||
|
||||
|
||||
template<typename Handler> void serialize(Handler & h)
|
||||
template<typename Handler> void serialize(Handler & handler)
|
||||
{
|
||||
h & this->h;
|
||||
h & hid;
|
||||
h & name;
|
||||
handler & h;
|
||||
handler & hid;
|
||||
handler & name;
|
||||
}
|
||||
};
|
||||
|
||||
@ -225,18 +223,17 @@ void foreach_neighbour(CCallback * cbp, const int3 & pos, const Func & foo) // a
|
||||
}
|
||||
|
||||
bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
|
||||
bool isObjectPassable(const CGObjectInstance * obj);
|
||||
bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj);
|
||||
bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations objectRelations);
|
||||
bool isBlockVisitObj(const int3 & pos);
|
||||
|
||||
bool isWeeklyRevisitable(const CGObjectInstance * obj);
|
||||
bool isWeeklyRevisitable(const Nullkiller * ai, 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(const CGHeroInstance * h, uint64_t dangerStrength);
|
||||
bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet *, uint64_t dangerStrength);
|
||||
|
||||
bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
|
||||
bool compareHeroStrength(const CGHeroInstance * h1, const CGHeroInstance * h2);
|
||||
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
|
||||
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2);
|
||||
bool townHasFreeTavern(const CGTownInstance * town);
|
||||
@ -246,38 +243,6 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
|
||||
// todo: move to obj manager
|
||||
bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj);
|
||||
|
||||
template<typename TFunc>
|
||||
void pforeachTilePos(const int3 & mapSize, TFunc fn)
|
||||
{
|
||||
for(int z = 0; z < mapSize.z; ++z)
|
||||
{
|
||||
parallel_for(blocked_range<size_t>(0, mapSize.x), [&](const blocked_range<size_t>& r)
|
||||
{
|
||||
int3 pos(0, 0, z);
|
||||
|
||||
for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
|
||||
{
|
||||
for(pos.y = 0; pos.y < mapSize.y; ++pos.y)
|
||||
{
|
||||
fn(pos);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class CDistanceSorter
|
||||
{
|
||||
const CGHeroInstance * hero;
|
||||
|
||||
public:
|
||||
CDistanceSorter(const CGHeroInstance * hero)
|
||||
: hero(hero)
|
||||
{
|
||||
}
|
||||
bool operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class SharedPool
|
||||
{
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "DangerHitMapAnalyzer.h"
|
||||
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../pforeach.h"
|
||||
#include "../../../lib/CRandomGenerator.h"
|
||||
|
||||
namespace NKAI
|
||||
@ -82,9 +83,9 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
pforeachTilePos(mapSize, [&](const int3 & pos)
|
||||
pforeachTilePaths(mapSize, ai, [&](const int3 & pos, const std::vector<AIPath> & paths)
|
||||
{
|
||||
for(AIPath & path : ai->pathfinder->getPathInfo(pos))
|
||||
for(const AIPath & path : paths)
|
||||
{
|
||||
if(path.getFirstBlockedAction())
|
||||
continue;
|
||||
@ -194,14 +195,14 @@ void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
|
||||
ai->pathfinder->updatePaths(townHeroes, ps);
|
||||
|
||||
pforeachTilePos(mapSize, [&](const int3 & pos)
|
||||
pforeachTilePaths(mapSize, ai, [&](const int3 & pos, const std::vector<AIPath> & paths)
|
||||
{
|
||||
float ourDistance = std::numeric_limits<float>::max();
|
||||
float enemyDistance = std::numeric_limits<float>::max();
|
||||
const CGTownInstance * enemyTown = nullptr;
|
||||
const CGTownInstance * ourTown = nullptr;
|
||||
|
||||
for(AIPath & path : ai->pathfinder->getPathInfo(pos))
|
||||
for(const AIPath & path : paths)
|
||||
{
|
||||
if(!path.targetHero || path.getFirstBlockedAction())
|
||||
continue;
|
||||
|
@ -109,6 +109,7 @@ void HeroManager::update()
|
||||
for(auto & hero : myHeroes)
|
||||
{
|
||||
scores[hero] = evaluateFightingStrength(hero);
|
||||
knownFightingStrength[hero->id] = hero->getFightingStrength();
|
||||
}
|
||||
|
||||
auto scoreSort = [&](const CGHeroInstance * h1, const CGHeroInstance * h2) -> bool
|
||||
@ -192,6 +193,13 @@ bool HeroManager::heroCapReached() const
|
||||
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
|
||||
}
|
||||
|
||||
float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const
|
||||
{
|
||||
auto cached = knownFightingStrength.find(hero->id);
|
||||
|
||||
return cached != knownFightingStrength.end() ? cached->second : hero->getFightingStrength();
|
||||
}
|
||||
|
||||
float HeroManager::getMagicStrength(const CGHeroInstance * hero) const
|
||||
{
|
||||
auto hasFly = hero->spellbookContainsSpell(SpellID::FLY);
|
||||
|
@ -20,23 +20,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
class DLL_EXPORT IHeroManager //: public: IAbstractManager
|
||||
{
|
||||
public:
|
||||
virtual ~IHeroManager() = default;
|
||||
virtual const std::map<HeroPtr, HeroRole> & getHeroRoles() const = 0;
|
||||
virtual int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const = 0;
|
||||
virtual HeroRole getHeroRole(const HeroPtr & hero) const = 0;
|
||||
virtual void update() = 0;
|
||||
virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0;
|
||||
virtual float evaluateHero(const CGHeroInstance * hero) const = 0;
|
||||
virtual bool canRecruitHero(const CGTownInstance * t = nullptr) const = 0;
|
||||
virtual bool heroCapReached() const = 0;
|
||||
virtual const CGHeroInstance * findHeroWithGrail() const = 0;
|
||||
virtual const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const = 0;
|
||||
virtual float getMagicStrength(const CGHeroInstance * hero) const = 0;
|
||||
};
|
||||
|
||||
class DLL_EXPORT ISecondarySkillRule
|
||||
{
|
||||
public:
|
||||
@ -55,7 +38,7 @@ public:
|
||||
float evaluateSecSkill(const CGHeroInstance * hero, SecondarySkill skill) const;
|
||||
};
|
||||
|
||||
class DLL_EXPORT HeroManager : public IHeroManager
|
||||
class DLL_EXPORT HeroManager
|
||||
{
|
||||
private:
|
||||
static const SecondarySkillEvaluator wariorSkillsScores;
|
||||
@ -64,20 +47,22 @@ private:
|
||||
CCallback * cb; //this is enough, but we downcast from CCallback
|
||||
const Nullkiller * ai;
|
||||
std::map<HeroPtr, HeroRole> heroRoles;
|
||||
std::map<ObjectInstanceID, float> knownFightingStrength;
|
||||
|
||||
public:
|
||||
HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {}
|
||||
const std::map<HeroPtr, HeroRole> & getHeroRoles() const override;
|
||||
HeroRole getHeroRole(const HeroPtr & hero) const override;
|
||||
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override;
|
||||
void update() override;
|
||||
float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override;
|
||||
float evaluateHero(const CGHeroInstance * hero) const override;
|
||||
bool canRecruitHero(const CGTownInstance * t = nullptr) const override;
|
||||
bool heroCapReached() const override;
|
||||
const CGHeroInstance * findHeroWithGrail() const override;
|
||||
const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const override;
|
||||
float getMagicStrength(const CGHeroInstance * hero) const override;
|
||||
const std::map<HeroPtr, HeroRole> & getHeroRoles() const;
|
||||
HeroRole getHeroRole(const HeroPtr & hero) const;
|
||||
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const;
|
||||
void update();
|
||||
float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const;
|
||||
float evaluateHero(const CGHeroInstance * hero) const;
|
||||
bool canRecruitHero(const CGTownInstance * t = nullptr) const;
|
||||
bool heroCapReached() const;
|
||||
const CGHeroInstance * findHeroWithGrail() const;
|
||||
const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const;
|
||||
float getMagicStrength(const CGHeroInstance * hero) const;
|
||||
float getFightingStrengthCached(const CGHeroInstance * hero) const;
|
||||
|
||||
private:
|
||||
float evaluateFightingStrength(const CGHeroInstance * hero) const;
|
||||
|
@ -90,64 +90,74 @@ std::vector<std::shared_ptr<ObjectCluster>> ObjectClusterizer::getLockedClusters
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<const CGObjectInstance *> ObjectClusterizer::getBlocker(const AIPathNodeInfo & node) const
|
||||
{
|
||||
std::vector<const CGObjectInstance *> blockers = {};
|
||||
|
||||
if(node.layer == EPathfindingLayer::LAND || node.layer == EPathfindingLayer::SAIL)
|
||||
{
|
||||
auto guardPos = ai->cb->getGuardingCreaturePosition(node.coord);
|
||||
|
||||
blockers = ai->cb->getVisitableObjs(node.coord);
|
||||
|
||||
if(guardPos.valid())
|
||||
{
|
||||
auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node.coord));
|
||||
|
||||
if(guard)
|
||||
{
|
||||
blockers.insert(blockers.begin(), guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(node.specialAction && node.actionIsBlocked)
|
||||
{
|
||||
auto blockerObject = node.specialAction->targetObject();
|
||||
|
||||
if(blockerObject)
|
||||
{
|
||||
blockers.insert(blockers.begin(), blockerObject);
|
||||
}
|
||||
}
|
||||
|
||||
if(blockers.empty())
|
||||
return std::optional< const CGObjectInstance *>();
|
||||
|
||||
auto blocker = blockers.front();
|
||||
|
||||
if(isObjectPassable(ai, blocker))
|
||||
return std::optional< const CGObjectInstance *>();
|
||||
|
||||
if(blocker->ID == Obj::GARRISON
|
||||
|| blocker->ID == Obj::GARRISON2)
|
||||
{
|
||||
if(dynamic_cast<const CArmedInstance *>(blocker)->getArmyStrength() == 0)
|
||||
return std::optional< const CGObjectInstance *>();
|
||||
else
|
||||
return blocker;
|
||||
}
|
||||
|
||||
if(blocker->ID == Obj::MONSTER
|
||||
|| blocker->ID == Obj::BORDERGUARD
|
||||
|| blocker->ID == Obj::BORDER_GATE
|
||||
|| blocker->ID == Obj::SHIPYARD
|
||||
|| (blocker->ID == Obj::QUEST_GUARD && node.actionIsBlocked))
|
||||
{
|
||||
return blocker;
|
||||
}
|
||||
|
||||
return std::optional< const CGObjectInstance *>();
|
||||
}
|
||||
|
||||
const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) const
|
||||
{
|
||||
for(auto node = path.nodes.rbegin(); node != path.nodes.rend(); node++)
|
||||
{
|
||||
std::vector<const CGObjectInstance *> blockers = {};
|
||||
auto blocker = getBlocker(*node);
|
||||
|
||||
if(node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL)
|
||||
{
|
||||
auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord);
|
||||
|
||||
blockers = ai->cb->getVisitableObjs(node->coord);
|
||||
|
||||
if(guardPos.valid())
|
||||
{
|
||||
auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node->coord));
|
||||
|
||||
if(guard)
|
||||
{
|
||||
blockers.insert(blockers.begin(), guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(node->specialAction && node->actionIsBlocked)
|
||||
{
|
||||
auto blockerObject = node->specialAction->targetObject();
|
||||
|
||||
if(blockerObject)
|
||||
{
|
||||
blockers.insert(blockers.begin(), blockerObject);
|
||||
}
|
||||
}
|
||||
|
||||
if(blockers.empty())
|
||||
continue;
|
||||
|
||||
auto blocker = blockers.front();
|
||||
|
||||
if(isObjectPassable(ai, blocker))
|
||||
continue;
|
||||
|
||||
if(blocker->ID == Obj::GARRISON
|
||||
|| blocker->ID == Obj::GARRISON2)
|
||||
{
|
||||
if(dynamic_cast<const CArmedInstance *>(blocker)->getArmyStrength() == 0)
|
||||
continue;
|
||||
else
|
||||
return blocker;
|
||||
}
|
||||
|
||||
if(blocker->ID == Obj::MONSTER
|
||||
|| blocker->ID == Obj::BORDERGUARD
|
||||
|| blocker->ID == Obj::BORDER_GATE
|
||||
|| blocker->ID == Obj::SHIPYARD
|
||||
|| (blocker->ID == Obj::QUEST_GUARD && node->actionIsBlocked))
|
||||
{
|
||||
return blocker;
|
||||
}
|
||||
if(blocker)
|
||||
return *blocker;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@ -170,7 +180,7 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const
|
||||
|
||||
auto playerRelations = ai->cb->getPlayerRelations(ai->playerID, obj->tempOwner);
|
||||
|
||||
if(playerRelations != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj))
|
||||
if(playerRelations != PlayerRelations::ENEMIES && !isWeeklyRevisitable(ai, obj))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -225,15 +235,17 @@ void ObjectClusterizer::clusterize()
|
||||
ai->memory->visitableObjs.end());
|
||||
|
||||
#if NKAI_TRACE_LEVEL == 0
|
||||
parallel_for(blocked_range<size_t>(0, objs.size()), [&](const blocked_range<size_t> & r) {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, objs.size()), [&](const tbb::blocked_range<size_t> & r) {
|
||||
#else
|
||||
blocked_range<size_t> r(0, objs.size());
|
||||
tbb::blocked_range<size_t> r(0, objs.size());
|
||||
#endif
|
||||
auto priorityEvaluator = ai->priorityEvaluators->acquire();
|
||||
auto heroes = ai->cb->getHeroesInfo();
|
||||
std::vector<AIPath> pathCache;
|
||||
|
||||
for(int i = r.begin(); i != r.end(); i++)
|
||||
{
|
||||
clusterizeObject(objs[i], priorityEvaluator.get());
|
||||
clusterizeObject(objs[i], priorityEvaluator.get(), pathCache, heroes);
|
||||
}
|
||||
#if NKAI_TRACE_LEVEL == 0
|
||||
});
|
||||
@ -257,7 +269,11 @@ void ObjectClusterizer::clusterize()
|
||||
logAi->trace("Clusterization complete in %ld", timeElapsed(start));
|
||||
}
|
||||
|
||||
void ObjectClusterizer::clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator)
|
||||
void ObjectClusterizer::clusterizeObject(
|
||||
const CGObjectInstance * obj,
|
||||
PriorityEvaluator * priorityEvaluator,
|
||||
std::vector<AIPath> & pathCache,
|
||||
std::vector<const CGHeroInstance *> & heroes)
|
||||
{
|
||||
if(!shouldVisitObject(obj))
|
||||
{
|
||||
@ -271,9 +287,14 @@ void ObjectClusterizer::clusterizeObject(const CGObjectInstance * obj, PriorityE
|
||||
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
auto paths = ai->pathfinder->getPathInfo(obj->visitablePos(), true);
|
||||
if(ai->settings->isObjectGraphAllowed())
|
||||
{
|
||||
ai->pathfinder->calculateQuickPathsWithBlocker(pathCache, heroes, obj->visitablePos());
|
||||
}
|
||||
else
|
||||
ai->pathfinder->calculatePathInfo(pathCache, obj->visitablePos(), false);
|
||||
|
||||
if(paths.empty())
|
||||
if(pathCache.empty())
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("No paths found.");
|
||||
@ -281,17 +302,17 @@ void ObjectClusterizer::clusterizeObject(const CGObjectInstance * obj, PriorityE
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
|
||||
std::sort(pathCache.begin(), pathCache.end(), [](const AIPath & p1, const AIPath & p2) -> bool
|
||||
{
|
||||
return p1.movementCost() < p2.movementCost();
|
||||
});
|
||||
|
||||
if(vstd::contains(IgnoredObjectTypes, obj->ID))
|
||||
{
|
||||
farObjects.addObject(obj, paths.front(), 0);
|
||||
farObjects.addObject(obj, pathCache.front(), 0);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
|
||||
logAi->trace("Object ignored. Moved to far objects with path %s", pathCache.front().toString());
|
||||
#endif
|
||||
|
||||
return;
|
||||
@ -299,12 +320,35 @@ void ObjectClusterizer::clusterizeObject(const CGObjectInstance * obj, PriorityE
|
||||
|
||||
std::set<const CGHeroInstance *> heroesProcessed;
|
||||
|
||||
for(auto & path : paths)
|
||||
for(auto & path : pathCache)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Checking path %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(ai->heroManager->getHeroRole(path.targetHero) == HeroRole::SCOUT)
|
||||
{
|
||||
if(path.movementCost() > 2.0f)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path is too far %f", path.movementCost());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if(path.movementCost() > 4.0f && obj->ID != Obj::TOWN)
|
||||
{
|
||||
auto strategicalValue = valueEvaluator.getStrategicalValue(obj);
|
||||
|
||||
if(strategicalValue < 0.3f)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Object value is too low %f", strategicalValue);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(!shouldVisit(ai, path.targetHero, obj))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Pathfinding/AINodeStorage.h"
|
||||
#include "../Engine/PriorityEvaluator.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -49,8 +50,6 @@ public:
|
||||
|
||||
using ClusterMap = tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>>;
|
||||
|
||||
class PriorityEvaluator;
|
||||
|
||||
class ObjectClusterizer
|
||||
{
|
||||
private:
|
||||
@ -60,6 +59,7 @@ private:
|
||||
ObjectCluster farObjects;
|
||||
ClusterMap blockedObjects;
|
||||
const Nullkiller * ai;
|
||||
RewardEvaluator valueEvaluator;
|
||||
|
||||
public:
|
||||
void clusterize();
|
||||
@ -67,12 +67,17 @@ public:
|
||||
std::vector<const CGObjectInstance *> getFarObjects() const;
|
||||
std::vector<std::shared_ptr<ObjectCluster>> getLockedClusters() const;
|
||||
const CGObjectInstance * getBlocker(const AIPath & path) const;
|
||||
std::optional<const CGObjectInstance *> getBlocker(const AIPathNodeInfo & node) const;
|
||||
|
||||
ObjectClusterizer(const Nullkiller * ai): ai(ai) {}
|
||||
ObjectClusterizer(const Nullkiller * ai): ai(ai), valueEvaluator(ai) {}
|
||||
|
||||
private:
|
||||
bool shouldVisitObject(const CGObjectInstance * obj) const;
|
||||
void clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator);
|
||||
void clusterizeObject(
|
||||
const CGObjectInstance * obj,
|
||||
PriorityEvaluator * priorityEvaluator,
|
||||
std::vector<AIPath> & pathCache,
|
||||
std::vector<const CGHeroInstance *> & heroes);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -27,14 +27,14 @@ std::string BuildingBehavior::toString() const
|
||||
return "Build";
|
||||
}
|
||||
|
||||
Goals::TGoalVec BuildingBehavior::decompose() const
|
||||
Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
TResources resourcesRequired = ai->nullkiller->buildAnalyzer->getResourcesRequiredNow();
|
||||
TResources totalDevelopmentCost = ai->nullkiller->buildAnalyzer->getTotalResourcesRequired();
|
||||
TResources availableResources = ai->nullkiller->getFreeResources();
|
||||
TResources dailyIncome = ai->nullkiller->buildAnalyzer->getDailyIncome();
|
||||
TResources resourcesRequired = ai->buildAnalyzer->getResourcesRequiredNow();
|
||||
TResources totalDevelopmentCost = ai->buildAnalyzer->getTotalResourcesRequired();
|
||||
TResources availableResources = ai->getFreeResources();
|
||||
TResources dailyIncome = ai->buildAnalyzer->getDailyIncome();
|
||||
|
||||
logAi->trace("Free resources amount: %s", availableResources.toString());
|
||||
|
||||
@ -46,8 +46,8 @@ Goals::TGoalVec BuildingBehavior::decompose() const
|
||||
resourcesRequired.toString(),
|
||||
totalDevelopmentCost.toString());
|
||||
|
||||
auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo();
|
||||
auto isGoldPreasureLow = !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh();
|
||||
auto & developmentInfos = ai->buildAnalyzer->getDevelopmentInfo();
|
||||
auto isGoldPreasureLow = !ai->buildAnalyzer->isGoldPreasureHigh();
|
||||
|
||||
for(auto & developmentInfo : developmentInfos)
|
||||
{
|
||||
@ -57,7 +57,7 @@ Goals::TGoalVec BuildingBehavior::decompose() const
|
||||
{
|
||||
if(buildingInfo.notEnoughRes)
|
||||
{
|
||||
if(ai->nullkiller->getLockedResources().canAfford(buildingInfo.buildCost))
|
||||
if(ai->getLockedResources().canAfford(buildingInfo.buildCost))
|
||||
continue;
|
||||
|
||||
Composition composition;
|
||||
|
@ -25,7 +25,7 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
Goals::TGoalVec decompose() const override;
|
||||
Goals::TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
bool operator==(const BuildingBehavior & other) const override
|
||||
{
|
||||
|
@ -24,11 +24,11 @@ std::string BuyArmyBehavior::toString() const
|
||||
return "Buy army";
|
||||
}
|
||||
|
||||
Goals::TGoalVec BuyArmyBehavior::decompose() const
|
||||
Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
if(cb->getDate(Date::DAY) == 1)
|
||||
if(ai->cb->getDate(Date::DAY) == 1)
|
||||
return tasks;
|
||||
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
@ -40,26 +40,26 @@ Goals::TGoalVec BuyArmyBehavior::decompose() const
|
||||
|
||||
for(auto town : cb->getTownsInfo())
|
||||
{
|
||||
auto townArmyAvailableToBuy = ai->nullkiller->armyManager->getArmyAvailableToBuyAsCCreatureSet(
|
||||
auto townArmyAvailableToBuy = ai->armyManager->getArmyAvailableToBuyAsCCreatureSet(
|
||||
town,
|
||||
ai->nullkiller->getFreeResources());
|
||||
ai->getFreeResources());
|
||||
|
||||
for(const CGHeroInstance * targetHero : heroes)
|
||||
{
|
||||
if(ai->nullkiller->buildAnalyzer->isGoldPreasureHigh() && !town->hasBuilt(BuildingID::CITY_HALL))
|
||||
if(ai->buildAnalyzer->isGoldPreasureHigh() && !town->hasBuilt(BuildingID::CITY_HALL))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ai->nullkiller->heroManager->getHeroRole(targetHero) == HeroRole::MAIN)
|
||||
if(ai->heroManager->getHeroRole(targetHero) == HeroRole::MAIN)
|
||||
{
|
||||
auto reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanGet(
|
||||
auto reinforcement = ai->armyManager->howManyReinforcementsCanGet(
|
||||
targetHero,
|
||||
targetHero,
|
||||
&*townArmyAvailableToBuy);
|
||||
|
||||
if(reinforcement)
|
||||
vstd::amin(reinforcement, ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town));
|
||||
vstd::amin(reinforcement, ai->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town));
|
||||
|
||||
if(reinforcement)
|
||||
{
|
||||
|
@ -20,11 +20,7 @@ namespace Goals
|
||||
class BuyArmyBehavior : public CGoal<BuyArmyBehavior>
|
||||
{
|
||||
public:
|
||||
BuyArmyBehavior()
|
||||
{
|
||||
}
|
||||
|
||||
Goals::TGoalVec decompose() const override;
|
||||
Goals::TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
bool operator==(const BuyArmyBehavior & other) const override
|
||||
{
|
||||
|
@ -47,7 +47,7 @@ bool CaptureObjectsBehavior::operator==(const CaptureObjectsBehavior & other) co
|
||||
&& vectorEquals(objectSubTypes, other.objectSubTypes);
|
||||
}
|
||||
|
||||
Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit)
|
||||
Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath> & paths, const Nullkiller * nullkiller, const CGObjectInstance * objToVisit)
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
@ -64,7 +64,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
logAi->trace("Path found %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
if(nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.getHeroStrength());
|
||||
@ -72,19 +72,31 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
continue;
|
||||
}
|
||||
|
||||
if(objToVisit && !shouldVisit(ai->nullkiller.get(), path.targetHero, objToVisit))
|
||||
if(objToVisit && !shouldVisit(nullkiller, path.targetHero, objToVisit))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Hero %s should not visit obj %s", path.targetHero->getNameTranslated(), objToVisit->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
auto hero = path.targetHero;
|
||||
auto danger = path.getTotalDanger();
|
||||
|
||||
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && path.exchangeCount > 1)
|
||||
if(nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT
|
||||
&& (path.getTotalDanger() == 0 || path.turn() > 0)
|
||||
&& path.exchangeCount > 1)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Hero %s is SCOUT, chain used and no danger", path.targetHero->getNameTranslated());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
auto firstBlockedAction = path.getFirstBlockedAction();
|
||||
if(firstBlockedAction)
|
||||
{
|
||||
auto subGoal = firstBlockedAction->decompose(path.targetHero);
|
||||
auto subGoal = firstBlockedAction->decompose(nullkiller, path.targetHero);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Decomposing special action %s returns %s", firstBlockedAction->toString(), subGoal->toString());
|
||||
@ -123,7 +135,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
|
||||
sharedPtr.reset(newWay);
|
||||
|
||||
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
|
||||
auto heroRole = nullkiller->heroManager->getHeroRole(path.targetHero);
|
||||
|
||||
auto & closestWay = closestWaysByRole[heroRole];
|
||||
|
||||
@ -132,7 +144,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
closestWay = &path;
|
||||
}
|
||||
|
||||
if(!ai->nullkiller->arePathHeroesLocked(path))
|
||||
if(!nullkiller->arePathHeroesLocked(path))
|
||||
{
|
||||
waysToVisitObj.push_back(newWay);
|
||||
tasks[tasks.size() - 1] = sharedPtr;
|
||||
@ -142,7 +154,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
|
||||
for(auto way : waysToVisitObj)
|
||||
{
|
||||
auto heroRole = ai->nullkiller->heroManager->getHeroRole(way->getPath().targetHero);
|
||||
auto heroRole = nullkiller->heroManager->getHeroRole(way->getPath().targetHero);
|
||||
auto closestWay = closestWaysByRole[heroRole];
|
||||
|
||||
if(closestWay)
|
||||
@ -155,66 +167,80 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
return tasks;
|
||||
}
|
||||
|
||||
Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
||||
void CaptureObjectsBehavior::decomposeObjects(
|
||||
Goals::TGoalVec & result,
|
||||
const std::vector<const CGObjectInstance *> & objs,
|
||||
const Nullkiller * nullkiller) const
|
||||
{
|
||||
if(objs.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::mutex sync;
|
||||
|
||||
logAi->debug("Scanning objects, count %d", objs.size());
|
||||
|
||||
// tbb::blocked_range<size_t> r(0, objs.size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, objs.size()),
|
||||
[this, &objs, &sync, &result, nullkiller](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
std::vector<AIPath> paths;
|
||||
Goals::TGoalVec tasksLocal;
|
||||
|
||||
for(auto i = r.begin(); i != r.end(); i++)
|
||||
{
|
||||
auto objToVisit = objs[i];
|
||||
|
||||
if(!objectMatchesFilter(objToVisit))
|
||||
continue;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
nullkiller->pathfinder->calculatePathInfo(paths, objToVisit->visitablePos(), nullkiller->settings->isObjectGraphAllowed());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Found %d paths", paths.size());
|
||||
#endif
|
||||
vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit));
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(sync);
|
||||
vstd::concatenate(result, tasksLocal);
|
||||
});
|
||||
}
|
||||
|
||||
Goals::TGoalVec CaptureObjectsBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
auto captureObjects = [&](const std::vector<const CGObjectInstance*> & objs) -> void
|
||||
vstd::erase_if(tasks, [](TSubgoal task) -> bool
|
||||
{
|
||||
if(objs.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logAi->debug("Scanning objects, count %d", objs.size());
|
||||
|
||||
for(auto objToVisit : objs)
|
||||
{
|
||||
if(!objectMatchesFilter(objToVisit))
|
||||
continue;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
const int3 pos = objToVisit->visitablePos();
|
||||
bool useObjectGraph = ai->nullkiller->settings->isObjectGraphAllowed()
|
||||
&& ai->nullkiller->getScanDepth() != ScanDepth::SMALL;
|
||||
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos, useObjectGraph);
|
||||
|
||||
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
|
||||
std::shared_ptr<ExecuteHeroChain> closestWay;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Found %d paths", paths.size());
|
||||
#endif
|
||||
vstd::concatenate(tasks, getVisitGoals(paths, objToVisit));
|
||||
}
|
||||
|
||||
vstd::erase_if(tasks, [](TSubgoal task) -> bool
|
||||
{
|
||||
return task->invalid();
|
||||
});
|
||||
};
|
||||
return task->invalid();
|
||||
});
|
||||
|
||||
if(specificObjects)
|
||||
{
|
||||
captureObjects(objectsToCapture);
|
||||
decomposeObjects(tasks, objectsToCapture, ai);
|
||||
}
|
||||
else if(objectTypes.size())
|
||||
{
|
||||
captureObjects(
|
||||
decomposeObjects(
|
||||
tasks,
|
||||
std::vector<const CGObjectInstance *>(
|
||||
ai->nullkiller->memory->visitableObjs.begin(),
|
||||
ai->nullkiller->memory->visitableObjs.end()));
|
||||
ai->memory->visitableObjs.begin(),
|
||||
ai->memory->visitableObjs.end()),
|
||||
ai);
|
||||
}
|
||||
else
|
||||
{
|
||||
captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
|
||||
decomposeObjects(tasks, ai->objectClusterizer->getNearbyObjects(), ai);
|
||||
|
||||
if(tasks.empty() || ai->nullkiller->getScanDepth() != ScanDepth::SMALL)
|
||||
captureObjects(ai->nullkiller->objectClusterizer->getFarObjects());
|
||||
if(tasks.empty() || ai->getScanDepth() != ScanDepth::SMALL)
|
||||
decomposeObjects(tasks, ai->objectClusterizer->getFarObjects(), ai);
|
||||
}
|
||||
|
||||
return tasks;
|
||||
|
@ -25,6 +25,7 @@ namespace Goals
|
||||
std::vector<int> objectSubTypes;
|
||||
std::vector<const CGObjectInstance *> objectsToCapture;
|
||||
bool specificObjects;
|
||||
|
||||
public:
|
||||
CaptureObjectsBehavior()
|
||||
:CGoal(CAPTURE_OBJECTS)
|
||||
@ -48,7 +49,7 @@ namespace Goals
|
||||
specificObjects = true;
|
||||
}
|
||||
|
||||
Goals::TGoalVec decompose() const override;
|
||||
Goals::TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
CaptureObjectsBehavior & ofType(int type)
|
||||
@ -67,10 +68,14 @@ namespace Goals
|
||||
|
||||
bool operator==(const CaptureObjectsBehavior & other) const override;
|
||||
|
||||
static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit = nullptr);
|
||||
static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const Nullkiller * nullkiller, const CGObjectInstance * objToVisit = nullptr);
|
||||
|
||||
private:
|
||||
bool objectMatchesFilter(const CGObjectInstance * obj) const;
|
||||
void decomposeObjects(
|
||||
Goals::TGoalVec & result,
|
||||
const std::vector<const CGObjectInstance *> & objs,
|
||||
const Nullkiller * nullkiller) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,23 +26,24 @@ std::string ClusterBehavior::toString() const
|
||||
return "Unlock Clusters";
|
||||
}
|
||||
|
||||
Goals::TGoalVec ClusterBehavior::decompose() const
|
||||
Goals::TGoalVec ClusterBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
auto clusters = ai->nullkiller->objectClusterizer->getLockedClusters();
|
||||
auto clusters = ai->objectClusterizer->getLockedClusters();
|
||||
|
||||
for(auto cluster : clusters)
|
||||
{
|
||||
vstd::concatenate(tasks, decomposeCluster(cluster));
|
||||
vstd::concatenate(tasks, decomposeCluster(ai, cluster));
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const
|
||||
Goals::TGoalVec ClusterBehavior::decomposeCluster(const Nullkiller * ai, std::shared_ptr<ObjectCluster> cluster) const
|
||||
{
|
||||
auto center = cluster->calculateCenter();
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos(), ai->nullkiller->settings->isObjectGraphAllowed());
|
||||
auto paths = ai->pathfinder->getPathInfo(center->visitablePos(), ai->settings->isObjectGraphAllowed());
|
||||
|
||||
auto blockerPos = cluster->blocker->visitablePos();
|
||||
std::vector<AIPath> blockerPaths;
|
||||
|
||||
@ -64,7 +65,7 @@ Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster>
|
||||
logAi->trace("Checking path %s", path->toString());
|
||||
#endif
|
||||
|
||||
auto blocker = ai->nullkiller->objectClusterizer->getBlocker(*path);
|
||||
auto blocker = ai->objectClusterizer->getBlocker(*path);
|
||||
|
||||
if(blocker != cluster->blocker)
|
||||
{
|
||||
@ -82,7 +83,7 @@ Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster>
|
||||
{
|
||||
clonedPath.nodes.insert(clonedPath.nodes.begin(), *node);
|
||||
|
||||
if(node->coord == blockerPos || cb->getGuardingCreaturePosition(node->coord) == blockerPos)
|
||||
if(node->coord == blockerPos || ai->cb->getGuardingCreaturePosition(node->coord) == blockerPos)
|
||||
break;
|
||||
}
|
||||
|
||||
@ -99,7 +100,7 @@ Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster>
|
||||
logAi->trace("Decompose unlock paths");
|
||||
#endif
|
||||
|
||||
auto unlockTasks = CaptureObjectsBehavior::getVisitGoals(blockerPaths);
|
||||
auto unlockTasks = CaptureObjectsBehavior::getVisitGoals(blockerPaths, ai);
|
||||
|
||||
for(int i = 0; i < paths.size(); i++)
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
TGoalVec decompose() const override;
|
||||
TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
bool operator==(const ClusterBehavior & other) const override
|
||||
@ -37,7 +37,7 @@ namespace Goals
|
||||
}
|
||||
|
||||
private:
|
||||
Goals::TGoalVec decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const;
|
||||
Goals::TGoalVec decomposeCluster(const Nullkiller * ai, std::shared_ptr<ObjectCluster> cluster) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -34,21 +34,21 @@ std::string DefenceBehavior::toString() const
|
||||
return "Defend towns";
|
||||
}
|
||||
|
||||
Goals::TGoalVec DefenceBehavior::decompose() const
|
||||
Goals::TGoalVec DefenceBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
for(auto town : cb->getTownsInfo())
|
||||
for(auto town : ai->cb->getTownsInfo())
|
||||
{
|
||||
evaluateDefence(tasks, town);
|
||||
evaluateDefence(tasks, town, ai);
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
bool isThreatUnderControl(const CGTownInstance * town, const HitMapInfo & threat, const std::vector<AIPath> & paths)
|
||||
bool isThreatUnderControl(const CGTownInstance * town, const HitMapInfo & threat, const Nullkiller * ai, const std::vector<AIPath> & paths)
|
||||
{
|
||||
int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
|
||||
int dayOfWeek = ai->cb->getDate(Date::DAY_OF_WEEK);
|
||||
|
||||
for(const AIPath & path : paths)
|
||||
{
|
||||
@ -81,14 +81,15 @@ void handleCounterAttack(
|
||||
const CGTownInstance * town,
|
||||
const HitMapInfo & threat,
|
||||
const HitMapInfo & maximumDanger,
|
||||
const Nullkiller * ai,
|
||||
Goals::TGoalVec & tasks)
|
||||
{
|
||||
if(threat.hero.validAndSet()
|
||||
&& threat.turn <= 1
|
||||
&& (threat.danger == maximumDanger.danger || threat.turn < maximumDanger.turn))
|
||||
{
|
||||
auto heroCapturingPaths = ai->nullkiller->pathfinder->getPathInfo(threat.hero->visitablePos());
|
||||
auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, threat.hero.get());
|
||||
auto heroCapturingPaths = ai->pathfinder->getPathInfo(threat.hero->visitablePos());
|
||||
auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, ai, threat.hero.get());
|
||||
|
||||
for(int i = 0; i < heroCapturingPaths.size(); i++)
|
||||
{
|
||||
@ -106,9 +107,9 @@ void handleCounterAttack(
|
||||
}
|
||||
}
|
||||
|
||||
bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoalVec & tasks)
|
||||
bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoalVec & tasks, const Nullkiller * ai)
|
||||
{
|
||||
if(ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
|
||||
if(ai->isHeroLocked(town->garrisonHero.get()))
|
||||
{
|
||||
logAi->trace(
|
||||
"Hero %s in garrison of town %s is supposed to defend the town",
|
||||
@ -120,7 +121,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
||||
|
||||
if(!town->visitingHero)
|
||||
{
|
||||
if(cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
|
||||
if(ai->cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
|
||||
{
|
||||
logAi->trace(
|
||||
"Extracting hero %s from garrison of town %s",
|
||||
@ -131,10 +132,10 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
||||
|
||||
return true;
|
||||
}
|
||||
else if(ai->nullkiller->heroManager->getHeroRole(town->garrisonHero.get()) == HeroRole::MAIN)
|
||||
else if(ai->heroManager->getHeroRole(town->garrisonHero.get()) == HeroRole::MAIN)
|
||||
{
|
||||
auto armyDismissLimit = 1000;
|
||||
auto heroToDismiss = ai->nullkiller->heroManager->findWeakHeroToDismiss(armyDismissLimit);
|
||||
auto heroToDismiss = ai->heroManager->findWeakHeroToDismiss(armyDismissLimit);
|
||||
|
||||
if(heroToDismiss)
|
||||
{
|
||||
@ -148,16 +149,16 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
||||
return false;
|
||||
}
|
||||
|
||||
void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
|
||||
void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town, const Nullkiller * ai) const
|
||||
{
|
||||
logAi->trace("Evaluating defence for %s", town->getNameTranslated());
|
||||
|
||||
auto threatNode = ai->nullkiller->dangerHitMap->getObjectThreat(town);
|
||||
std::vector<HitMapInfo> threats = ai->nullkiller->dangerHitMap->getTownThreats(town);
|
||||
auto threatNode = ai->dangerHitMap->getObjectThreat(town);
|
||||
std::vector<HitMapInfo> threats = ai->dangerHitMap->getTownThreats(town);
|
||||
|
||||
threats.push_back(threatNode.fastestDanger); // no guarantee that fastest danger will be there
|
||||
|
||||
if(town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks))
|
||||
if(town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks, ai))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -169,7 +170,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
|
||||
uint64_t reinforcement = ai->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
|
||||
|
||||
if(reinforcement)
|
||||
{
|
||||
@ -177,7 +178,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(0.5f)));
|
||||
}
|
||||
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(town->visitablePos());
|
||||
auto paths = ai->pathfinder->getPathInfo(town->visitablePos());
|
||||
|
||||
for(auto & threat : threats)
|
||||
{
|
||||
@ -188,14 +189,14 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
std::to_string(threat.turn),
|
||||
threat.hero ? threat.hero->getNameTranslated() : std::string("<no hero>"));
|
||||
|
||||
handleCounterAttack(town, threat, threatNode.maximumDanger, tasks);
|
||||
handleCounterAttack(town, threat, threatNode.maximumDanger, ai, tasks);
|
||||
|
||||
if(isThreatUnderControl(town, threat, paths))
|
||||
if(isThreatUnderControl(town, threat, ai, paths))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
evaluateRecruitingHero(tasks, threat, town);
|
||||
evaluateRecruitingHero(tasks, threat, town, ai);
|
||||
|
||||
if(paths.empty())
|
||||
{
|
||||
@ -274,7 +275,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
// main without army and visiting scout with army, very specific case
|
||||
if(town->visitingHero && town->getUpperArmy()->stacksCount() == 0
|
||||
&& path.targetHero != town->visitingHero.get() && path.exchangeCount == 1 && path.turn() == 0
|
||||
&& ai->nullkiller->heroManager->evaluateHero(path.targetHero) > ai->nullkiller->heroManager->evaluateHero(town->visitingHero.get())
|
||||
&& ai->heroManager->evaluateHero(path.targetHero) > ai->heroManager->evaluateHero(town->visitingHero.get())
|
||||
&& 10 * path.targetHero->getTotalStrength() < town->visitingHero->getTotalStrength())
|
||||
{
|
||||
path.heroArmy = town->visitingHero.get();
|
||||
@ -293,7 +294,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
|
||||
if(threat.turn == 0 || (path.turn() <= threat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= threat.danger))
|
||||
{
|
||||
if(ai->nullkiller->arePathHeroesLocked(path))
|
||||
if(ai->arePathHeroesLocked(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Can not move %s to defend town %s. Path is locked.",
|
||||
@ -344,7 +345,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
{
|
||||
if(town->garrisonHero)
|
||||
{
|
||||
if(ai->nullkiller->heroManager->getHeroRole(town->visitingHero.get()) == HeroRole::SCOUT
|
||||
if(ai->heroManager->getHeroRole(town->visitingHero.get()) == HeroRole::SCOUT
|
||||
&& town->visitingHero->getArmyStrength() < path.heroArmy->getArmyStrength() / 20)
|
||||
{
|
||||
if(path.turn() == 0)
|
||||
@ -378,7 +379,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
auto firstBlockedAction = path.getFirstBlockedAction();
|
||||
if(firstBlockedAction)
|
||||
{
|
||||
auto subGoal = firstBlockedAction->decompose(path.targetHero);
|
||||
auto subGoal = firstBlockedAction->decompose(ai, path.targetHero);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Decomposing special action %s returns %s", firstBlockedAction->toString(), subGoal->toString());
|
||||
@ -402,19 +403,19 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
logAi->debug("Found %d tasks", tasks.size());
|
||||
}
|
||||
|
||||
void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & threat, const CGTownInstance * town) const
|
||||
void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & threat, const CGTownInstance * town, const Nullkiller * ai) const
|
||||
{
|
||||
if(town->hasBuilt(BuildingID::TAVERN)
|
||||
&& cb->getResourceAmount(EGameResID::GOLD) > GameConstants::HERO_GOLD_COST)
|
||||
&& ai->cb->getResourceAmount(EGameResID::GOLD) > GameConstants::HERO_GOLD_COST)
|
||||
{
|
||||
auto heroesInTavern = cb->getAvailableHeroes(town);
|
||||
auto heroesInTavern = ai->cb->getAvailableHeroes(town);
|
||||
|
||||
for(auto hero : heroesInTavern)
|
||||
{
|
||||
if(hero->getTotalStrength() < threat.danger)
|
||||
continue;
|
||||
|
||||
auto myHeroes = cb->getHeroesInfo();
|
||||
auto myHeroes = ai->cb->getHeroesInfo();
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Hero %s can be recruited to defend %s", hero->getObjectName(), town->getObjectName());
|
||||
@ -448,9 +449,9 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
|
||||
if(heroToDismiss && heroToDismiss->getArmyStrength() + 500 > hero->getArmyStrength())
|
||||
continue;
|
||||
}
|
||||
else if(ai->nullkiller->heroManager->heroCapReached())
|
||||
else if(ai->heroManager->heroCapReached())
|
||||
{
|
||||
heroToDismiss = ai->nullkiller->heroManager->findWeakHeroToDismiss(hero->getArmyStrength());
|
||||
heroToDismiss = ai->heroManager->findWeakHeroToDismiss(hero->getArmyStrength());
|
||||
|
||||
if(!heroToDismiss)
|
||||
continue;
|
||||
|
@ -20,7 +20,6 @@ struct HitMapInfo;
|
||||
|
||||
namespace Goals
|
||||
{
|
||||
|
||||
class DefenceBehavior : public CGoal<DefenceBehavior>
|
||||
{
|
||||
public:
|
||||
@ -29,7 +28,7 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
Goals::TGoalVec decompose() const override;
|
||||
Goals::TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
bool operator==(const DefenceBehavior & other) const override
|
||||
@ -38,8 +37,8 @@ namespace Goals
|
||||
}
|
||||
|
||||
private:
|
||||
void evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const;
|
||||
void evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & threat, const CGTownInstance * town) const;
|
||||
void evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town, const Nullkiller * ai) const;
|
||||
void evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & threat, const CGTownInstance * town, const Nullkiller * ai) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,11 @@ std::string GatherArmyBehavior::toString() const
|
||||
return "Gather army";
|
||||
}
|
||||
|
||||
Goals::TGoalVec GatherArmyBehavior::decompose() const
|
||||
Goals::TGoalVec GatherArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto heroes = ai->cb->getHeroesInfo();
|
||||
|
||||
if(heroes.empty())
|
||||
{
|
||||
@ -43,33 +43,33 @@ Goals::TGoalVec GatherArmyBehavior::decompose() const
|
||||
|
||||
for(const CGHeroInstance * hero : heroes)
|
||||
{
|
||||
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN)
|
||||
if(ai->heroManager->getHeroRole(hero) == HeroRole::MAIN)
|
||||
{
|
||||
vstd::concatenate(tasks, deliverArmyToHero(hero));
|
||||
vstd::concatenate(tasks, deliverArmyToHero(ai, hero));
|
||||
}
|
||||
}
|
||||
|
||||
auto towns = cb->getTownsInfo();
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
for(const CGTownInstance * town : towns)
|
||||
{
|
||||
vstd::concatenate(tasks, upgradeArmy(town));
|
||||
vstd::concatenate(tasks, upgradeArmy(ai, town));
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * hero) const
|
||||
Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
const int3 pos = hero->visitablePos();
|
||||
auto targetHeroScore = ai->nullkiller->heroManager->evaluateHero(hero);
|
||||
auto targetHeroScore = ai->heroManager->evaluateHero(hero);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
|
||||
#endif
|
||||
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
|
||||
auto paths = ai->pathfinder->getPathInfo(pos, ai->settings->isObjectGraphAllowed());
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Gather army found %d paths", paths.size());
|
||||
@ -89,7 +89,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.turn() > 0 && ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
if(path.turn() > 0 && ai->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
||||
@ -97,7 +97,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ai->nullkiller->arePathHeroesLocked(path))
|
||||
if(ai->arePathHeroesLocked(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path because of locked hero");
|
||||
@ -107,7 +107,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
||||
|
||||
HeroExchange heroExchange(hero, path);
|
||||
|
||||
uint64_t armyValue = heroExchange.getReinforcementArmyStrength();
|
||||
uint64_t armyValue = heroExchange.getReinforcementArmyStrength(ai);
|
||||
float armyRatio = (float)armyValue / hero->getArmyStrength();
|
||||
|
||||
// avoid transferring very small amount of army
|
||||
@ -126,11 +126,11 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
||||
{
|
||||
if(!node.targetHero) continue;
|
||||
|
||||
auto heroRole = ai->nullkiller->heroManager->getHeroRole(node.targetHero);
|
||||
auto heroRole = ai->heroManager->getHeroRole(node.targetHero);
|
||||
|
||||
if(heroRole == HeroRole::MAIN)
|
||||
{
|
||||
auto score = ai->nullkiller->heroManager->evaluateHero(node.targetHero);
|
||||
auto score = ai->heroManager->evaluateHero(node.targetHero);
|
||||
|
||||
if(score >= targetHeroScore)
|
||||
{
|
||||
@ -174,7 +174,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
||||
|
||||
if(hero->inTownGarrison && path.turn() == 0)
|
||||
{
|
||||
auto lockReason = ai->nullkiller->getHeroLockedReason(hero);
|
||||
auto lockReason = ai->getHeroLockedReason(hero);
|
||||
|
||||
if(path.targetHero->visitedTown == hero->visitedTown)
|
||||
{
|
||||
@ -201,7 +201,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Action is blocked. Considering decomposition.");
|
||||
#endif
|
||||
auto subGoal = blockedAction->decompose(path.targetHero);
|
||||
auto subGoal = blockedAction->decompose(ai, path.targetHero);
|
||||
|
||||
if(subGoal->invalid())
|
||||
{
|
||||
@ -221,18 +221,18 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
|
||||
return tasks;
|
||||
}
|
||||
|
||||
Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) const
|
||||
Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGTownInstance * upgrader) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
const int3 pos = upgrader->visitablePos();
|
||||
TResources availableResources = ai->nullkiller->getFreeResources();
|
||||
TResources availableResources = ai->getFreeResources();
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Checking ways to upgrade army in town %s, %s", upgrader->getObjectName(), pos.toString());
|
||||
#endif
|
||||
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
|
||||
auto goals = CaptureObjectsBehavior::getVisitGoals(paths);
|
||||
auto paths = ai->pathfinder->getPathInfo(pos, ai->settings->isObjectGraphAllowed());
|
||||
auto goals = CaptureObjectsBehavior::getVisitGoals(paths, ai);
|
||||
|
||||
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
|
||||
|
||||
@ -244,9 +244,9 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
|
||||
for(const AIPath & path : paths)
|
||||
{
|
||||
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
|
||||
auto heroRole = ai->heroManager->getHeroRole(path.targetHero);
|
||||
|
||||
if(heroRole == HeroRole::MAIN && path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit())
|
||||
if(heroRole == HeroRole::MAIN && path.turn() < ai->settings->getScoutHeroTurnDistanceLimit())
|
||||
hasMainAround = true;
|
||||
}
|
||||
|
||||
@ -275,7 +275,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ai->nullkiller->arePathHeroesLocked(path))
|
||||
if(ai->arePathHeroesLocked(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path because of locked hero");
|
||||
@ -292,10 +292,10 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
|
||||
auto heroRole = ai->heroManager->getHeroRole(path.targetHero);
|
||||
|
||||
if(heroRole == HeroRole::SCOUT
|
||||
&& ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
&& ai->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
|
||||
@ -303,17 +303,17 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
|
||||
auto upgrade = ai->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
|
||||
|
||||
if(!upgrader->garrisonHero
|
||||
&& (
|
||||
hasMainAround
|
||||
|| ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN))
|
||||
|| ai->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN))
|
||||
{
|
||||
ArmyUpgradeInfo armyToGetOrBuy;
|
||||
|
||||
armyToGetOrBuy.addArmyToGet(
|
||||
ai->nullkiller->armyManager->getBestArmy(
|
||||
ai->armyManager->getBestArmy(
|
||||
path.targetHero,
|
||||
path.heroArmy,
|
||||
upgrader->getUpperArmy()));
|
||||
@ -321,11 +321,11 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength();
|
||||
|
||||
armyToGetOrBuy.addArmyToBuy(
|
||||
ai->nullkiller->armyManager->toSlotInfo(
|
||||
ai->nullkiller->armyManager->getArmyAvailableToBuy(
|
||||
ai->armyManager->toSlotInfo(
|
||||
ai->armyManager->getArmyAvailableToBuy(
|
||||
path.heroArmy,
|
||||
upgrader,
|
||||
ai->nullkiller->getFreeResources(),
|
||||
ai->getFreeResources(),
|
||||
path.turn())));
|
||||
|
||||
upgrade.upgradeValue += armyToGetOrBuy.upgradeValue;
|
||||
@ -334,17 +334,17 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
||||
|
||||
if(!upgrade.upgradeValue
|
||||
&& armyToGetOrBuy.upgradeValue > 20000
|
||||
&& ai->nullkiller->heroManager->canRecruitHero(town)
|
||||
&& path.turn() < ai->nullkiller->settings->getScoutHeroTurnDistanceLimit())
|
||||
&& ai->heroManager->canRecruitHero(town)
|
||||
&& path.turn() < ai->settings->getScoutHeroTurnDistanceLimit())
|
||||
{
|
||||
for(auto hero : cb->getAvailableHeroes(town))
|
||||
{
|
||||
auto scoutReinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(hero, town)
|
||||
+ ai->nullkiller->armyManager->howManyReinforcementsCanGet(hero, town);
|
||||
auto scoutReinforcement = ai->armyManager->howManyReinforcementsCanBuy(hero, town)
|
||||
+ ai->armyManager->howManyReinforcementsCanGet(hero, town);
|
||||
|
||||
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
||||
&& ai->nullkiller->getFreeGold() >20000
|
||||
&& !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh())
|
||||
&& ai->getFreeGold() >20000
|
||||
&& !ai->buildAnalyzer->isGoldPreasureHigh())
|
||||
{
|
||||
Composition recruitHero;
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
TGoalVec decompose() const override;
|
||||
TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
bool operator==(const GatherArmyBehavior & other) const override
|
||||
@ -34,8 +34,8 @@ namespace Goals
|
||||
}
|
||||
|
||||
private:
|
||||
TGoalVec deliverArmyToHero(const CGHeroInstance * hero) const;
|
||||
TGoalVec upgradeArmy(const CGTownInstance * upgrader) const;
|
||||
TGoalVec deliverArmyToHero(const Nullkiller * ai, const CGHeroInstance * hero) const;
|
||||
TGoalVec upgradeArmy(const Nullkiller * ai, const CGTownInstance * upgrader) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -24,12 +24,12 @@ std::string RecruitHeroBehavior::toString() const
|
||||
return "Recruit hero";
|
||||
}
|
||||
|
||||
Goals::TGoalVec RecruitHeroBehavior::decompose() const
|
||||
Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
auto towns = cb->getTownsInfo();
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
auto ourHeroes = ai->nullkiller->heroManager->getHeroRoles();
|
||||
auto ourHeroes = ai->heroManager->getHeroRoles();
|
||||
auto minScoreToHireMain = std::numeric_limits<float>::max();
|
||||
|
||||
for(auto hero : ourHeroes)
|
||||
@ -37,7 +37,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
|
||||
if(hero.second != HeroRole::MAIN)
|
||||
continue;
|
||||
|
||||
auto newScore = ai->nullkiller->heroManager->evaluateHero(hero.first.get());
|
||||
auto newScore = ai->heroManager->evaluateHero(hero.first.get());
|
||||
|
||||
if(minScoreToHireMain > newScore)
|
||||
{
|
||||
@ -48,13 +48,13 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
|
||||
|
||||
for(auto town : towns)
|
||||
{
|
||||
if(ai->nullkiller->heroManager->canRecruitHero(town))
|
||||
if(ai->heroManager->canRecruitHero(town))
|
||||
{
|
||||
auto availableHeroes = cb->getAvailableHeroes(town);
|
||||
auto availableHeroes = ai->cb->getAvailableHeroes(town);
|
||||
|
||||
for(auto hero : availableHeroes)
|
||||
{
|
||||
auto score = ai->nullkiller->heroManager->evaluateHero(hero);
|
||||
auto score = ai->heroManager->evaluateHero(hero);
|
||||
|
||||
if(score > minScoreToHireMain)
|
||||
{
|
||||
@ -65,16 +65,16 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
|
||||
|
||||
int treasureSourcesCount = 0;
|
||||
|
||||
for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
|
||||
for(auto obj : ai->objectClusterizer->getNearbyObjects())
|
||||
{
|
||||
if((obj->ID == Obj::RESOURCE)
|
||||
|| obj->ID == Obj::TREASURE_CHEST
|
||||
|| obj->ID == Obj::CAMPFIRE
|
||||
|| isWeeklyRevisitable(obj)
|
||||
|| isWeeklyRevisitable(ai, obj)
|
||||
|| obj->ID ==Obj::ARTIFACT)
|
||||
{
|
||||
auto tile = obj->visitablePos();
|
||||
auto closestTown = ai->nullkiller->dangerHitMap->getClosestTown(tile);
|
||||
auto closestTown = ai->dangerHitMap->getClosestTown(tile);
|
||||
|
||||
if(town == closestTown)
|
||||
treasureSourcesCount++;
|
||||
@ -84,8 +84,8 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const
|
||||
if(treasureSourcesCount < 5 && (town->garrisonHero || town->getUpperArmy()->getArmyStrength() < 10000))
|
||||
continue;
|
||||
|
||||
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|
||||
|| (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->nullkiller->buildAnalyzer->isGoldPreasureHigh()))
|
||||
if(ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPreasureHigh()))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3)));
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
TGoalVec decompose() const override;
|
||||
TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
bool operator==(const RecruitHeroBehavior & other) const override
|
||||
|
@ -41,9 +41,9 @@ const AIPath getShortestPath(const CGTownInstance * town, const std::vector<AIPa
|
||||
return shortestPath;
|
||||
}
|
||||
|
||||
const CGHeroInstance * getNearestHero(const CGTownInstance * town)
|
||||
const CGHeroInstance * getNearestHero(const Nullkiller * ai, const CGTownInstance * town)
|
||||
{
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(town->visitablePos());
|
||||
auto paths = ai->pathfinder->getPathInfo(town->visitablePos());
|
||||
|
||||
if(paths.empty())
|
||||
return nullptr;
|
||||
@ -59,9 +59,9 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town)
|
||||
return shortestPath.targetHero;
|
||||
}
|
||||
|
||||
bool needToRecruitHero(const CGTownInstance * startupTown)
|
||||
bool needToRecruitHero(const Nullkiller * ai, const CGTownInstance * startupTown)
|
||||
{
|
||||
if(!ai->nullkiller->heroManager->canRecruitHero(startupTown))
|
||||
if(!ai->heroManager->canRecruitHero(startupTown))
|
||||
return false;
|
||||
|
||||
if(!startupTown->garrisonHero && !startupTown->visitingHero)
|
||||
@ -69,7 +69,7 @@ bool needToRecruitHero(const CGTownInstance * startupTown)
|
||||
|
||||
int treasureSourcesCount = 0;
|
||||
|
||||
for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
|
||||
for(auto obj : ai->objectClusterizer->getNearbyObjects())
|
||||
{
|
||||
if((obj->ID == Obj::RESOURCE && dynamic_cast<const CGResource *>(obj)->resourceID() == EGameResID::GOLD)
|
||||
|| obj->ID == Obj::TREASURE_CHEST
|
||||
@ -91,10 +91,10 @@ bool needToRecruitHero(const CGTownInstance * startupTown)
|
||||
return cb->getHeroCount(ai->playerID, true) < basicCount + boost;
|
||||
}
|
||||
|
||||
Goals::TGoalVec StartupBehavior::decompose() const
|
||||
Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
auto towns = cb->getTownsInfo();
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
if(!towns.size())
|
||||
return tasks;
|
||||
@ -103,38 +103,38 @@ Goals::TGoalVec StartupBehavior::decompose() const
|
||||
|
||||
if(towns.size() > 1)
|
||||
{
|
||||
startupTown = *vstd::maxElementByFun(towns, [](const CGTownInstance * town) -> float
|
||||
startupTown = *vstd::maxElementByFun(towns, [ai](const CGTownInstance * town) -> float
|
||||
{
|
||||
if(town->garrisonHero)
|
||||
return ai->nullkiller->heroManager->evaluateHero(town->garrisonHero.get());
|
||||
return ai->heroManager->evaluateHero(town->garrisonHero.get());
|
||||
|
||||
auto closestHero = getNearestHero(town);
|
||||
auto closestHero = getNearestHero(ai, town);
|
||||
|
||||
if(closestHero)
|
||||
return ai->nullkiller->heroManager->evaluateHero(closestHero);
|
||||
return ai->heroManager->evaluateHero(closestHero);
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
if(!startupTown->hasBuilt(BuildingID::TAVERN)
|
||||
&& cb->canBuildStructure(startupTown, BuildingID::TAVERN) == EBuildingState::ALLOWED)
|
||||
&& ai->cb->canBuildStructure(startupTown, BuildingID::TAVERN) == EBuildingState::ALLOWED)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::BuildThis(BuildingID::TAVERN, startupTown).setpriority(100)));
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
bool canRecruitHero = needToRecruitHero(startupTown);
|
||||
auto closestHero = getNearestHero(startupTown);
|
||||
bool canRecruitHero = needToRecruitHero(ai, startupTown);
|
||||
auto closestHero = getNearestHero(ai, startupTown);
|
||||
|
||||
if(closestHero)
|
||||
{
|
||||
if(!startupTown->visitingHero)
|
||||
{
|
||||
if(ai->nullkiller->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero) > 200)
|
||||
if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero) > 200)
|
||||
{
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(startupTown->visitablePos());
|
||||
auto paths = ai->pathfinder->getPathInfo(startupTown->visitablePos());
|
||||
|
||||
if(paths.size())
|
||||
{
|
||||
@ -147,22 +147,22 @@ Goals::TGoalVec StartupBehavior::decompose() const
|
||||
else
|
||||
{
|
||||
auto visitingHero = startupTown->visitingHero.get();
|
||||
auto visitingHeroScore = ai->nullkiller->heroManager->evaluateHero(visitingHero);
|
||||
auto visitingHeroScore = ai->heroManager->evaluateHero(visitingHero);
|
||||
|
||||
if(startupTown->garrisonHero)
|
||||
{
|
||||
auto garrisonHero = startupTown->garrisonHero.get();
|
||||
auto garrisonHeroScore = ai->nullkiller->heroManager->evaluateHero(garrisonHero);
|
||||
auto garrisonHeroScore = ai->heroManager->evaluateHero(garrisonHero);
|
||||
|
||||
if(visitingHeroScore > garrisonHeroScore
|
||||
|| (ai->nullkiller->heroManager->getHeroRole(garrisonHero) == HeroRole::SCOUT && ai->nullkiller->heroManager->getHeroRole(visitingHero) == HeroRole::MAIN))
|
||||
|| (ai->heroManager->getHeroRole(garrisonHero) == HeroRole::SCOUT && ai->heroManager->getHeroRole(visitingHero) == HeroRole::MAIN))
|
||||
{
|
||||
if(canRecruitHero || ai->nullkiller->armyManager->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200)
|
||||
if(canRecruitHero || ai->armyManager->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero, HeroLockedReason::STARTUP).setpriority(100)));
|
||||
}
|
||||
}
|
||||
else if(ai->nullkiller->armyManager->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200)
|
||||
else if(ai->armyManager->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero, HeroLockedReason::STARTUP).setpriority(100)));
|
||||
}
|
||||
@ -170,7 +170,7 @@ Goals::TGoalVec StartupBehavior::decompose() const
|
||||
else if(canRecruitHero)
|
||||
{
|
||||
auto canPickTownArmy = startupTown->stacksCount() == 0
|
||||
|| ai->nullkiller->armyManager->howManyReinforcementsCanGet(startupTown->visitingHero, startupTown) > 0;
|
||||
|| ai->armyManager->howManyReinforcementsCanGet(startupTown->visitingHero, startupTown) > 0;
|
||||
|
||||
if(canPickTownArmy)
|
||||
{
|
||||
@ -189,7 +189,7 @@ Goals::TGoalVec StartupBehavior::decompose() const
|
||||
{
|
||||
for(auto town : towns)
|
||||
{
|
||||
if(!town->visitingHero && needToRecruitHero(town))
|
||||
if(!town->visitingHero && needToRecruitHero(ai, town))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(town)));
|
||||
|
||||
@ -205,7 +205,7 @@ Goals::TGoalVec StartupBehavior::decompose() const
|
||||
if(town->garrisonHero
|
||||
&& town->garrisonHero->movementPointsRemaining()
|
||||
&& !town->visitingHero
|
||||
&& ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
|
||||
&& ai->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(MIN_PRIORITY)));
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
TGoalVec decompose() const override;
|
||||
TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
bool operator==(const StartupBehavior & other) const override
|
||||
|
@ -27,26 +27,31 @@ std::string StayAtTownBehavior::toString() const
|
||||
return "StayAtTownBehavior";
|
||||
}
|
||||
|
||||
Goals::TGoalVec StayAtTownBehavior::decompose() const
|
||||
Goals::TGoalVec StayAtTownBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
auto towns = cb->getTownsInfo();
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
if(!towns.size())
|
||||
return tasks;
|
||||
|
||||
std::vector<AIPath> paths;
|
||||
|
||||
for(auto town : towns)
|
||||
{
|
||||
if(!town->hasBuilt(BuildingID::MAGES_GUILD_1))
|
||||
continue;
|
||||
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(town->visitablePos());
|
||||
ai->pathfinder->calculatePathInfo(paths, town->visitablePos());
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
if(town->visitingHero && town->visitingHero.get() != path.targetHero)
|
||||
continue;
|
||||
|
||||
if(!path.targetHero->hasSpellbook() || path.targetHero->mana >= 0.75f * path.targetHero->manaLimit())
|
||||
continue;
|
||||
|
||||
if(path.turn() == 0 && !path.getFirstBlockedAction() && path.exchangeCount <= 1)
|
||||
{
|
||||
if(path.targetHero->mana == path.targetHero->manaLimit())
|
||||
|
@ -25,7 +25,7 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
TGoalVec decompose() const override;
|
||||
TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
bool operator==(const StayAtTownBehavior & other) const override
|
||||
|
@ -81,6 +81,7 @@ set(Nullkiller_HEADERS
|
||||
Pathfinding/Rules/AIPreviousNodeRule.h
|
||||
Pathfinding/ObjectGraph.h
|
||||
AIUtility.h
|
||||
pforeach.h
|
||||
Analyzers/ArmyManager.h
|
||||
Analyzers/HeroManager.h
|
||||
Engine/Settings.h
|
||||
|
@ -26,6 +26,11 @@ namespace NKAI
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
DeepDecomposer::DeepDecomposer(const Nullkiller * ai)
|
||||
:ai(ai), depth(0)
|
||||
{
|
||||
}
|
||||
|
||||
void DeepDecomposer::reset()
|
||||
{
|
||||
decompositionCache.clear();
|
||||
@ -136,7 +141,7 @@ Goals::TSubgoal DeepDecomposer::aggregateGoals(int startDepth, TSubgoal last)
|
||||
|
||||
Goals::TSubgoal DeepDecomposer::unwrapComposition(Goals::TSubgoal goal)
|
||||
{
|
||||
return goal->goalType == Goals::COMPOSITION ? goal->decompose().back() : goal;
|
||||
return goal->goalType == Goals::COMPOSITION ? goal->decompose(ai).back() : goal;
|
||||
}
|
||||
|
||||
bool isEquivalentGoals(TSubgoal goal1, TSubgoal goal2)
|
||||
@ -156,7 +161,7 @@ bool isEquivalentGoals(TSubgoal goal1, TSubgoal goal2)
|
||||
|
||||
bool DeepDecomposer::isCompositionLoop(TSubgoal goal)
|
||||
{
|
||||
auto goalsToTest = goal->goalType == Goals::COMPOSITION ? goal->decompose() : TGoalVec{goal};
|
||||
auto goalsToTest = goal->goalType == Goals::COMPOSITION ? goal->decompose(ai) : TGoalVec{goal};
|
||||
|
||||
for(auto goalToTest : goalsToTest)
|
||||
{
|
||||
@ -206,7 +211,7 @@ TGoalVec DeepDecomposer::decomposeCached(TSubgoal goal, bool & fromCache)
|
||||
|
||||
fromCache = false;
|
||||
|
||||
return goal->decompose();
|
||||
return goal->decompose(ai);
|
||||
}
|
||||
|
||||
void DeepDecomposer::addToCache(TSubgoal goal)
|
||||
|
@ -30,8 +30,10 @@ private:
|
||||
std::vector<Goals::TGoalVec> goals;
|
||||
std::vector<TGoalHashSet> decompositionCache;
|
||||
int depth;
|
||||
const Nullkiller * ai;
|
||||
|
||||
public:
|
||||
DeepDecomposer(const Nullkiller * ai);
|
||||
void reset();
|
||||
Goals::TGoalVec decompose(Goals::TSubgoal behavior, int depthLimit);
|
||||
|
||||
|
@ -37,10 +37,11 @@ Nullkiller::Nullkiller()
|
||||
settings = std::make_unique<Settings>();
|
||||
}
|
||||
|
||||
void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
|
||||
{
|
||||
this->cb = cb;
|
||||
this->playerID = playerID;
|
||||
this->gateway = gateway;
|
||||
this->playerID = gateway->playerID;
|
||||
|
||||
baseGraph.reset();
|
||||
|
||||
@ -59,7 +60,7 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
pathfinder.reset(new AIPathfinder(cb.get(), this));
|
||||
armyManager.reset(new ArmyManager(cb.get(), this));
|
||||
heroManager.reset(new HeroManager(cb.get(), this));
|
||||
decomposer.reset(new DeepDecomposer());
|
||||
decomposer.reset(new DeepDecomposer(this));
|
||||
armyFormation.reset(new ArmyFormation(cb, this));
|
||||
}
|
||||
|
||||
@ -120,12 +121,11 @@ void Nullkiller::resetAiState()
|
||||
|
||||
lockedResources = TResources();
|
||||
scanDepth = ScanDepth::MAIN_FULL;
|
||||
playerID = ai->playerID;
|
||||
lockedHeroes.clear();
|
||||
dangerHitMap->reset();
|
||||
useHeroChain = true;
|
||||
|
||||
if(!baseGraph && ai->nullkiller->settings->isObjectGraphAllowed())
|
||||
if(!baseGraph && settings->isObjectGraphAllowed())
|
||||
{
|
||||
baseGraph = std::make_unique<ObjectGraph>();
|
||||
baseGraph->updateGraph(this);
|
||||
@ -188,7 +188,10 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
||||
|
||||
if(settings->isObjectGraphAllowed())
|
||||
{
|
||||
pathfinder->updateGraphs(activeHeroes);
|
||||
pathfinder->updateGraphs(
|
||||
activeHeroes,
|
||||
scanDepth == ScanDepth::SMALL ? 255 : 10,
|
||||
scanDepth == ScanDepth::ALL_FULL ? 255 : 3);
|
||||
}
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
@ -250,6 +253,7 @@ void Nullkiller::makeTurn()
|
||||
|
||||
for(int i = 1; i <= settings->getMaxPass(); i++)
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
updateAiState(i);
|
||||
|
||||
Goals::TTask bestTask = taskptr(Goals::Invalid());
|
||||
@ -303,8 +307,7 @@ void Nullkiller::makeTurn()
|
||||
|
||||
// TODO: better to check turn distance here instead of priority
|
||||
if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
|
||||
&& scanDepth == ScanDepth::MAIN_FULL
|
||||
&& !settings->isObjectGraphAllowed())
|
||||
&& scanDepth == ScanDepth::MAIN_FULL)
|
||||
{
|
||||
useHeroChain = false;
|
||||
scanDepth = ScanDepth::SMALL;
|
||||
@ -317,25 +320,22 @@ void Nullkiller::makeTurn()
|
||||
|
||||
if(bestTask->priority < MIN_PRIORITY)
|
||||
{
|
||||
if(!settings->isObjectGraphAllowed())
|
||||
{
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
{
|
||||
return h->movementPointsRemaining() > 100;
|
||||
});
|
||||
|
||||
if(hasMp && scanDepth != ScanDepth::ALL_FULL)
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
{
|
||||
logAi->trace(
|
||||
"Goal %s has too low priority %f so increasing scan depth to full.",
|
||||
taskDescription,
|
||||
bestTask->priority);
|
||||
return h->movementPointsRemaining() > 100;
|
||||
});
|
||||
|
||||
scanDepth = ScanDepth::ALL_FULL;
|
||||
useHeroChain = false;
|
||||
continue;
|
||||
}
|
||||
if(hasMp && scanDepth != ScanDepth::ALL_FULL)
|
||||
{
|
||||
logAi->trace(
|
||||
"Goal %s has too low priority %f so increasing scan depth to full.",
|
||||
taskDescription,
|
||||
bestTask->priority);
|
||||
|
||||
scanDepth = ScanDepth::ALL_FULL;
|
||||
useHeroChain = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", taskDescription);
|
||||
@ -343,6 +343,8 @@ void Nullkiller::makeTurn()
|
||||
return;
|
||||
}
|
||||
|
||||
logAi->debug("Decission madel in %ld", timeElapsed(start));
|
||||
|
||||
executeTask(bestTask);
|
||||
|
||||
if(i == settings->getMaxPass())
|
||||
@ -362,7 +364,7 @@ void Nullkiller::executeTask(Goals::TTask task)
|
||||
|
||||
try
|
||||
{
|
||||
task->accept(ai);
|
||||
task->accept(gateway);
|
||||
logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
|
||||
}
|
||||
catch(goalFulfilledException &)
|
||||
|
@ -57,6 +57,7 @@ private:
|
||||
ScanDepth scanDepth;
|
||||
TResources lockedResources;
|
||||
bool useHeroChain;
|
||||
AIGateway * gateway;
|
||||
|
||||
public:
|
||||
static std::unique_ptr<ObjectGraph> baseGraph;
|
||||
@ -79,7 +80,7 @@ public:
|
||||
std::mutex aiStateMutex;
|
||||
|
||||
Nullkiller();
|
||||
void init(std::shared_ptr<CCallback> cb, PlayerColor playerID);
|
||||
void init(std::shared_ptr<CCallback> cb, AIGateway * gateway);
|
||||
void makeTurn();
|
||||
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
|
||||
bool isHeroLocked(const CGHeroInstance * hero) const;
|
||||
|
@ -709,7 +709,7 @@ public:
|
||||
|
||||
Goals::HeroExchange & heroExchange = dynamic_cast<Goals::HeroExchange &>(*task);
|
||||
|
||||
uint64_t armyStrength = heroExchange.getReinforcementArmyStrength();
|
||||
uint64_t armyStrength = heroExchange.getReinforcementArmyStrength(evaluationContext.evaluator.ai);
|
||||
|
||||
evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero.get()->getArmyStrength());
|
||||
evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero.get());
|
||||
@ -1064,7 +1064,7 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal
|
||||
|
||||
if(goal->goalType == Goals::COMPOSITION)
|
||||
{
|
||||
parts = goal->decompose();
|
||||
parts = goal->decompose(ai);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -21,6 +21,7 @@ namespace NKAI
|
||||
struct HeroPtr;
|
||||
class AIGateway;
|
||||
class FuzzyHelper;
|
||||
class Nullkiller;
|
||||
|
||||
namespace Goals
|
||||
{
|
||||
@ -128,7 +129,7 @@ namespace Goals
|
||||
return const_cast<AbstractGoal *>(this);
|
||||
}
|
||||
|
||||
virtual TGoalVec decompose() const
|
||||
virtual TGoalVec decompose(const Nullkiller * ai) const
|
||||
{
|
||||
return TGoalVec();
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
namespace Nullkiller
|
||||
{
|
||||
|
||||
/*
|
||||
* Build.cpp, part of VCMI engine
|
||||
*
|
||||
@ -23,6 +20,8 @@ namespace Nullkiller
|
||||
#include "../../../lib/CPathfinder.h"
|
||||
#include "../../../lib/StringConstants.h"
|
||||
|
||||
namespace Nullkiller
|
||||
{
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
||||
|
@ -1,6 +1,3 @@
|
||||
namespace Nullkiller
|
||||
{
|
||||
|
||||
/*
|
||||
* Build.h, part of VCMI engine
|
||||
*
|
||||
@ -14,6 +11,9 @@ namespace Nullkiller
|
||||
|
||||
#include "CGoal.h"
|
||||
|
||||
namespace Nullkiller
|
||||
{
|
||||
|
||||
struct HeroPtr;
|
||||
class AIGateway;
|
||||
class FuzzyHelper;
|
||||
|
@ -54,9 +54,9 @@ namespace Goals
|
||||
|
||||
virtual bool operator==(const T & other) const = 0;
|
||||
|
||||
TGoalVec decompose() const override
|
||||
TGoalVec decompose(const Nullkiller * ai) const override
|
||||
{
|
||||
TSubgoal single = decomposeSingle();
|
||||
TSubgoal single = decomposeSingle(ai);
|
||||
|
||||
if(!single || single->invalid())
|
||||
return {};
|
||||
@ -65,7 +65,7 @@ namespace Goals
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual TSubgoal decomposeSingle() const
|
||||
virtual TSubgoal decomposeSingle(const Nullkiller * ai) const
|
||||
{
|
||||
return TSubgoal();
|
||||
}
|
||||
|
@ -35,9 +35,9 @@ std::string CaptureObject::toString() const
|
||||
return "Capture " + name + " at " + tile.toString();
|
||||
}
|
||||
|
||||
TGoalVec CaptureObject::decompose() const
|
||||
TGoalVec CaptureObject::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
return CaptureObjectsBehavior(cb->getObj(ObjectInstanceID(objid))).decompose();
|
||||
return CaptureObjectsBehavior(ai->cb->getObj(ObjectInstanceID(objid))).decompose(ai);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ namespace Goals
|
||||
}
|
||||
|
||||
bool operator==(const CaptureObject & other) const override;
|
||||
Goals::TGoalVec decompose() const override;
|
||||
Goals::TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
bool hasHash() const override { return true; }
|
||||
uint64_t getHash() const override;
|
||||
|
@ -29,36 +29,36 @@ std::string CompleteQuest::toString() const
|
||||
return "Complete quest " + questToString();
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::decompose() const
|
||||
TGoalVec CompleteQuest::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
if(isKeyMaster(q))
|
||||
{
|
||||
return missionKeymaster();
|
||||
return missionKeymaster(ai);
|
||||
}
|
||||
|
||||
logAi->debug("Trying to realize quest: %s", questToString());
|
||||
|
||||
if(!q.quest->mission.artifacts.empty())
|
||||
return missionArt();
|
||||
return missionArt(ai);
|
||||
|
||||
if(!q.quest->mission.heroes.empty())
|
||||
return missionHero();
|
||||
return missionHero(ai);
|
||||
|
||||
if(!q.quest->mission.creatures.empty())
|
||||
return missionArmy();
|
||||
return missionArmy(ai);
|
||||
|
||||
if(q.quest->mission.resources.nonZero())
|
||||
return missionResources();
|
||||
return missionResources(ai);
|
||||
|
||||
if(q.quest->killTarget != ObjectInstanceID::NONE)
|
||||
return missionDestroyObj();
|
||||
return missionDestroyObj(ai);
|
||||
|
||||
for(auto & s : q.quest->mission.primary)
|
||||
if(s)
|
||||
return missionIncreasePrimaryStat();
|
||||
return missionIncreasePrimaryStat(ai);
|
||||
|
||||
if(q.quest->mission.heroLevel > 0)
|
||||
return missionLevel();
|
||||
return missionLevel(ai);
|
||||
|
||||
return TGoalVec();
|
||||
}
|
||||
@ -103,21 +103,21 @@ std::string CompleteQuest::questToString() const
|
||||
return ms.toString();
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::tryCompleteQuest() const
|
||||
TGoalVec CompleteQuest::tryCompleteQuest(const Nullkiller * ai) const
|
||||
{
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(q.obj->visitablePos());
|
||||
auto paths = ai->pathfinder->getPathInfo(q.obj->visitablePos());
|
||||
|
||||
vstd::erase_if(paths, [&](const AIPath & path) -> bool
|
||||
{
|
||||
return !q.quest->checkQuest(path.targetHero);
|
||||
});
|
||||
|
||||
return CaptureObjectsBehavior::getVisitGoals(paths, q.obj);
|
||||
return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.obj);
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::missionArt() const
|
||||
TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
|
||||
{
|
||||
TGoalVec solutions = tryCompleteQuest();
|
||||
TGoalVec solutions = tryCompleteQuest(ai);
|
||||
|
||||
if(!solutions.empty())
|
||||
return solutions;
|
||||
@ -132,9 +132,9 @@ TGoalVec CompleteQuest::missionArt() const
|
||||
return solutions;
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::missionHero() const
|
||||
TGoalVec CompleteQuest::missionHero(const Nullkiller * ai) const
|
||||
{
|
||||
TGoalVec solutions = tryCompleteQuest();
|
||||
TGoalVec solutions = tryCompleteQuest(ai);
|
||||
|
||||
if(solutions.empty())
|
||||
{
|
||||
@ -145,43 +145,43 @@ TGoalVec CompleteQuest::missionHero() const
|
||||
return solutions;
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::missionArmy() const
|
||||
TGoalVec CompleteQuest::missionArmy(const Nullkiller * ai) const
|
||||
{
|
||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(q.obj->visitablePos());
|
||||
auto paths = ai->pathfinder->getPathInfo(q.obj->visitablePos());
|
||||
|
||||
vstd::erase_if(paths, [&](const AIPath & path) -> bool
|
||||
{
|
||||
return !CQuest::checkMissionArmy(q.quest, path.heroArmy);
|
||||
});
|
||||
|
||||
return CaptureObjectsBehavior::getVisitGoals(paths, q.obj);
|
||||
return CaptureObjectsBehavior::getVisitGoals(paths, ai, q.obj);
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
|
||||
TGoalVec CompleteQuest::missionIncreasePrimaryStat(const Nullkiller * ai) const
|
||||
{
|
||||
return tryCompleteQuest();
|
||||
return tryCompleteQuest(ai);
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::missionLevel() const
|
||||
TGoalVec CompleteQuest::missionLevel(const Nullkiller * ai) const
|
||||
{
|
||||
return tryCompleteQuest();
|
||||
return tryCompleteQuest(ai);
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::missionKeymaster() const
|
||||
TGoalVec CompleteQuest::missionKeymaster(const Nullkiller * ai) const
|
||||
{
|
||||
if(isObjectPassable(q.obj))
|
||||
if(isObjectPassable(ai, q.obj))
|
||||
{
|
||||
return CaptureObjectsBehavior(q.obj).decompose();
|
||||
return CaptureObjectsBehavior(q.obj).decompose(ai);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.obj->subID).decompose();
|
||||
return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.obj->subID).decompose(ai);
|
||||
}
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::missionResources() const
|
||||
TGoalVec CompleteQuest::missionResources(const Nullkiller * ai) const
|
||||
{
|
||||
TGoalVec solutions = tryCompleteQuest();
|
||||
TGoalVec solutions = tryCompleteQuest(ai);
|
||||
|
||||
/*auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities?
|
||||
|
||||
@ -208,14 +208,14 @@ TGoalVec CompleteQuest::missionResources() const
|
||||
return solutions;
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::missionDestroyObj() const
|
||||
TGoalVec CompleteQuest::missionDestroyObj(const Nullkiller * ai) const
|
||||
{
|
||||
auto obj = cb->getObj(q.quest->killTarget);
|
||||
auto obj = ai->cb->getObj(q.quest->killTarget);
|
||||
|
||||
if(!obj)
|
||||
return CaptureObjectsBehavior(q.obj).decompose();
|
||||
return CaptureObjectsBehavior(q.obj).decompose(ai);
|
||||
|
||||
auto relations = cb->getPlayerRelations(ai->playerID, obj->tempOwner);
|
||||
auto relations = ai->cb->getPlayerRelations(ai->playerID, obj->tempOwner);
|
||||
|
||||
//if(relations == PlayerRelations::SAME_PLAYER)
|
||||
//{
|
||||
@ -226,7 +226,7 @@ TGoalVec CompleteQuest::missionDestroyObj() const
|
||||
//else
|
||||
if(relations == PlayerRelations::ENEMIES)
|
||||
{
|
||||
return CaptureObjectsBehavior(obj).decompose();
|
||||
return CaptureObjectsBehavior(obj).decompose(ai);
|
||||
}
|
||||
|
||||
return TGoalVec();
|
||||
|
@ -29,7 +29,7 @@ namespace Goals
|
||||
{
|
||||
}
|
||||
|
||||
Goals::TGoalVec decompose() const override;
|
||||
Goals::TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
std::string toString() const override;
|
||||
bool hasHash() const override { return true; }
|
||||
uint64_t getHash() const override;
|
||||
@ -37,15 +37,15 @@ namespace Goals
|
||||
bool operator==(const CompleteQuest & other) const override;
|
||||
|
||||
private:
|
||||
TGoalVec tryCompleteQuest() const;
|
||||
TGoalVec missionArt() const;
|
||||
TGoalVec missionHero() const;
|
||||
TGoalVec missionArmy() const;
|
||||
TGoalVec missionResources() const;
|
||||
TGoalVec missionDestroyObj() const;
|
||||
TGoalVec missionIncreasePrimaryStat() const;
|
||||
TGoalVec missionLevel() const;
|
||||
TGoalVec missionKeymaster() const;
|
||||
TGoalVec tryCompleteQuest(const Nullkiller * ai) const;
|
||||
TGoalVec missionArt(const Nullkiller * ai) const;
|
||||
TGoalVec missionHero(const Nullkiller * ai) const;
|
||||
TGoalVec missionArmy(const Nullkiller * ai) const;
|
||||
TGoalVec missionResources(const Nullkiller * ai) const;
|
||||
TGoalVec missionDestroyObj(const Nullkiller * ai) const;
|
||||
TGoalVec missionIncreasePrimaryStat(const Nullkiller * ai) const;
|
||||
TGoalVec missionLevel(const Nullkiller * ai) const;
|
||||
TGoalVec missionKeymaster(const Nullkiller * ai) const;
|
||||
std::string questToString() const;
|
||||
};
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ void Composition::accept(AIGateway * ai)
|
||||
}
|
||||
}
|
||||
|
||||
TGoalVec Composition::decompose() const
|
||||
TGoalVec Composition::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
TGoalVec result;
|
||||
|
||||
|
@ -32,7 +32,7 @@ namespace Goals
|
||||
Composition & addNext(const AbstractGoal & goal);
|
||||
Composition & addNext(TSubgoal goal);
|
||||
Composition & addNextSequence(const TGoalVec & taskSequence);
|
||||
TGoalVec decompose() const override;
|
||||
TGoalVec decompose(const Nullkiller * ai) const override;
|
||||
bool isElementar() const override;
|
||||
int getHeroExchangeCount() const override;
|
||||
};
|
||||
|
@ -67,40 +67,40 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
|
||||
for(int i = chainPath.nodes.size() - 1; i >= 0; i--)
|
||||
{
|
||||
auto & node = chainPath.nodes[i];
|
||||
auto * node = &chainPath.nodes[i];
|
||||
|
||||
const CGHeroInstance * hero = node.targetHero;
|
||||
const CGHeroInstance * hero = node->targetHero;
|
||||
HeroPtr heroPtr = hero;
|
||||
|
||||
if(node.parentIndex >= i)
|
||||
if(node->parentIndex >= i)
|
||||
{
|
||||
logAi->error("Invalid parentIndex while executing node " + node.coord.toString());
|
||||
logAi->error("Invalid parentIndex while executing node " + node->coord.toString());
|
||||
}
|
||||
|
||||
if(vstd::contains(blockedIndexes, i))
|
||||
{
|
||||
blockedIndexes.insert(node.parentIndex);
|
||||
blockedIndexes.insert(node->parentIndex);
|
||||
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->getNameTranslated(), node.coord.toString());
|
||||
logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->getNameTranslated(), node->coord.toString());
|
||||
|
||||
try
|
||||
{
|
||||
if(hero->movementPointsRemaining() > 0)
|
||||
{
|
||||
ai->nullkiller->setActive(hero, node.coord);
|
||||
ai->nullkiller->setActive(hero, node->coord);
|
||||
|
||||
if(node.specialAction)
|
||||
if(node->specialAction)
|
||||
{
|
||||
if(node.actionIsBlocked)
|
||||
if(node->actionIsBlocked)
|
||||
{
|
||||
throw cannotFulfillGoalException("Path is nondeterministic.");
|
||||
}
|
||||
|
||||
node.specialAction->execute(hero);
|
||||
node->specialAction->execute(ai, hero);
|
||||
|
||||
if(!heroPtr.validAndSet())
|
||||
{
|
||||
@ -109,10 +109,34 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(node.turns == 0 && node.coord != hero->visitablePos())
|
||||
else if(i > 0 && ai->nullkiller->settings->isObjectGraphAllowed())
|
||||
{
|
||||
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node.coord);
|
||||
auto chainMask = i < chainPath.nodes.size() - 1 ? chainPath.nodes[i + 1].chainMask : node->chainMask;
|
||||
|
||||
for(auto j = i - 1; j >= 0; j--)
|
||||
{
|
||||
auto & nextNode = chainPath.nodes[j];
|
||||
|
||||
if(nextNode.specialAction || nextNode.chainMask != chainMask)
|
||||
break;
|
||||
|
||||
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(nextNode.coord);
|
||||
|
||||
if(!targetNode->reachable()
|
||||
|| targetNode->getCost() > nextNode.cost)
|
||||
break;
|
||||
|
||||
i = j;
|
||||
node = &nextNode;
|
||||
|
||||
if(targetNode->action == EPathNodeAction::BATTLE || targetNode->action == EPathNodeAction::TELEPORT_BATTLE)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(node->turns == 0 && node->coord != hero->visitablePos())
|
||||
{
|
||||
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node->coord);
|
||||
|
||||
if(targetNode->accessible == EPathAccessibility::NOT_SET
|
||||
|| targetNode->accessible == EPathAccessibility::BLOCKED
|
||||
@ -122,7 +146,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
logAi->error(
|
||||
"Unable to complete chain. Expected hero %s to arrive to %s in 0 turns but he cannot do this",
|
||||
hero->getNameTranslated(),
|
||||
node.coord.toString());
|
||||
node->coord.toString());
|
||||
|
||||
return;
|
||||
}
|
||||
@ -132,7 +156,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(moveHeroToTile(hero, node.coord))
|
||||
if(moveHeroToTile(ai, hero, node->coord))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -149,11 +173,11 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
if(hero->movementPointsRemaining() > 0)
|
||||
{
|
||||
CGPath path;
|
||||
bool isOk = cb->getPathsInfo(hero)->getPath(path, node.coord);
|
||||
bool isOk = cb->getPathsInfo(hero)->getPath(path, node->coord);
|
||||
|
||||
if(isOk && path.nodes.back().turns > 0)
|
||||
{
|
||||
logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movementPointsRemaining(), node.coord.toString());
|
||||
logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movementPointsRemaining(), node->coord.toString());
|
||||
|
||||
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
|
||||
return;
|
||||
@ -165,15 +189,15 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
}
|
||||
}
|
||||
|
||||
if(node.coord == hero->visitablePos())
|
||||
if(node->coord == hero->visitablePos())
|
||||
continue;
|
||||
|
||||
if(node.turns == 0)
|
||||
if(node->turns == 0)
|
||||
{
|
||||
logAi->error(
|
||||
"Unable to complete chain. Expected hero %s to arive to %s but he is at %s",
|
||||
hero->getNameTranslated(),
|
||||
node.coord.toString(),
|
||||
node->coord.toString(),
|
||||
hero->visitablePos().toString());
|
||||
|
||||
return;
|
||||
@ -181,13 +205,13 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
|
||||
// no exception means we were not able to reach the tile
|
||||
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
|
||||
blockedIndexes.insert(node.parentIndex);
|
||||
blockedIndexes.insert(node->parentIndex);
|
||||
}
|
||||
catch(const goalFulfilledException &)
|
||||
{
|
||||
if(!heroPtr.validAndSet())
|
||||
{
|
||||
logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.name, node.coord.toString());
|
||||
logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.name, node->coord.toString());
|
||||
|
||||
return;
|
||||
}
|
||||
@ -200,9 +224,9 @@ std::string ExecuteHeroChain::toString() const
|
||||
return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->getNameTranslated();
|
||||
}
|
||||
|
||||
bool ExecuteHeroChain::moveHeroToTile(const CGHeroInstance * hero, const int3 & tile)
|
||||
bool ExecuteHeroChain::moveHeroToTile(AIGateway * ai, const CGHeroInstance * hero, const int3 & tile)
|
||||
{
|
||||
if(tile == hero->visitablePos() && cb->getVisitableObjs(hero->visitablePos()).size() < 2)
|
||||
if(tile == hero->visitablePos() && ai->myCb->getVisitableObjs(hero->visitablePos()).size() < 2)
|
||||
{
|
||||
logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", hero->getNameTranslated(), tile.toString());
|
||||
|
||||
|
@ -27,7 +27,6 @@ namespace Goals
|
||||
|
||||
ExecuteHeroChain(const AIPath & path, const CGObjectInstance * obj = nullptr);
|
||||
|
||||
|
||||
void accept(AIGateway * ai) override;
|
||||
std::string toString() const override;
|
||||
bool operator==(const ExecuteHeroChain & other) const override;
|
||||
@ -36,7 +35,7 @@ namespace Goals
|
||||
int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
|
||||
|
||||
private:
|
||||
bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile);
|
||||
bool moveHeroToTile(AIGateway * ai, const CGHeroInstance * hero, const int3 & tile);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
namespace Nullkiller
|
||||
{
|
||||
|
||||
/*
|
||||
* GatherArmy.cpp, part of VCMI engine
|
||||
*
|
||||
@ -22,6 +19,8 @@ namespace Nullkiller
|
||||
#include "../../../lib/CPathfinder.h"
|
||||
#include "../../../lib/StringConstants.h"
|
||||
|
||||
namespace Nullkiller
|
||||
{
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<AIGateway> ai;
|
||||
|
@ -1,6 +1,3 @@
|
||||
namespace Nullkiller
|
||||
{
|
||||
|
||||
/*
|
||||
* GatherArmy.h, part of VCMI engine
|
||||
*
|
||||
@ -14,6 +11,9 @@ namespace Nullkiller
|
||||
|
||||
#include "CGoal.h"
|
||||
|
||||
namespace Nullkiller
|
||||
{
|
||||
|
||||
struct HeroPtr;
|
||||
class AIGateway;
|
||||
class FuzzyHelper;
|
||||
|
@ -27,7 +27,7 @@ namespace Goals
|
||||
{
|
||||
priority = -1;
|
||||
}
|
||||
TGoalVec decompose() const override
|
||||
TGoalVec decompose(const Nullkiller * ai) const override
|
||||
{
|
||||
return TGoalVec();
|
||||
}
|
||||
|
@ -29,9 +29,9 @@ std::string HeroExchange::toString() const
|
||||
return "Hero exchange for " +hero.get()->getObjectName() + " by " + exchangePath.toString();
|
||||
}
|
||||
|
||||
uint64_t HeroExchange::getReinforcementArmyStrength() const
|
||||
uint64_t HeroExchange::getReinforcementArmyStrength(const Nullkiller * ai) const
|
||||
{
|
||||
uint64_t armyValue = ai->nullkiller->armyManager->howManyReinforcementsCanGet(hero.get(), exchangePath.heroArmy);
|
||||
uint64_t armyValue = ai->armyManager->howManyReinforcementsCanGet(hero.get(), exchangePath.heroArmy);
|
||||
|
||||
return armyValue;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ namespace Goals
|
||||
bool operator==(const HeroExchange & other) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
uint64_t getReinforcementArmyStrength() const;
|
||||
uint64_t getReinforcementArmyStrength(const Nullkiller * ai) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,8 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 4>> AISharedStorage::shared;
|
||||
uint64_t AISharedStorage::version = 0;
|
||||
boost::mutex AISharedStorage::locker;
|
||||
std::set<int3> commitedTiles;
|
||||
std::set<int3> commitedTilesInitial;
|
||||
@ -40,11 +41,24 @@ const bool DO_NOT_SAVE_TO_COMMITED_TILES = false;
|
||||
AISharedStorage::AISharedStorage(int3 sizes)
|
||||
{
|
||||
if(!shared){
|
||||
shared.reset(new boost::multi_array<AIPathNode, 5>(
|
||||
boost::extents[EPathfindingLayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
|
||||
}
|
||||
shared.reset(new boost::multi_array<AIPathNode, 4>(
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
|
||||
|
||||
nodes = shared;
|
||||
nodes = shared;
|
||||
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
for(auto i = 0; i < AIPathfinding::NUM_CHAINS; i++)
|
||||
{
|
||||
auto & node = get(pos)[i];
|
||||
|
||||
node.version = -1;
|
||||
node.coord = pos;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
nodes = shared;
|
||||
}
|
||||
|
||||
AISharedStorage::~AISharedStorage()
|
||||
@ -80,6 +94,9 @@ void AIPathNode::addSpecialAction(std::shared_ptr<const SpecialAction> action)
|
||||
AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
|
||||
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
|
||||
{
|
||||
accesibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
|
||||
boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
|
||||
|
||||
dangerEvaluator.reset(new FuzzyHelper(ai));
|
||||
}
|
||||
|
||||
@ -90,6 +107,8 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
||||
if(heroChainPass)
|
||||
return;
|
||||
|
||||
AISharedStorage::version++;
|
||||
|
||||
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
|
||||
const PlayerColor fowPlayer = ai->playerID;
|
||||
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
|
||||
@ -97,7 +116,7 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
||||
|
||||
//Each thread gets different x, but an array of y located next to each other in memory
|
||||
|
||||
parallel_for(blocked_range<size_t>(0, sizes.x), [&](const blocked_range<size_t>& r)
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, sizes.x), [&](const tbb::blocked_range<size_t>& r)
|
||||
{
|
||||
int3 pos;
|
||||
|
||||
@ -152,9 +171,9 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
{
|
||||
int bucketIndex = ((uintptr_t)actor) % AIPathfinding::BUCKET_COUNT;
|
||||
int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE;
|
||||
auto chains = nodes.get(pos, layer);
|
||||
auto chains = nodes.get(pos);
|
||||
|
||||
if(chains[0].blocked())
|
||||
if(blocked(pos, layer))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -163,15 +182,17 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
{
|
||||
AIPathNode & node = chains[i + bucketOffset];
|
||||
|
||||
if(node.actor == actor)
|
||||
if(node.version != AISharedStorage::version)
|
||||
{
|
||||
node.reset(layer, getAccessibility(pos, layer));
|
||||
node.version = AISharedStorage::version;
|
||||
node.actor = actor;
|
||||
|
||||
return &node;
|
||||
}
|
||||
|
||||
if(!node.actor)
|
||||
if(node.actor == actor && node.layer == layer)
|
||||
{
|
||||
node.actor = actor;
|
||||
|
||||
return &node;
|
||||
}
|
||||
}
|
||||
@ -226,21 +247,6 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
||||
return initialNodes;
|
||||
}
|
||||
|
||||
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPathAccessibility accessibility)
|
||||
{
|
||||
for(AIPathNode & heroNode : nodes.get(coord, layer))
|
||||
{
|
||||
heroNode.actor = nullptr;
|
||||
heroNode.danger = 0;
|
||||
heroNode.manaCost = 0;
|
||||
heroNode.specialAction.reset();
|
||||
heroNode.armyLoss = 0;
|
||||
heroNode.chainOther = nullptr;
|
||||
heroNode.dayFlags = DayFlags::NONE;
|
||||
heroNode.update(coord, layer, accessibility);
|
||||
}
|
||||
}
|
||||
|
||||
void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source)
|
||||
{
|
||||
const AIPathNode * srcNode = getAINode(source.node);
|
||||
@ -306,30 +312,31 @@ void AINodeStorage::commit(
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
void AINodeStorage::calculateNeighbours(
|
||||
std::vector<CGPathNode *> & result,
|
||||
const PathNodeInfo & source,
|
||||
EPathfindingLayer layer,
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
const CPathfinderHelper * pathfinderHelper)
|
||||
{
|
||||
std::vector<CGPathNode *> neighbours;
|
||||
neighbours.reserve(16);
|
||||
std::vector<int3> accessibleNeighbourTiles;
|
||||
|
||||
result.clear();
|
||||
accessibleNeighbourTiles.reserve(8);
|
||||
|
||||
pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
|
||||
|
||||
const AIPathNode * srcNode = getAINode(source.node);
|
||||
auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
|
||||
|
||||
for(auto & neighbour : accessibleNeighbourTiles)
|
||||
{
|
||||
for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1))
|
||||
{
|
||||
auto nextNode = getOrCreateNode(neighbour, i, srcNode->actor);
|
||||
auto nextNode = getOrCreateNode(neighbour, layer, srcNode->actor);
|
||||
|
||||
if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
|
||||
continue;
|
||||
if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
|
||||
continue;
|
||||
|
||||
neighbours.push_back(nextNode.value());
|
||||
}
|
||||
result.push_back(nextNode.value());
|
||||
}
|
||||
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
constexpr std::array phisycalLayers = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
|
||||
@ -346,19 +353,16 @@ bool AINodeStorage::increaseHeroChainTurnLimit()
|
||||
{
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
auto chains = nodes.get(pos, layer);
|
||||
|
||||
if(!chains[0].blocked())
|
||||
{
|
||||
for(AIPathNode & node : chains)
|
||||
iterateValidNodesUntil(pos, layer, [&](AIPathNode & node)
|
||||
{
|
||||
if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
|
||||
{
|
||||
commitedTiles.insert(pos);
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -374,22 +378,17 @@ bool AINodeStorage::calculateHeroChainFinal()
|
||||
{
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
auto chains = nodes.get(pos, layer);
|
||||
|
||||
if(!chains[0].blocked())
|
||||
{
|
||||
for(AIPathNode & node : chains)
|
||||
iterateValidNodes(pos, layer, [&](AIPathNode & node)
|
||||
{
|
||||
if(node.turns > heroChainTurn
|
||||
&& !node.locked
|
||||
&& node.action != EPathNodeAction::UNKNOWN
|
||||
&& node.actor->actorExchangeCount > 1
|
||||
&& !hasBetterChain(&node, &node, chains))
|
||||
&& !hasBetterChain(&node, node))
|
||||
{
|
||||
heroChain.push_back(&node);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -413,7 +412,6 @@ struct DelayedWork
|
||||
class HeroChainCalculationTask
|
||||
{
|
||||
private:
|
||||
AISharedStorage & nodes;
|
||||
AINodeStorage & storage;
|
||||
std::vector<AIPathNode *> existingChains;
|
||||
std::vector<ExchangeCandidate> newChains;
|
||||
@ -425,14 +423,14 @@ private:
|
||||
|
||||
public:
|
||||
HeroChainCalculationTask(
|
||||
AINodeStorage & storage, AISharedStorage & nodes, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
|
||||
:existingChains(), newChains(), delayedWork(), nodes(nodes), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
|
||||
AINodeStorage & storage, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
|
||||
:existingChains(), newChains(), delayedWork(), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
|
||||
{
|
||||
existingChains.reserve(AIPathfinding::NUM_CHAINS);
|
||||
newChains.reserve(AIPathfinding::NUM_CHAINS);
|
||||
}
|
||||
|
||||
void execute(const blocked_range<size_t>& r)
|
||||
void execute(const tbb::blocked_range<size_t>& r)
|
||||
{
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 randomEngine(randomDevice());
|
||||
@ -443,21 +441,19 @@ public:
|
||||
|
||||
for(auto layer : phisycalLayers)
|
||||
{
|
||||
auto chains = nodes.get(pos, layer);
|
||||
existingChains.clear();
|
||||
|
||||
// fast cut inactive nodes
|
||||
if(chains[0].blocked())
|
||||
storage.iterateValidNodes(pos, layer, [this](AIPathNode & node)
|
||||
{
|
||||
if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
|
||||
existingChains.push_back(&node);
|
||||
});
|
||||
|
||||
if(existingChains.empty())
|
||||
continue;
|
||||
|
||||
existingChains.clear();
|
||||
newChains.clear();
|
||||
|
||||
for(AIPathNode & node : chains)
|
||||
{
|
||||
if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
|
||||
existingChains.push_back(&node);
|
||||
}
|
||||
|
||||
std::shuffle(existingChains.begin(), existingChains.end(), randomEngine);
|
||||
|
||||
for(AIPathNode * node : existingChains)
|
||||
@ -530,10 +526,10 @@ bool AINodeStorage::calculateHeroChain()
|
||||
|
||||
std::shuffle(data.begin(), data.end(), randomEngine);
|
||||
|
||||
parallel_for(blocked_range<size_t>(0, data.size()), [&](const blocked_range<size_t>& r)
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, data.size()), [&](const tbb::blocked_range<size_t>& r)
|
||||
{
|
||||
//auto r = blocked_range<size_t>(0, data.size());
|
||||
HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn);
|
||||
HeroChainCalculationTask task(*this, data, chainMask, heroChainTurn);
|
||||
|
||||
task.execute(r);
|
||||
|
||||
@ -546,8 +542,8 @@ bool AINodeStorage::calculateHeroChain()
|
||||
}
|
||||
else
|
||||
{
|
||||
auto r = blocked_range<size_t>(0, data.size());
|
||||
HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn);
|
||||
auto r = tbb::blocked_range<size_t>(0, data.size());
|
||||
HeroChainCalculationTask task(*this, data, chainMask, heroChainTurn);
|
||||
|
||||
task.execute(r);
|
||||
task.flushResult(heroChain);
|
||||
@ -612,14 +608,20 @@ bool AINodeStorage::selectNextActor()
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t AINodeStorage::evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const
|
||||
{
|
||||
float fightingStrength = ai->heroManager->getFightingStrengthCached(hero);
|
||||
double ratio = (double)danger / (armyValue * fightingStrength);
|
||||
|
||||
return (uint64_t)(armyValue * ratio * ratio);
|
||||
}
|
||||
|
||||
void HeroChainCalculationTask::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
|
||||
{
|
||||
vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
|
||||
{
|
||||
auto pos = chainInfo.coord;
|
||||
auto chains = nodes.get(pos, EPathfindingLayer::LAND);
|
||||
auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
|
||||
|| storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
|
||||
auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, chainInfo)
|
||||
|| storage.hasBetterChain(chainInfo.carrierParent, chainInfo, result);
|
||||
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
if(isNotEffective)
|
||||
@ -645,7 +647,7 @@ void HeroChainCalculationTask::calculateHeroChain(
|
||||
{
|
||||
for(AIPathNode * node : variants)
|
||||
{
|
||||
if(node == srcNode || !node->actor)
|
||||
if(node == srcNode || !node->actor || node->version != AISharedStorage::version)
|
||||
continue;
|
||||
|
||||
if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0)
|
||||
@ -1174,7 +1176,7 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
|
||||
|
||||
if(actorsVector.size() * initialNodes.size() > 1000)
|
||||
{
|
||||
parallel_for(blocked_range<size_t>(0, actorsVector.size()), [&](const blocked_range<size_t> & r)
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, actorsVector.size()), [&](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
for(int i = r.begin(); i != r.end(); i++)
|
||||
{
|
||||
@ -1195,95 +1197,116 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
|
||||
|
||||
bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
{
|
||||
auto pos = destination.coord;
|
||||
auto chains = nodes.get(pos, EPathfindingLayer::LAND);
|
||||
auto candidateNode = getAINode(destination.node);
|
||||
|
||||
return hasBetterChain(source.node, getAINode(destination.node), chains);
|
||||
return hasBetterChain(source.node, *candidateNode);
|
||||
}
|
||||
|
||||
bool AINodeStorage::hasBetterChain(
|
||||
const CGPathNode * source,
|
||||
const AIPathNode & candidateNode) const
|
||||
{
|
||||
return iterateValidNodesUntil(
|
||||
candidateNode.coord,
|
||||
candidateNode.layer,
|
||||
[this, &source, candidateNode](const AIPathNode & node) -> bool
|
||||
{
|
||||
return isOtherChainBetter(source, candidateNode, node);
|
||||
});
|
||||
}
|
||||
|
||||
template<class NodeRange>
|
||||
bool AINodeStorage::hasBetterChain(
|
||||
const CGPathNode * source,
|
||||
const AIPathNode * candidateNode,
|
||||
const NodeRange & chains) const
|
||||
const CGPathNode * source,
|
||||
const AIPathNode & candidateNode,
|
||||
const NodeRange & nodes) const
|
||||
{
|
||||
auto candidateActor = candidateNode->actor;
|
||||
|
||||
for(const AIPathNode & node : chains)
|
||||
for(const AIPathNode & node : nodes)
|
||||
{
|
||||
auto sameNode = node.actor == candidateNode->actor;
|
||||
if(isOtherChainBetter(source, candidateNode, node))
|
||||
return true;
|
||||
}
|
||||
|
||||
if(sameNode || node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(node.danger <= candidateNode->danger && candidateNode->actor == node.actor->battleActor)
|
||||
{
|
||||
if(node.getCost() < candidateNode->getCost())
|
||||
{
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode->coord.toString(),
|
||||
candidateNode->actor->hero->getNameTranslated(),
|
||||
candidateNode->actor->chainMask,
|
||||
candidateNode->actor->armyValue,
|
||||
node.moveRemains - candidateNode->moveRemains);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool AINodeStorage::isOtherChainBetter(
|
||||
const CGPathNode * source,
|
||||
const AIPathNode & candidateNode,
|
||||
const AIPathNode & other) const
|
||||
{
|
||||
auto sameNode = other.actor == candidateNode.actor;
|
||||
|
||||
if(candidateActor->chainMask != node.actor->chainMask && heroChainPass != EHeroChainPass::FINAL)
|
||||
continue;
|
||||
if(sameNode || other.action == EPathNodeAction::UNKNOWN || !other.actor || !other.actor->hero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nodeActor = node.actor;
|
||||
auto nodeArmyValue = nodeActor->armyValue - node.armyLoss;
|
||||
auto candidateArmyValue = candidateActor->armyValue - candidateNode->armyLoss;
|
||||
|
||||
if(nodeArmyValue > candidateArmyValue
|
||||
&& node.getCost() <= candidateNode->getCost())
|
||||
if(other.danger <= candidateNode.danger && candidateNode.actor == other.actor->battleActor)
|
||||
{
|
||||
if(other.getCost() < candidateNode.getCost())
|
||||
{
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode->coord.toString(),
|
||||
candidateNode->actor->hero->getNameTranslated(),
|
||||
candidateNode->actor->chainMask,
|
||||
candidateNode->actor->armyValue,
|
||||
node.moveRemains - candidateNode->moveRemains);
|
||||
candidateNode.coord.toString(),
|
||||
candidateNode.actor->hero->getNameTranslated(),
|
||||
candidateNode.actor->chainMask,
|
||||
candidateNode.actor->armyValue,
|
||||
other.moveRemains - candidateNode.moveRemains);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(heroChainPass == EHeroChainPass::FINAL)
|
||||
if(candidateNode.actor->chainMask != other.actor->chainMask && heroChainPass != EHeroChainPass::FINAL)
|
||||
return false;
|
||||
|
||||
auto nodeActor = other.actor;
|
||||
auto nodeArmyValue = nodeActor->armyValue - other.armyLoss;
|
||||
auto candidateArmyValue = candidateNode.actor->armyValue - candidateNode.armyLoss;
|
||||
|
||||
if(nodeArmyValue > candidateArmyValue
|
||||
&& other.getCost() <= candidateNode.getCost())
|
||||
{
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode.coord.toString(),
|
||||
candidateNode.actor->hero->getNameTranslated(),
|
||||
candidateNode.actor->chainMask,
|
||||
candidateNode.actor->armyValue,
|
||||
other.moveRemains - candidateNode.moveRemains);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
if(heroChainPass == EHeroChainPass::FINAL)
|
||||
{
|
||||
if(nodeArmyValue == candidateArmyValue
|
||||
&& nodeActor->heroFightingStrength >= candidateNode.actor->heroFightingStrength
|
||||
&& other.getCost() <= candidateNode.getCost())
|
||||
{
|
||||
if(nodeArmyValue == candidateArmyValue
|
||||
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
|
||||
&& node.getCost() <= candidateNode->getCost())
|
||||
if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateNode.actor->heroFightingStrength)
|
||||
&& vstd::isAlmostEqual(other.getCost(), candidateNode.getCost())
|
||||
&& &other < &candidateNode)
|
||||
{
|
||||
if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateActor->heroFightingStrength)
|
||||
&& vstd::isAlmostEqual(node.getCost(), candidateNode->getCost())
|
||||
&& &node < candidateNode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode->coord.toString(),
|
||||
candidateNode->actor->hero->getNameTranslated(),
|
||||
candidateNode->actor->chainMask,
|
||||
candidateNode->actor->armyValue,
|
||||
node.moveRemains - candidateNode->moveRemains);
|
||||
logAi->trace(
|
||||
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
|
||||
source->coord.toString(),
|
||||
candidateNode.coord.toString(),
|
||||
candidateNode.actor->hero->getNameTranslated(),
|
||||
candidateNode.actor->chainMask,
|
||||
candidateNode.actor->armyValue,
|
||||
other.moveRemains - candidateNode.moveRemains);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1292,12 +1315,15 @@ bool AINodeStorage::hasBetterChain(
|
||||
|
||||
bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const
|
||||
{
|
||||
auto chains = nodes.get(pos, layer);
|
||||
auto chains = nodes.get(pos);
|
||||
|
||||
for(const AIPathNode & node : chains)
|
||||
{
|
||||
if(node.action != EPathNodeAction::UNKNOWN
|
||||
&& node.actor && node.actor->hero == hero.h)
|
||||
if(node.version == AISharedStorage::version
|
||||
&& node.layer == layer
|
||||
&& node.action != EPathNodeAction::UNKNOWN
|
||||
&& node.actor
|
||||
&& node.actor->hero == hero.h)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -1306,22 +1332,23 @@ bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, con
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) const
|
||||
void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 & pos, bool isOnLand) const
|
||||
{
|
||||
std::vector<AIPath> paths;
|
||||
|
||||
paths.reserve(AIPathfinding::NUM_CHAINS / 4);
|
||||
|
||||
auto chains = nodes.get(pos, isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL);
|
||||
auto layer = isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL;
|
||||
auto chains = nodes.get(pos);
|
||||
|
||||
for(const AIPathNode & node : chains)
|
||||
{
|
||||
if(node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero)
|
||||
if(node.version != AISharedStorage::version
|
||||
|| node.layer != layer
|
||||
|| node.action == EPathNodeAction::UNKNOWN
|
||||
|| !node.actor
|
||||
|| !node.actor->hero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AIPath path;
|
||||
AIPath & path = paths.emplace_back();
|
||||
|
||||
path.targetHero = node.actor->hero;
|
||||
path.heroArmy = node.actor->creatureSet;
|
||||
@ -1332,11 +1359,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
|
||||
path.exchangeCount = node.actor->actorExchangeCount;
|
||||
|
||||
fillChainInfo(&node, path, -1);
|
||||
|
||||
paths.push_back(path);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const
|
||||
@ -1349,33 +1372,29 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
|
||||
if(node->chainOther)
|
||||
fillChainInfo(node->chainOther, path, parentIndex);
|
||||
|
||||
//if(node->actor->hero->visitablePos() != node->coord)
|
||||
AIPathNodeInfo pathNode;
|
||||
|
||||
pathNode.cost = node->getCost();
|
||||
pathNode.targetHero = node->actor->hero;
|
||||
pathNode.chainMask = node->actor->chainMask;
|
||||
pathNode.specialAction = node->specialAction;
|
||||
pathNode.turns = node->turns;
|
||||
pathNode.danger = node->danger;
|
||||
pathNode.coord = node->coord;
|
||||
pathNode.parentIndex = parentIndex;
|
||||
pathNode.actionIsBlocked = false;
|
||||
pathNode.layer = node->layer;
|
||||
|
||||
if(pathNode.specialAction)
|
||||
{
|
||||
AIPathNodeInfo pathNode;
|
||||
auto targetNode =node->theNodeBefore ? getAINode(node->theNodeBefore) : node;
|
||||
|
||||
pathNode.cost = node->getCost();
|
||||
pathNode.targetHero = node->actor->hero;
|
||||
pathNode.chainMask = node->actor->chainMask;
|
||||
pathNode.specialAction = node->specialAction;
|
||||
pathNode.turns = node->turns;
|
||||
pathNode.danger = node->danger;
|
||||
pathNode.coord = node->coord;
|
||||
pathNode.parentIndex = parentIndex;
|
||||
pathNode.actionIsBlocked = false;
|
||||
pathNode.layer = node->layer;
|
||||
|
||||
if(pathNode.specialAction)
|
||||
{
|
||||
auto targetNode =node->theNodeBefore ? getAINode(node->theNodeBefore) : node;
|
||||
|
||||
pathNode.actionIsBlocked = !pathNode.specialAction->canAct(targetNode);
|
||||
}
|
||||
|
||||
parentIndex = path.nodes.size();
|
||||
|
||||
path.nodes.push_back(pathNode);
|
||||
pathNode.actionIsBlocked = !pathNode.specialAction->canAct(ai, targetNode);
|
||||
}
|
||||
|
||||
|
||||
parentIndex = path.nodes.size();
|
||||
|
||||
path.nodes.push_back(pathNode);
|
||||
node = getAINode(node->theNodeBefore);
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ namespace NKAI
|
||||
{
|
||||
namespace AIPathfinding
|
||||
{
|
||||
const int BUCKET_COUNT = 5;
|
||||
const int BUCKET_SIZE = 3;
|
||||
const int BUCKET_COUNT = 3;
|
||||
const int BUCKET_SIZE = 7;
|
||||
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
|
||||
const int CHAIN_MAX_DEPTH = 4;
|
||||
}
|
||||
@ -49,15 +49,24 @@ struct AIPathNode : public CGPathNode
|
||||
const AIPathNode * chainOther;
|
||||
std::shared_ptr<const SpecialAction> specialAction;
|
||||
const ChainActor * actor;
|
||||
|
||||
STRONG_INLINE
|
||||
bool blocked() const
|
||||
{
|
||||
return accessible == EPathAccessibility::NOT_SET
|
||||
|| accessible == EPathAccessibility::BLOCKED;
|
||||
}
|
||||
uint64_t version;
|
||||
|
||||
void addSpecialAction(std::shared_ptr<const SpecialAction> action);
|
||||
|
||||
inline void reset(EPathfindingLayer layer, EPathAccessibility accessibility)
|
||||
{
|
||||
CGPathNode::reset();
|
||||
|
||||
actor = nullptr;
|
||||
danger = 0;
|
||||
manaCost = 0;
|
||||
specialAction.reset();
|
||||
armyLoss = 0;
|
||||
chainOther = nullptr;
|
||||
dayFlags = DayFlags::NONE;
|
||||
this->layer = layer;
|
||||
accessible = accessibility;
|
||||
}
|
||||
};
|
||||
|
||||
struct AIPathNodeInfo
|
||||
@ -133,21 +142,21 @@ enum EHeroChainPass
|
||||
|
||||
class AISharedStorage
|
||||
{
|
||||
// 1 - layer (air, water, land)
|
||||
// 2-4 - position on map[z][x][y]
|
||||
// 5 - chain (normal, battle, spellcast and combinations)
|
||||
static std::shared_ptr<boost::multi_array<AIPathNode, 5>> shared;
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 5>> nodes;
|
||||
// 1-3 - position on map[z][x][y]
|
||||
// 4 - chain + layer (normal, battle, spellcast and combinations, water, air)
|
||||
static std::shared_ptr<boost::multi_array<AIPathNode, 4>> shared;
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 4>> nodes;
|
||||
public:
|
||||
static boost::mutex locker;
|
||||
static uint64_t version;
|
||||
|
||||
AISharedStorage(int3 mapSize);
|
||||
~AISharedStorage();
|
||||
|
||||
STRONG_INLINE
|
||||
boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile, EPathfindingLayer layer) const
|
||||
boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile) const
|
||||
{
|
||||
return (*nodes)[layer][tile.z][tile.x][tile.y];
|
||||
return (*nodes)[tile.z][tile.x][tile.y];
|
||||
}
|
||||
};
|
||||
|
||||
@ -156,6 +165,8 @@ class AINodeStorage : public INodeStorage
|
||||
private:
|
||||
int3 sizes;
|
||||
|
||||
std::unique_ptr<boost::multi_array<EPathAccessibility, 4>> accesibility;
|
||||
|
||||
const CPlayerSpecificInfoCallback * cb;
|
||||
const Nullkiller * ai;
|
||||
std::unique_ptr<FuzzyHelper> dangerEvaluator;
|
||||
@ -182,8 +193,10 @@ public:
|
||||
|
||||
std::vector<CGPathNode *> getInitialNodes() override;
|
||||
|
||||
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||
virtual void calculateNeighbours(
|
||||
std::vector<CGPathNode *> & result,
|
||||
const PathNodeInfo & source,
|
||||
EPathfindingLayer layer,
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
const CPathfinderHelper * pathfinderHelper) override;
|
||||
|
||||
@ -222,7 +235,27 @@ public:
|
||||
return aiNode->actor->hero;
|
||||
}
|
||||
|
||||
inline bool blocked(const int3 & tile, EPathfindingLayer layer) const
|
||||
{
|
||||
EPathAccessibility accessible = getAccessibility(tile, layer);
|
||||
|
||||
return accessible == EPathAccessibility::NOT_SET
|
||||
|| accessible == EPathAccessibility::BLOCKED;
|
||||
}
|
||||
|
||||
bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
|
||||
bool hasBetterChain(const CGPathNode * source, const AIPathNode & candidateNode) const;
|
||||
|
||||
template<class NodeRange>
|
||||
bool hasBetterChain(
|
||||
const CGPathNode * source,
|
||||
const AIPathNode & destinationNode,
|
||||
const NodeRange & chains) const;
|
||||
|
||||
bool isOtherChainBetter(
|
||||
const CGPathNode * source,
|
||||
const AIPathNode & candidateNode,
|
||||
const AIPathNode & other) const;
|
||||
|
||||
bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
{
|
||||
@ -231,14 +264,8 @@ public:
|
||||
|
||||
bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
|
||||
|
||||
template<class NodeRange>
|
||||
bool hasBetterChain(
|
||||
const CGPathNode * source,
|
||||
const AIPathNode * destinationNode,
|
||||
const NodeRange & chains) const;
|
||||
|
||||
std::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
|
||||
std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
|
||||
void calculateChainInfo(std::vector<AIPath> & result, const int3 & pos, bool isOnLand) const;
|
||||
bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
|
||||
void setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes);
|
||||
void setScoutTurnDistanceLimit(uint8_t distanceLimit) { turnDistanceLimit[HeroRole::SCOUT] = distanceLimit; }
|
||||
@ -256,17 +283,19 @@ public:
|
||||
return dangerEvaluator->evaluateDanger(tile, hero, checkGuards);
|
||||
}
|
||||
|
||||
inline uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const
|
||||
{
|
||||
double ratio = (double)danger / (armyValue * hero->getFightingStrength());
|
||||
uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const;
|
||||
|
||||
return (uint64_t)(armyValue * ratio * ratio);
|
||||
inline EPathAccessibility getAccessibility(const int3 & tile, EPathfindingLayer layer) const
|
||||
{
|
||||
return (*this->accesibility)[tile.z][tile.x][tile.y][layer];
|
||||
}
|
||||
|
||||
STRONG_INLINE
|
||||
void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility accessibility);
|
||||
inline void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility tileAccessibility)
|
||||
{
|
||||
(*this->accesibility)[tile.z][tile.x][tile.y][layer] = tileAccessibility;
|
||||
}
|
||||
|
||||
STRONG_INLINE int getBucket(const ChainActor * actor) const
|
||||
inline int getBucket(const ChainActor * actor) const
|
||||
{
|
||||
return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
|
||||
}
|
||||
@ -274,6 +303,44 @@ public:
|
||||
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
|
||||
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
|
||||
|
||||
template<typename Fn>
|
||||
void iterateValidNodes(const int3 & pos, EPathfindingLayer layer, Fn fn)
|
||||
{
|
||||
if(blocked(pos, layer))
|
||||
return;
|
||||
|
||||
auto chains = nodes.get(pos);
|
||||
|
||||
for(AIPathNode & node : chains)
|
||||
{
|
||||
if(node.version != AISharedStorage::version || node.layer != layer)
|
||||
continue;
|
||||
|
||||
fn(node);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
bool iterateValidNodesUntil(const int3 & pos, EPathfindingLayer layer, Fn predicate) const
|
||||
{
|
||||
if(blocked(pos, layer))
|
||||
return false;
|
||||
|
||||
auto chains = nodes.get(pos);
|
||||
|
||||
for(AIPathNode & node : chains)
|
||||
{
|
||||
if(node.version != AISharedStorage::version || node.layer != layer)
|
||||
continue;
|
||||
|
||||
if(predicate(node))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
template<class TVector>
|
||||
void calculateTownPortal(
|
||||
|
@ -17,6 +17,8 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
std::map<ObjectInstanceID, std::unique_ptr<GraphPaths>> AIPathfinder::heroGraphs;
|
||||
|
||||
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai)
|
||||
:cb(cb), ai(ai)
|
||||
{
|
||||
@ -33,16 +35,31 @@ bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) con
|
||||
|| storage->isTileAccessible(hero, tile, EPathfindingLayer::SAIL);
|
||||
}
|
||||
|
||||
std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile, bool includeGraph) const
|
||||
void AIPathfinder::calculateQuickPathsWithBlocker(std::vector<AIPath> & result, const std::vector<const CGHeroInstance *> & heroes, const int3 & tile)
|
||||
{
|
||||
result.clear();
|
||||
|
||||
for(auto hero : heroes)
|
||||
{
|
||||
auto graph = heroGraphs.find(hero->id);
|
||||
|
||||
if(graph != heroGraphs.end())
|
||||
graph->second->quickAddChainInfoWithBlocker(result, tile, hero, ai);
|
||||
}
|
||||
}
|
||||
|
||||
void AIPathfinder::calculatePathInfo(std::vector<AIPath> & result, const int3 & tile, bool includeGraph) const
|
||||
{
|
||||
const TerrainTile * tileInfo = cb->getTile(tile, false);
|
||||
|
||||
result.clear();
|
||||
|
||||
if(!tileInfo)
|
||||
{
|
||||
return std::vector<AIPath>();
|
||||
return;
|
||||
}
|
||||
|
||||
auto info = storage->getChainInfo(tile, !tileInfo->isWater());
|
||||
storage->calculateChainInfo(result, tile, !tileInfo->isWater());
|
||||
|
||||
if(includeGraph)
|
||||
{
|
||||
@ -51,11 +68,9 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile, bool includeGra
|
||||
auto graph = heroGraphs.find(hero->id);
|
||||
|
||||
if(graph != heroGraphs.end())
|
||||
graph->second.addChainInfo(info, tile, hero, ai);
|
||||
graph->second->addChainInfo(result, tile, hero, ai);
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings)
|
||||
@ -125,7 +140,10 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
|
||||
logAi->trace("Recalculated paths in %ld", timeElapsed(start));
|
||||
}
|
||||
|
||||
void AIPathfinder::updateGraphs(const std::map<const CGHeroInstance *, HeroRole> & heroes)
|
||||
void AIPathfinder::updateGraphs(
|
||||
const std::map<const CGHeroInstance *, HeroRole> & heroes,
|
||||
uint8_t mainScanDepth,
|
||||
uint8_t scoutScanDepth)
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
std::vector<const CGHeroInstance *> heroesVector;
|
||||
@ -134,21 +152,29 @@ void AIPathfinder::updateGraphs(const std::map<const CGHeroInstance *, HeroRole>
|
||||
|
||||
for(auto hero : heroes)
|
||||
{
|
||||
if(heroGraphs.try_emplace(hero.first->id, GraphPaths()).second)
|
||||
if(heroGraphs.try_emplace(hero.first->id).second)
|
||||
{
|
||||
heroGraphs[hero.first->id] = std::make_unique<GraphPaths>();
|
||||
heroesVector.push_back(hero.first);
|
||||
}
|
||||
}
|
||||
|
||||
parallel_for(blocked_range<size_t>(0, heroesVector.size()), [this, &heroesVector](const blocked_range<size_t> & r)
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, heroesVector.size()), [this, &heroesVector, &heroes, mainScanDepth, scoutScanDepth](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
for(auto i = r.begin(); i != r.end(); i++)
|
||||
heroGraphs.at(heroesVector[i]->id).calculatePaths(heroesVector[i], ai);
|
||||
{
|
||||
auto role = heroes.at(heroesVector[i]);
|
||||
auto scanLimit = role == HeroRole::MAIN ? mainScanDepth : scoutScanDepth;
|
||||
|
||||
heroGraphs.at(heroesVector[i]->id)->calculatePaths(heroesVector[i], ai, scanLimit);
|
||||
}
|
||||
});
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 1)
|
||||
{
|
||||
for(auto hero : heroes)
|
||||
{
|
||||
heroGraphs[hero.first->id].dumpToLog();
|
||||
heroGraphs[hero.first->id]->dumpToLog();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,20 +40,30 @@ private:
|
||||
std::shared_ptr<AINodeStorage> storage;
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
Nullkiller * ai;
|
||||
std::map<ObjectInstanceID, GraphPaths> heroGraphs;
|
||||
static std::map<ObjectInstanceID, std::unique_ptr<GraphPaths>> heroGraphs;
|
||||
|
||||
public:
|
||||
AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai);
|
||||
std::vector<AIPath> getPathInfo(const int3 & tile, bool includeGraph = false) const;
|
||||
void calculatePathInfo(std::vector<AIPath> & paths, const int3 & tile, bool includeGraph = false) const;
|
||||
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
|
||||
void updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings);
|
||||
void updateGraphs(const std::map<const CGHeroInstance *, HeroRole> & heroes);
|
||||
void updateGraphs(const std::map<const CGHeroInstance *, HeroRole> & heroes, uint8_t mainScanDepth, uint8_t scoutScanDepth);
|
||||
void calculateQuickPathsWithBlocker(std::vector<AIPath> & result, const std::vector<const CGHeroInstance *> & heroes, const int3 & tile);
|
||||
void init();
|
||||
|
||||
std::shared_ptr<AINodeStorage>getStorage()
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
|
||||
std::vector<AIPath> getPathInfo(const int3 & tile, bool includeGraph = false)
|
||||
{
|
||||
std::vector<AIPath> result;
|
||||
|
||||
calculatePathInfo(result, tile, includeGraph);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ namespace AIPathfinding
|
||||
std::make_shared<AIMovementToDestinationRule>(nodeStorage, allowBypassObjects),
|
||||
std::make_shared<MovementCostRule>(),
|
||||
std::make_shared<AIPreviousNodeRule>(nodeStorage),
|
||||
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage, allowBypassObjects)
|
||||
std::make_shared<AIMovementAfterDestinationRule>(ai, cb, nodeStorage, allowBypassObjects)
|
||||
};
|
||||
|
||||
return rules;
|
||||
|
@ -49,14 +49,14 @@ namespace AIPathfinding
|
||||
dstNode->dayFlags = static_cast<DayFlags>(dstNode->dayFlags | flagsToAdd);
|
||||
}
|
||||
|
||||
void AdventureCastAction::execute(const CGHeroInstance * hero) const
|
||||
void AdventureCastAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
assert(hero == this->hero);
|
||||
|
||||
Goals::AdventureSpellCast(hero, spellToCast).accept(ai);
|
||||
}
|
||||
|
||||
bool AdventureCastAction::canAct(const AIPathNode * source) const
|
||||
bool AdventureCastAction::canAct(const Nullkiller * ai, const AIPathNode * source) const
|
||||
{
|
||||
assert(hero == this->hero);
|
||||
|
||||
|
@ -29,7 +29,7 @@ namespace AIPathfinding
|
||||
public:
|
||||
AdventureCastAction(SpellID spellToCast, const CGHeroInstance * hero, DayFlags flagsToAdd = DayFlags::NONE);
|
||||
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual void applyOnDestination(
|
||||
const CGHeroInstance * hero,
|
||||
@ -38,7 +38,7 @@ namespace AIPathfinding
|
||||
AIPathNode * dstMode,
|
||||
const AIPathNode * srcNode) const override;
|
||||
|
||||
bool canAct(const AIPathNode * source) const override;
|
||||
bool canAct(const Nullkiller * ai, const AIPathNode * source) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ namespace NKAI
|
||||
|
||||
namespace AIPathfinding
|
||||
{
|
||||
void BattleAction::execute(const CGHeroInstance * hero) const
|
||||
void BattleAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
ai->moveHeroToTile(targetTile, hero);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace AIPathfinding
|
||||
{
|
||||
}
|
||||
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
};
|
||||
|
@ -22,12 +22,12 @@ namespace NKAI
|
||||
|
||||
namespace AIPathfinding
|
||||
{
|
||||
void BuildBoatAction::execute(const CGHeroInstance * hero) const
|
||||
void BuildBoatAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
return Goals::BuildBoat(shipyard).accept(ai);
|
||||
}
|
||||
|
||||
Goals::TSubgoal BuildBoatAction::decompose(const CGHeroInstance * hero) const
|
||||
Goals::TSubgoal BuildBoatAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
if(cb->getPlayerRelations(ai->playerID, shipyard->getObject()->getOwner()) == PlayerRelations::ENEMIES)
|
||||
{
|
||||
@ -37,10 +37,8 @@ namespace AIPathfinding
|
||||
return Goals::sptr(Goals::Invalid());
|
||||
}
|
||||
|
||||
bool BuildBoatAction::canAct(const AIPathNode * source) const
|
||||
bool BuildBoatAction::canAct(const Nullkiller * ai, const CGHeroInstance * hero, const TResources & reservedResources) const
|
||||
{
|
||||
auto hero = source->actor->hero;
|
||||
|
||||
if(cb->getPlayerRelations(hero->tempOwner, shipyard->getObject()->getOwner()) == PlayerRelations::ENEMIES)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL > 1
|
||||
@ -53,7 +51,7 @@ namespace AIPathfinding
|
||||
|
||||
shipyard->getBoatCost(boatCost);
|
||||
|
||||
if(!cb->getResourceAmount().canAfford(source->actor->armyCost + boatCost))
|
||||
if(!cb->getResourceAmount().canAfford(reservedResources + boatCost))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL > 1
|
||||
logAi->trace("Can not build a boat. Not enough resources.");
|
||||
@ -65,6 +63,18 @@ namespace AIPathfinding
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuildBoatAction::canAct(const Nullkiller * ai, const AIPathNode * source) const
|
||||
{
|
||||
return canAct(ai, source->actor->hero, source->actor->armyCost);
|
||||
}
|
||||
|
||||
bool BuildBoatAction::canAct(const Nullkiller * ai, const AIPathNodeInfo & source) const
|
||||
{
|
||||
TResources res;
|
||||
|
||||
return canAct(ai, source.targetHero, res);
|
||||
}
|
||||
|
||||
const CGObjectInstance * BuildBoatAction::targetObject() const
|
||||
{
|
||||
return dynamic_cast<const CGObjectInstance*>(shipyard);
|
||||
@ -75,7 +85,12 @@ namespace AIPathfinding
|
||||
return sourceActor->resourceActor;
|
||||
}
|
||||
|
||||
void SummonBoatAction::execute(const CGHeroInstance * hero) const
|
||||
std::shared_ptr<SpecialAction> BuildBoatActionFactory::create(const Nullkiller * ai)
|
||||
{
|
||||
return std::make_shared<BuildBoatAction>(ai->cb.get(), dynamic_cast<const IShipyard * >(ai->cb->getObj(shipyard)));
|
||||
}
|
||||
|
||||
void SummonBoatAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai);
|
||||
}
|
||||
@ -101,7 +116,7 @@ namespace AIPathfinding
|
||||
return "Build Boat at " + shipyard->getObject()->visitablePos().toString();
|
||||
}
|
||||
|
||||
bool SummonBoatAction::canAct(const AIPathNode * source) const
|
||||
bool SummonBoatAction::canAct(const Nullkiller * ai, const AIPathNode * source) const
|
||||
{
|
||||
auto hero = source->actor->hero;
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace AIPathfinding
|
||||
class SummonBoatAction : public VirtualBoatAction
|
||||
{
|
||||
public:
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
virtual void applyOnDestination(
|
||||
const CGHeroInstance * hero,
|
||||
@ -34,7 +34,7 @@ namespace AIPathfinding
|
||||
AIPathNode * dstMode,
|
||||
const AIPathNode * srcNode) const override;
|
||||
|
||||
bool canAct(const AIPathNode * source) const override;
|
||||
bool canAct(const Nullkiller * ai, const AIPathNode * source) const override;
|
||||
|
||||
const ChainActor * getActor(const ChainActor * sourceActor) const override;
|
||||
|
||||
@ -56,11 +56,13 @@ namespace AIPathfinding
|
||||
{
|
||||
}
|
||||
|
||||
bool canAct(const AIPathNode * source) const override;
|
||||
bool canAct(const Nullkiller * ai, const AIPathNode * source) const override;
|
||||
bool canAct(const Nullkiller * ai, const AIPathNodeInfo & source) const override;
|
||||
bool canAct(const Nullkiller * ai, const CGHeroInstance * hero, const TResources & reservedResources) const;
|
||||
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
||||
Goals::TSubgoal decompose(const Nullkiller * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
const ChainActor * getActor(const ChainActor * sourceActor) const override;
|
||||
|
||||
@ -68,6 +70,19 @@ namespace AIPathfinding
|
||||
|
||||
const CGObjectInstance * targetObject() const override;
|
||||
};
|
||||
|
||||
class BuildBoatActionFactory : public ISpecialActionFactory
|
||||
{
|
||||
ObjectInstanceID shipyard;
|
||||
|
||||
public:
|
||||
BuildBoatActionFactory(ObjectInstanceID shipyard)
|
||||
:shipyard(shipyard)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<SpecialAction> create(const Nullkiller * ai) override;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace NKAI
|
||||
|
||||
namespace AIPathfinding
|
||||
{
|
||||
void BuyArmyAction::execute(const CGHeroInstance * hero) const
|
||||
void BuyArmyAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
if(!hero->visitedTown)
|
||||
{
|
||||
|
@ -21,12 +21,12 @@ namespace AIPathfinding
|
||||
private:
|
||||
|
||||
public:
|
||||
bool canAct(const AIPathNode * source) const override
|
||||
bool canAct(const Nullkiller * ai, const AIPathNode * source) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
|
||||
std::string toString() const override;
|
||||
};
|
||||
}
|
||||
|
@ -18,23 +18,33 @@ namespace NKAI
|
||||
|
||||
namespace AIPathfinding
|
||||
{
|
||||
bool QuestAction::canAct(const AIPathNode * node) const
|
||||
bool QuestAction::canAct(const Nullkiller * ai, const AIPathNode * node) const
|
||||
{
|
||||
return canAct(ai, node->actor->hero);
|
||||
}
|
||||
|
||||
bool QuestAction::canAct(const Nullkiller * ai, const AIPathNodeInfo & node) const
|
||||
{
|
||||
return canAct(ai, node.targetHero);
|
||||
}
|
||||
|
||||
bool QuestAction::canAct(const Nullkiller * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
|
||||
{
|
||||
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
|
||||
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(hero);
|
||||
}
|
||||
|
||||
return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner())
|
||||
|| questInfo.quest->checkQuest(node->actor->hero);
|
||||
return questInfo.quest->activeForPlayers.count(hero->getOwner())
|
||||
|| questInfo.quest->checkQuest(hero);
|
||||
}
|
||||
|
||||
Goals::TSubgoal QuestAction::decompose(const CGHeroInstance * hero) const
|
||||
Goals::TSubgoal QuestAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
return Goals::sptr(Goals::CompleteQuest(questInfo));
|
||||
}
|
||||
|
||||
void QuestAction::execute(const CGHeroInstance * hero) const
|
||||
void QuestAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
ai->moveHeroToTile(questInfo.obj->visitablePos(), hero);
|
||||
}
|
||||
|
@ -28,11 +28,13 @@ namespace AIPathfinding
|
||||
{
|
||||
}
|
||||
|
||||
bool canAct(const AIPathNode * node) const override;
|
||||
bool canAct(const Nullkiller * ai, const AIPathNode * node) const override;
|
||||
bool canAct(const Nullkiller * ai, const AIPathNodeInfo & node) const override;
|
||||
bool canAct(const Nullkiller * ai, const CGHeroInstance * hero) const;
|
||||
|
||||
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
||||
Goals::TSubgoal decompose(const Nullkiller * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
};
|
||||
|
@ -17,43 +17,43 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
Goals::TSubgoal SpecialAction::decompose(const CGHeroInstance * hero) const
|
||||
Goals::TSubgoal SpecialAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
return Goals::sptr(Goals::Invalid());
|
||||
}
|
||||
|
||||
void SpecialAction::execute(const CGHeroInstance * hero) const
|
||||
void SpecialAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
throw cannotFulfillGoalException("Can not execute " + toString());
|
||||
}
|
||||
|
||||
bool CompositeAction::canAct(const AIPathNode * source) const
|
||||
bool CompositeAction::canAct(const Nullkiller * ai, const AIPathNode * source) const
|
||||
{
|
||||
for(auto part : parts)
|
||||
{
|
||||
if(!part->canAct(source)) return false;
|
||||
if(!part->canAct(ai, source)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Goals::TSubgoal CompositeAction::decompose(const CGHeroInstance * hero) const
|
||||
Goals::TSubgoal CompositeAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
for(auto part : parts)
|
||||
{
|
||||
auto goal = part->decompose(hero);
|
||||
auto goal = part->decompose(ai, hero);
|
||||
|
||||
if(!goal->invalid()) return goal;
|
||||
}
|
||||
|
||||
return SpecialAction::decompose(hero);
|
||||
return SpecialAction::decompose(ai, hero);
|
||||
}
|
||||
|
||||
void CompositeAction::execute(const CGHeroInstance * hero) const
|
||||
void CompositeAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
for(auto part : parts)
|
||||
{
|
||||
part->execute(hero);
|
||||
part->execute(ai, hero);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ namespace NKAI
|
||||
{
|
||||
|
||||
struct AIPathNode;
|
||||
struct AIPathNodeInfo;
|
||||
class ChainActor;
|
||||
|
||||
class SpecialAction
|
||||
@ -29,14 +30,19 @@ class SpecialAction
|
||||
public:
|
||||
virtual ~SpecialAction() = default;
|
||||
|
||||
virtual bool canAct(const AIPathNode * source) const
|
||||
virtual bool canAct(const Nullkiller * ai, const AIPathNode * source) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const;
|
||||
virtual bool canAct(const Nullkiller * ai, const AIPathNodeInfo & source) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void execute(const CGHeroInstance * hero) const;
|
||||
virtual Goals::TSubgoal decompose(const Nullkiller * ai, const CGHeroInstance * hero) const;
|
||||
|
||||
virtual void execute(AIGateway * ai, const CGHeroInstance * hero) const;
|
||||
|
||||
virtual void applyOnDestination(
|
||||
const CGHeroInstance * hero,
|
||||
@ -70,11 +76,11 @@ private:
|
||||
public:
|
||||
CompositeAction(std::vector<std::shared_ptr<const SpecialAction>> parts) : parts(parts) {}
|
||||
|
||||
bool canAct(const AIPathNode * source) const override;
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
bool canAct(const Nullkiller * ai, const AIPathNode * source) const override;
|
||||
void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
|
||||
std::string toString() const override;
|
||||
const CGObjectInstance * targetObject() const override;
|
||||
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
||||
Goals::TSubgoal decompose(const Nullkiller * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
std::vector<std::shared_ptr<const SpecialAction>> getParts() const override
|
||||
{
|
||||
@ -89,4 +95,11 @@ public:
|
||||
const AIPathNode * srcNode) const override;
|
||||
};
|
||||
|
||||
class ISpecialActionFactory
|
||||
{
|
||||
public:
|
||||
virtual std::shared_ptr<SpecialAction> create(const Nullkiller * ai) = 0;
|
||||
virtual ~ISpecialActionFactory() = default;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace NKAI
|
||||
|
||||
using namespace AIPathfinding;
|
||||
|
||||
void TownPortalAction::execute(const CGHeroInstance * hero) const
|
||||
void TownPortalAction::execute(AIGateway * ai, const CGHeroInstance * hero) const
|
||||
{
|
||||
auto goal = Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL);
|
||||
|
||||
|
@ -29,7 +29,7 @@ namespace AIPathfinding
|
||||
{
|
||||
}
|
||||
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
void execute(AIGateway * ai, const CGHeroInstance * hero) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
};
|
||||
|
@ -15,15 +15,26 @@
|
||||
#include "../../../lib/mapping/CMap.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/logging/VisualLogger.h"
|
||||
#include "Actions/QuestAction.h"
|
||||
#include "../pforeach.h"
|
||||
#include "Actions/BoatActions.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
struct ConnectionCostInfo
|
||||
{
|
||||
float totalCost = 0;
|
||||
float avg = 0;
|
||||
int connectionsCount = 0;
|
||||
};
|
||||
|
||||
class ObjectGraphCalculator
|
||||
{
|
||||
private:
|
||||
ObjectGraph * target;
|
||||
const Nullkiller * ai;
|
||||
std::mutex syncLock;
|
||||
|
||||
std::map<const CGHeroInstance *, HeroRole> actors;
|
||||
std::map<const CGHeroInstance *, const CGObjectInstance *> actorObjectMap;
|
||||
@ -33,11 +44,15 @@ private:
|
||||
|
||||
public:
|
||||
ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
|
||||
:ai(ai), target(target)
|
||||
:ai(ai), target(target), syncLock()
|
||||
{
|
||||
}
|
||||
|
||||
void setGraphObjects()
|
||||
{
|
||||
for(auto obj : ai->memory->visitableObjs)
|
||||
{
|
||||
if(obj && obj->isVisitable() && obj->ID != Obj::HERO)
|
||||
if(obj && obj->isVisitable() && obj->ID != Obj::HERO && obj->ID != Obj::EVENT)
|
||||
{
|
||||
addObjectActor(obj);
|
||||
}
|
||||
@ -47,7 +62,81 @@ public:
|
||||
{
|
||||
addObjectActor(town);
|
||||
}
|
||||
}
|
||||
|
||||
void calculateConnections()
|
||||
{
|
||||
updatePaths();
|
||||
|
||||
std::vector<AIPath> pathCache;
|
||||
|
||||
foreach_tile_pos(ai->cb.get(), [this, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
|
||||
{
|
||||
calculateConnections(pos, pathCache);
|
||||
});
|
||||
|
||||
removeExtraConnections();
|
||||
}
|
||||
|
||||
float getNeighborConnectionsCost(const int3 & pos, std::vector<AIPath> & pathCache)
|
||||
{
|
||||
float neighborCost = std::numeric_limits<float>::max();
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Checking junction %s", pos.toString());
|
||||
}
|
||||
|
||||
foreach_neighbour(
|
||||
ai->cb.get(),
|
||||
pos,
|
||||
[this, &neighborCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(pathCache, neighbor);
|
||||
|
||||
auto costTotal = this->getConnectionsCost(pathCache);
|
||||
|
||||
if(costTotal.connectionsCount > 2 && costTotal.avg < neighborCost)
|
||||
{
|
||||
neighborCost = costTotal.avg;
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Better node found at %s", neighbor.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return neighborCost;
|
||||
}
|
||||
|
||||
void addMinimalDistanceJunctions()
|
||||
{
|
||||
pforeachTilePaths(ai->cb->getMapSize(), ai, [this](const int3 & pos, std::vector<AIPath> & paths)
|
||||
{
|
||||
if(target->hasNodeAt(pos))
|
||||
return;
|
||||
|
||||
if(ai->cb->getGuardingCreaturePosition(pos).valid())
|
||||
return;
|
||||
|
||||
ConnectionCostInfo currentCost = getConnectionsCost(paths);
|
||||
|
||||
if(currentCost.connectionsCount <= 2)
|
||||
return;
|
||||
|
||||
float neighborCost = getNeighborConnectionsCost(pos, paths);
|
||||
|
||||
if(currentCost.avg < neighborCost)
|
||||
{
|
||||
addJunctionActor(pos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
void updatePaths()
|
||||
{
|
||||
PathfinderSettings ps;
|
||||
|
||||
ps.mainTurnDistanceLimit = 5;
|
||||
@ -57,41 +146,101 @@ public:
|
||||
ai->pathfinder->updatePaths(actors, ps);
|
||||
}
|
||||
|
||||
void calculateConnections(const int3 & pos)
|
||||
void calculateConnections(const int3 & pos, std::vector<AIPath> & pathCache)
|
||||
{
|
||||
auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid();
|
||||
|
||||
if(guarded)
|
||||
return;
|
||||
|
||||
auto paths = ai->pathfinder->getPathInfo(pos);
|
||||
|
||||
for(AIPath & path1 : paths)
|
||||
if(target->hasNodeAt(pos))
|
||||
{
|
||||
for(AIPath & path2 : paths)
|
||||
foreach_neighbour(
|
||||
ai->cb.get(),
|
||||
pos,
|
||||
[this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
|
||||
{
|
||||
if(target->hasNodeAt(neighbor))
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(pathCache, neighbor);
|
||||
|
||||
for(auto & path : pathCache)
|
||||
{
|
||||
if(pos == path.targetHero->visitablePos())
|
||||
{
|
||||
target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto obj = ai->cb->getTopObj(pos);
|
||||
|
||||
if((obj && obj->ID == Obj::BOAT) || target->isVirtualBoat(pos))
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(pathCache, pos);
|
||||
|
||||
for(AIPath & path : pathCache)
|
||||
{
|
||||
auto from = path.targetHero->visitablePos();
|
||||
auto fromObj = actorObjectMap[path.targetHero];
|
||||
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos, path.targetHero, true);
|
||||
auto updated = target->tryAddConnection(
|
||||
from,
|
||||
pos,
|
||||
path.movementCost(),
|
||||
danger);
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2 && updated)
|
||||
{
|
||||
logAi->trace(
|
||||
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
|
||||
fromObj ? fromObj->getObjectName() : "J", from.toString(),
|
||||
"Boat", pos.toString(),
|
||||
pos.toString(),
|
||||
path.movementCost());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto guardPos = ai->cb->getGuardingCreaturePosition(pos);
|
||||
|
||||
ai->pathfinder->calculatePathInfo(pathCache, pos);
|
||||
|
||||
for(AIPath & path1 : pathCache)
|
||||
{
|
||||
for(AIPath & path2 : pathCache)
|
||||
{
|
||||
if(path1.targetHero == path2.targetHero)
|
||||
continue;
|
||||
|
||||
auto pos1 = path1.targetHero->visitablePos();
|
||||
auto pos2 = path2.targetHero->visitablePos();
|
||||
|
||||
if(guardPos.valid() && guardPos != pos1 && guardPos != pos2)
|
||||
continue;
|
||||
|
||||
auto obj1 = actorObjectMap[path1.targetHero];
|
||||
auto obj2 = actorObjectMap[path2.targetHero];
|
||||
|
||||
auto tile1 = cb->getTile(obj1->visitablePos());
|
||||
auto tile2 = cb->getTile(obj2->visitablePos());
|
||||
auto tile1 = cb->getTile(pos1);
|
||||
auto tile2 = cb->getTile(pos2);
|
||||
|
||||
if(tile2->isWater() && !tile1->isWater())
|
||||
{
|
||||
auto linkTile = cb->getTile(pos);
|
||||
if(!cb->getTile(pos)->isWater())
|
||||
continue;
|
||||
|
||||
if(!linkTile->isWater() || obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD)
|
||||
auto startingObjIsBoat = (obj1 && obj1->ID == Obj::BOAT) || target->isVirtualBoat(pos1);
|
||||
|
||||
if(!startingObjIsBoat)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true);
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos2, path1.targetHero, true);
|
||||
|
||||
auto updated = target->tryAddConnection(
|
||||
obj1->visitablePos(),
|
||||
obj2->visitablePos(),
|
||||
pos1,
|
||||
pos2,
|
||||
path1.movementCost() + path2.movementCost(),
|
||||
danger);
|
||||
|
||||
@ -99,8 +248,8 @@ public:
|
||||
{
|
||||
logAi->trace(
|
||||
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
|
||||
obj1->getObjectName(), obj1->visitablePos().toString(),
|
||||
obj2->getObjectName(), obj2->visitablePos().toString(),
|
||||
obj1 ? obj1->getObjectName() : "J", pos1.toString(),
|
||||
obj2 ? obj2->getObjectName() : "J", pos2.toString(),
|
||||
pos.toString(),
|
||||
path1.movementCost() + path2.movementCost());
|
||||
}
|
||||
@ -108,7 +257,49 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool isExtraConnection(float direct, float side1, float side2) const
|
||||
{
|
||||
float sideRatio = (side1 + side2) / direct;
|
||||
|
||||
return sideRatio < 1.25f && direct > side1 && direct > side2;
|
||||
}
|
||||
|
||||
void removeExtraConnections()
|
||||
{
|
||||
std::vector<std::pair<int3, int3>> connectionsToRemove;
|
||||
|
||||
for(auto & actor : temporaryActorHeroes)
|
||||
{
|
||||
auto pos = actor->visitablePos();
|
||||
auto & currentNode = target->getNode(pos);
|
||||
|
||||
target->iterateConnections(pos, [this, &pos, &connectionsToRemove, ¤tNode](int3 n1, ObjectLink o1)
|
||||
{
|
||||
target->iterateConnections(n1, [&pos, &o1, ¤tNode, &connectionsToRemove, this](int3 n2, ObjectLink o2)
|
||||
{
|
||||
auto direct = currentNode.connections.find(n2);
|
||||
|
||||
if(direct != currentNode.connections.end() && isExtraConnection(direct->second.cost, o1.cost, o2.cost))
|
||||
{
|
||||
connectionsToRemove.push_back({pos, n2});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(connectionsToRemove);
|
||||
|
||||
for(auto & c : connectionsToRemove)
|
||||
{
|
||||
target->removeConnection(c.first, c.second);
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Remove ineffective connection %s->%s", c.first.toString(), c.second.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addObjectActor(const CGObjectInstance * obj)
|
||||
{
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
|
||||
@ -126,11 +317,88 @@ private:
|
||||
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
|
||||
}
|
||||
|
||||
assert(objectActor->visitablePos() == visitablePos);
|
||||
|
||||
actorObjectMap[objectActor] = obj;
|
||||
actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::BOAT ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
|
||||
target->addObject(obj);
|
||||
};
|
||||
|
||||
auto shipyard = dynamic_cast<const IShipyard *>(obj);
|
||||
|
||||
if(shipyard && shipyard->bestLocation().valid())
|
||||
{
|
||||
int3 virtualBoat = shipyard->bestLocation();
|
||||
|
||||
addJunctionActor(virtualBoat, true);
|
||||
target->addVirtualBoat(virtualBoat, obj);
|
||||
}
|
||||
}
|
||||
|
||||
void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(syncLock);
|
||||
|
||||
auto internalCb = temporaryActorHeroes.front()->cb;
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(internalCb)).get();
|
||||
|
||||
CRandomGenerator rng;
|
||||
|
||||
objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
|
||||
objectActor->initHero(rng, static_cast<HeroTypeID>(0));
|
||||
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
|
||||
objectActor->initObj(rng);
|
||||
|
||||
if(isVirtualBoat || ai->cb->getTile(visitablePos)->isWater())
|
||||
{
|
||||
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
|
||||
}
|
||||
|
||||
assert(objectActor->visitablePos() == visitablePos);
|
||||
|
||||
actorObjectMap[objectActor] = nullptr;
|
||||
actors[objectActor] = isVirtualBoat ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
|
||||
target->registerJunction(visitablePos);
|
||||
}
|
||||
|
||||
ConnectionCostInfo getConnectionsCost(std::vector<AIPath> & paths) const
|
||||
{
|
||||
std::map<int3, float> costs;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
auto fromPos = path.targetHero->visitablePos();
|
||||
auto cost = costs.find(fromPos);
|
||||
|
||||
if(cost == costs.end())
|
||||
{
|
||||
costs.emplace(fromPos, path.movementCost());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(path.movementCost() < cost->second)
|
||||
{
|
||||
costs[fromPos] = path.movementCost();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionCostInfo result;
|
||||
|
||||
for(auto & cost : costs)
|
||||
{
|
||||
result.totalCost += cost.second;
|
||||
result.connectionsCount++;
|
||||
}
|
||||
|
||||
if(result.connectionsCount)
|
||||
{
|
||||
result.avg = result.totalCost / result.connectionsCount;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
bool ObjectGraph::tryAddConnection(
|
||||
@ -139,7 +407,20 @@ bool ObjectGraph::tryAddConnection(
|
||||
float cost,
|
||||
uint64_t danger)
|
||||
{
|
||||
return nodes[from].connections[to].update(cost, danger);
|
||||
auto result = nodes[from].connections[to].update(cost, danger);
|
||||
auto & connection = nodes[from].connections[to];
|
||||
|
||||
if(result && isVirtualBoat(to) && !connection.specialAction)
|
||||
{
|
||||
connection.specialAction = std::make_shared<AIPathfinding::BuildBoatActionFactory>(virtualBoats[to]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ObjectGraph::removeConnection(const int3 & from, const int3 & to)
|
||||
{
|
||||
nodes[from].connections.erase(to);
|
||||
}
|
||||
|
||||
void ObjectGraph::updateGraph(const Nullkiller * ai)
|
||||
@ -148,29 +429,41 @@ void ObjectGraph::updateGraph(const Nullkiller * ai)
|
||||
|
||||
ObjectGraphCalculator calculator(this, ai);
|
||||
|
||||
foreach_tile_pos(cb.get(), [this, &calculator](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
|
||||
{
|
||||
if(nodes.find(pos) != nodes.end())
|
||||
return;
|
||||
|
||||
calculator.calculateConnections(pos);
|
||||
});
|
||||
calculator.setGraphObjects();
|
||||
calculator.calculateConnections();
|
||||
calculator.addMinimalDistanceJunctions();
|
||||
calculator.calculateConnections();
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 1)
|
||||
dumpToLog("graph");
|
||||
|
||||
}
|
||||
|
||||
void ObjectGraph::addObject(const CGObjectInstance * obj)
|
||||
{
|
||||
nodes[obj->visitablePos()].init(obj);
|
||||
if(!hasNodeAt(obj->visitablePos()))
|
||||
nodes[obj->visitablePos()].init(obj);
|
||||
}
|
||||
|
||||
void ObjectGraph::addVirtualBoat(const int3 & pos, const CGObjectInstance * shipyard)
|
||||
{
|
||||
if(!isVirtualBoat(pos))
|
||||
{
|
||||
virtualBoats[pos] = shipyard->id;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectGraph::registerJunction(const int3 & pos)
|
||||
{
|
||||
if(!hasNodeAt(pos))
|
||||
nodes[pos].initJunction();
|
||||
|
||||
}
|
||||
|
||||
void ObjectGraph::removeObject(const CGObjectInstance * obj)
|
||||
{
|
||||
nodes[obj->visitablePos()].objectExists = false;
|
||||
|
||||
if(obj->ID == Obj::BOAT)
|
||||
if(obj->ID == Obj::BOAT && !isVirtualBoat(obj->visitablePos()))
|
||||
{
|
||||
vstd::erase_if(nodes[obj->visitablePos()].connections, [&](const std::pair<int3, ObjectLink> & link) -> bool
|
||||
{
|
||||
@ -198,6 +491,9 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai)
|
||||
|
||||
for(AIPath & path : paths)
|
||||
{
|
||||
if(path.getFirstBlockedAction())
|
||||
continue;
|
||||
|
||||
auto heroPos = path.targetHero->visitablePos();
|
||||
|
||||
nodes[pos].connections[heroPos].update(
|
||||
@ -240,9 +536,35 @@ bool GraphNodeComparer::operator()(const GraphPathNodePointer & lhs, const Graph
|
||||
return pathNodes.at(lhs.coord)[lhs.nodeType].cost > pathNodes.at(rhs.coord)[rhs.nodeType].cost;
|
||||
}
|
||||
|
||||
void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai)
|
||||
GraphPaths::GraphPaths()
|
||||
: visualKey(""), graph(), pathNodes()
|
||||
{
|
||||
graph = *ai->baseGraph;
|
||||
}
|
||||
|
||||
std::shared_ptr<SpecialAction> getCompositeAction(
|
||||
const Nullkiller * ai,
|
||||
std::shared_ptr<ISpecialActionFactory> linkActionFactory,
|
||||
std::shared_ptr<SpecialAction> transitionAction)
|
||||
{
|
||||
if(!linkActionFactory)
|
||||
return transitionAction;
|
||||
|
||||
auto linkAction = linkActionFactory->create(ai);
|
||||
|
||||
if(!transitionAction)
|
||||
return linkAction;
|
||||
|
||||
std::vector<std::shared_ptr<const SpecialAction>> actionsArray = {
|
||||
transitionAction,
|
||||
linkAction
|
||||
};
|
||||
|
||||
return std::make_shared<CompositeAction>(actionsArray);
|
||||
}
|
||||
|
||||
void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai, uint8_t scanDepth)
|
||||
{
|
||||
graph.copyFrom(*ai->baseGraph);
|
||||
graph.connectHeroes(ai);
|
||||
|
||||
visualKey = std::to_string(ai->playerID) + ":" + targetHero->getNameTranslated();
|
||||
@ -259,20 +581,61 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
|
||||
GraphPathNodePointer pos = pq.top();
|
||||
pq.pop();
|
||||
|
||||
auto & node = getNode(pos);
|
||||
auto & node = getOrCreateNode(pos);
|
||||
std::shared_ptr<SpecialAction> transitionAction;
|
||||
|
||||
if(node.obj)
|
||||
{
|
||||
if(node.obj->ID == Obj::QUEST_GUARD
|
||||
|| node.obj->ID == Obj::BORDERGUARD
|
||||
|| node.obj->ID == Obj::BORDER_GATE)
|
||||
{
|
||||
auto questObj = dynamic_cast<const IQuestObject *>(node.obj);
|
||||
auto questInfo = QuestInfo(questObj->quest, node.obj, pos.coord);
|
||||
|
||||
if(node.obj->ID == Obj::QUEST_GUARD
|
||||
&& questObj->quest->mission == Rewardable::Limiter{}
|
||||
&& questObj->quest->killTarget == ObjectInstanceID::NONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto questAction = std::make_shared<AIPathfinding::QuestAction>(questInfo);
|
||||
|
||||
if(!questAction->canAct(ai, targetHero))
|
||||
{
|
||||
transitionAction = questAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.isInQueue = false;
|
||||
|
||||
graph.iterateConnections(pos.coord, [&](int3 target, ObjectLink o)
|
||||
graph.iterateConnections(pos.coord, [this, ai, &pos, &node, &transitionAction, &pq, scanDepth](int3 target, const ObjectLink & o)
|
||||
{
|
||||
auto targetNodeType = o.danger ? GrapthPathNodeType::BATTLE : pos.nodeType;
|
||||
auto compositeAction = getCompositeAction(ai, o.specialAction, transitionAction);
|
||||
auto targetNodeType = o.danger || compositeAction ? GrapthPathNodeType::BATTLE : pos.nodeType;
|
||||
auto targetPointer = GraphPathNodePointer(target, targetNodeType);
|
||||
auto & targetNode = getNode(targetPointer);
|
||||
auto & targetNode = getOrCreateNode(targetPointer);
|
||||
|
||||
if(targetNode.tryUpdate(pos, node, o))
|
||||
{
|
||||
if(graph.getNode(target).objTypeID == Obj::HERO)
|
||||
if(targetNode.cost > scanDepth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
targetNode.specialAction = compositeAction;
|
||||
|
||||
auto targetGraphNode = graph.getNode(target);
|
||||
|
||||
if(targetGraphNode.objID.hasValue())
|
||||
{
|
||||
targetNode.obj = ai->cb->getObj(targetGraphNode.objID, false);
|
||||
|
||||
if(targetNode.obj && targetNode.obj->ID == Obj::HERO)
|
||||
return;
|
||||
}
|
||||
|
||||
if(targetNode.isInQueue)
|
||||
{
|
||||
@ -321,11 +684,6 @@ bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathN
|
||||
|
||||
if(newCost < cost)
|
||||
{
|
||||
if(nodeType < pos.nodeType)
|
||||
{
|
||||
logAi->error("Linking error");
|
||||
}
|
||||
|
||||
previous = pos;
|
||||
danger = prev.danger + link.danger;
|
||||
cost = newCost;
|
||||
@ -348,7 +706,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
if(!node.reachable())
|
||||
continue;
|
||||
|
||||
std::vector<int3> tilesToPass;
|
||||
std::vector<GraphPathNodePointer> tilesToPass;
|
||||
|
||||
uint64_t danger = node.danger;
|
||||
float cost = node.cost;
|
||||
@ -372,7 +730,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
vstd::amax(danger, currentNode.danger);
|
||||
vstd::amax(cost, currentNode.cost);
|
||||
|
||||
tilesToPass.push_back(current.coord);
|
||||
tilesToPass.push_back(current);
|
||||
|
||||
if(currentNode.cost < 2.0f)
|
||||
break;
|
||||
@ -383,7 +741,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
if(tilesToPass.empty())
|
||||
continue;
|
||||
|
||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back());
|
||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord);
|
||||
|
||||
for(auto & path : entryPaths)
|
||||
{
|
||||
@ -394,12 +752,18 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
{
|
||||
AIPathNodeInfo n;
|
||||
|
||||
n.coord = *graphTile;
|
||||
n.coord = graphTile->coord;
|
||||
n.cost = cost;
|
||||
n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
|
||||
n.danger = danger;
|
||||
n.targetHero = hero;
|
||||
n.parentIndex = 0;
|
||||
n.parentIndex = -1;
|
||||
n.specialAction = getNode(*graphTile).specialAction;
|
||||
|
||||
if(n.specialAction)
|
||||
{
|
||||
n.actionIsBlocked = !n.specialAction->canAct(ai, n);
|
||||
}
|
||||
|
||||
for(auto & node : path.nodes)
|
||||
{
|
||||
@ -418,4 +782,121 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
}
|
||||
}
|
||||
|
||||
void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const
|
||||
{
|
||||
auto nodes = pathNodes.find(tile);
|
||||
|
||||
if(nodes == pathNodes.end())
|
||||
return;
|
||||
|
||||
for(auto & targetNode : nodes->second)
|
||||
{
|
||||
if(!targetNode.reachable())
|
||||
continue;
|
||||
|
||||
std::vector<GraphPathNodePointer> tilesToPass;
|
||||
|
||||
uint64_t danger = targetNode.danger;
|
||||
float cost = targetNode.cost;
|
||||
bool allowBattle = false;
|
||||
|
||||
auto current = GraphPathNodePointer(nodes->first, targetNode.nodeType);
|
||||
|
||||
while(true)
|
||||
{
|
||||
auto currentTile = pathNodes.find(current.coord);
|
||||
|
||||
if(currentTile == pathNodes.end())
|
||||
break;
|
||||
|
||||
auto currentNode = currentTile->second[current.nodeType];
|
||||
|
||||
allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
|
||||
vstd::amax(danger, currentNode.danger);
|
||||
vstd::amax(cost, currentNode.cost);
|
||||
|
||||
tilesToPass.push_back(current);
|
||||
|
||||
if(currentNode.cost < 2.0f)
|
||||
break;
|
||||
|
||||
current = currentNode.previous;
|
||||
}
|
||||
|
||||
if(tilesToPass.empty())
|
||||
continue;
|
||||
|
||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord);
|
||||
|
||||
for(auto & entryPath : entryPaths)
|
||||
{
|
||||
if(entryPath.targetHero != hero)
|
||||
continue;
|
||||
|
||||
auto & path = paths.emplace_back();
|
||||
|
||||
path.targetHero = entryPath.targetHero;
|
||||
path.heroArmy = entryPath.heroArmy;
|
||||
path.exchangeCount = entryPath.exchangeCount;
|
||||
path.armyLoss = entryPath.armyLoss + ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
|
||||
path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
|
||||
path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
|
||||
|
||||
AIPathNodeInfo n;
|
||||
|
||||
n.targetHero = hero;
|
||||
n.parentIndex = -1;
|
||||
|
||||
// final node
|
||||
n.coord = tile;
|
||||
n.cost = targetNode.cost;
|
||||
n.danger = targetNode.danger;
|
||||
n.parentIndex = path.nodes.size();
|
||||
path.nodes.push_back(n);
|
||||
|
||||
for(auto entryNode = entryPath.nodes.rbegin(); entryNode != entryPath.nodes.rend(); entryNode++)
|
||||
{
|
||||
auto blocker = ai->objectClusterizer->getBlocker(*entryNode);
|
||||
|
||||
if(blocker)
|
||||
{
|
||||
// blocker node
|
||||
path.nodes.push_back(*entryNode);
|
||||
path.nodes.back().parentIndex = path.nodes.size() - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(path.nodes.size() > 1)
|
||||
continue;
|
||||
|
||||
for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
|
||||
{
|
||||
auto & node = getNode(*graphTile);
|
||||
|
||||
n.coord = graphTile->coord;
|
||||
n.cost = node.cost;
|
||||
n.turns = static_cast<ui8>(node.cost);
|
||||
n.danger = node.danger;
|
||||
n.specialAction = node.specialAction;
|
||||
n.parentIndex = path.nodes.size();
|
||||
|
||||
if(n.specialAction)
|
||||
{
|
||||
n.actionIsBlocked = !n.specialAction->canAct(ai, n);
|
||||
}
|
||||
|
||||
auto blocker = ai->objectClusterizer->getBlocker(n);
|
||||
|
||||
if(!blocker)
|
||||
continue;
|
||||
|
||||
// blocker node
|
||||
path.nodes.push_back(n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ struct ObjectLink
|
||||
{
|
||||
float cost = 100000; // some big number
|
||||
uint64_t danger = 0;
|
||||
std::shared_ptr<ISpecialActionFactory> specialAction;
|
||||
|
||||
bool update(float newCost, uint64_t newDanger)
|
||||
{
|
||||
@ -50,19 +51,47 @@ struct ObjectNode
|
||||
objID = obj->id;
|
||||
objTypeID = obj->ID;
|
||||
}
|
||||
|
||||
void initJunction()
|
||||
{
|
||||
objectExists = false;
|
||||
objID = ObjectInstanceID();
|
||||
objTypeID = Obj();
|
||||
}
|
||||
};
|
||||
|
||||
class ObjectGraph
|
||||
{
|
||||
std::unordered_map<int3, ObjectNode> nodes;
|
||||
std::unordered_map<int3, ObjectInstanceID> virtualBoats;
|
||||
|
||||
public:
|
||||
ObjectGraph()
|
||||
:nodes(), virtualBoats()
|
||||
{
|
||||
}
|
||||
|
||||
void updateGraph(const Nullkiller * ai);
|
||||
void addObject(const CGObjectInstance * obj);
|
||||
void registerJunction(const int3 & pos);
|
||||
void addVirtualBoat(const int3 & pos, const CGObjectInstance * shipyard);
|
||||
void connectHeroes(const Nullkiller * ai);
|
||||
void removeObject(const CGObjectInstance * obj);
|
||||
bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger);
|
||||
void removeConnection(const int3 & from, const int3 & to);
|
||||
void dumpToLog(std::string visualKey) const;
|
||||
|
||||
bool isVirtualBoat(const int3 & tile) const
|
||||
{
|
||||
return vstd::contains(virtualBoats, tile);
|
||||
}
|
||||
|
||||
void copyFrom(const ObjectGraph & other)
|
||||
{
|
||||
nodes = other.nodes;
|
||||
virtualBoats = other.virtualBoats;
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void iterateConnections(const int3 & pos, Func fn)
|
||||
{
|
||||
@ -77,7 +106,10 @@ public:
|
||||
return nodes.at(tile);
|
||||
}
|
||||
|
||||
bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger);
|
||||
bool hasNodeAt(const int3 & tile) const
|
||||
{
|
||||
return vstd::contains(nodes, tile);
|
||||
}
|
||||
};
|
||||
|
||||
struct GraphPathNode;
|
||||
@ -131,6 +163,8 @@ struct GraphPathNode
|
||||
GraphPathNodePointer previous;
|
||||
float cost = BAD_COST;
|
||||
uint64_t danger = 0;
|
||||
const CGObjectInstance * obj = nullptr;
|
||||
std::shared_ptr<SpecialAction> specialAction;
|
||||
|
||||
using TFibHeap = boost::heap::fibonacci_heap<GraphPathNodePointer, boost::heap::compare<GraphNodeComparer>>;
|
||||
|
||||
@ -152,12 +186,14 @@ class GraphPaths
|
||||
std::string visualKey;
|
||||
|
||||
public:
|
||||
void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai);
|
||||
GraphPaths();
|
||||
void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai, uint8_t scanDepth);
|
||||
void addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const;
|
||||
void quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const;
|
||||
void dumpToLog() const;
|
||||
|
||||
private:
|
||||
GraphPathNode & getNode(const GraphPathNodePointer & pos)
|
||||
GraphPathNode & getOrCreateNode(const GraphPathNodePointer & pos)
|
||||
{
|
||||
auto & node = pathNodes[pos.coord][pos.nodeType];
|
||||
|
||||
@ -165,6 +201,13 @@ private:
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
const GraphPathNode & getNode(const GraphPathNodePointer & pos) const
|
||||
{
|
||||
auto & node = pathNodes.at(pos.coord)[pos.nodeType];
|
||||
|
||||
return node;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,10 @@ namespace NKAI
|
||||
{
|
||||
namespace AIPathfinding
|
||||
{
|
||||
AILayerTransitionRule::AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
AILayerTransitionRule::AILayerTransitionRule(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:cb(cb), ai(ai), nodeStorage(nodeStorage)
|
||||
{
|
||||
setup();
|
||||
@ -174,7 +177,7 @@ namespace AIPathfinding
|
||||
const CGHeroInstance * hero = nodeStorage->getHero(source.node);
|
||||
|
||||
if(vstd::contains(summonableVirtualBoats, hero)
|
||||
&& summonableVirtualBoats.at(hero)->canAct(nodeStorage->getAINode(source.node)))
|
||||
&& summonableVirtualBoats.at(hero)->canAct(ai, nodeStorage->getAINode(source.node)))
|
||||
{
|
||||
virtualBoat = summonableVirtualBoats.at(hero);
|
||||
}
|
||||
|
@ -34,7 +34,10 @@ namespace AIPathfinding
|
||||
std::map<const CGHeroInstance *, std::shared_ptr<const AirWalkingAction>> airWalkingActions;
|
||||
|
||||
public:
|
||||
AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
AILayerTransitionRule(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
|
||||
virtual void process(
|
||||
const PathNodeInfo & source,
|
||||
|
@ -20,10 +20,11 @@ namespace NKAI
|
||||
namespace AIPathfinding
|
||||
{
|
||||
AIMovementAfterDestinationRule::AIMovementAfterDestinationRule(
|
||||
const Nullkiller * ai,
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
:cb(cb), nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
|
||||
:ai(ai), cb(cb), nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
|
||||
{
|
||||
}
|
||||
|
||||
@ -40,17 +41,46 @@ namespace AIPathfinding
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!allowBypassObjects
|
||||
&& destination.action == EPathNodeAction::EMBARK
|
||||
&& source.node->layer == EPathfindingLayer::LAND
|
||||
&& destination.node->layer == EPathfindingLayer::SAIL)
|
||||
{
|
||||
destination.blocked = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
|
||||
if(blocker == BlockingReason::NONE)
|
||||
{
|
||||
destination.blocked = nodeStorage->isDistanceLimitReached(source, destination);
|
||||
|
||||
if(destination.nodeObject
|
||||
&& !destination.blocked
|
||||
&& !allowBypassObjects
|
||||
&& !dynamic_cast<const CGTeleport *>(destination.nodeObject)
|
||||
&& destination.nodeObject->ID != Obj::EVENT)
|
||||
{
|
||||
destination.blocked = true;
|
||||
destination.node->locked = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!allowBypassObjects)
|
||||
{
|
||||
if(destination.nodeObject)
|
||||
{
|
||||
destination.blocked = true;
|
||||
destination.node->locked = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
@ -142,7 +172,7 @@ namespace AIPathfinding
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!questAction.canAct(destinationNode))
|
||||
if(!questAction.canAct(ai, destinationNode))
|
||||
{
|
||||
if(!destinationNode->actor->allowUseResources)
|
||||
{
|
||||
|
@ -24,11 +24,13 @@ namespace AIPathfinding
|
||||
{
|
||||
private:
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
const Nullkiller * ai;
|
||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||
bool allowBypassObjects;
|
||||
|
||||
public:
|
||||
AIMovementAfterDestinationRule(
|
||||
const Nullkiller * ai,
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects);
|
||||
|
@ -49,7 +49,7 @@ namespace AIPathfinding
|
||||
return;
|
||||
|
||||
// when actor represents moster graph node, we need to let him escape monster
|
||||
if(!destination.guarded && cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition)
|
||||
if(cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition)
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1 +1,10 @@
|
||||
/*
|
||||
* StdInc.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"
|
||||
|
@ -1,3 +1,12 @@
|
||||
/*
|
||||
* StdInc.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 "../../Global.h"
|
||||
VCMI_LIB_USING_NAMESPACE
|
||||
|
50
AI/Nullkiller/pforeach.h
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Nullkiller.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
template<typename TFunc>
|
||||
void pforeachTilePos(const int3 & mapSize, TFunc fn)
|
||||
{
|
||||
for(int z = 0; z < mapSize.z; ++z)
|
||||
{
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, mapSize.x), [&](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
int3 pos(0, 0, z);
|
||||
|
||||
for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
|
||||
{
|
||||
for(pos.y = 0; pos.y < mapSize.y; ++pos.y)
|
||||
{
|
||||
fn(pos);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TFunc>
|
||||
void pforeachTilePaths(const int3 & mapSize, const Nullkiller * ai, TFunc fn)
|
||||
{
|
||||
for(int z = 0; z < mapSize.z; ++z)
|
||||
{
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, mapSize.x), [&](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
int3 pos(0, 0, z);
|
||||
std::vector<AIPath> paths;
|
||||
|
||||
for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
|
||||
{
|
||||
for(pos.y = 0; pos.y < mapSize.y; ++pos.y)
|
||||
{
|
||||
ai->pathfinder->calculatePathInfo(paths, pos);
|
||||
fn(pos, paths);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,2 +1,11 @@
|
||||
/*
|
||||
* StdInc.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
|
||||
*
|
||||
*/
|
||||
// Creates the precompiled header
|
||||
#include "StdInc.h"
|
||||
|
@ -1,3 +1,12 @@
|
||||
/*
|
||||
* StdInc.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 "../../Global.h"
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* AIhelper.h, part of VCMI engine
|
||||
* AIhelper.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
|
@ -1,3 +1,12 @@
|
||||
/*
|
||||
* MapObjectsEvaluator.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 "MapObjectsEvaluator.h"
|
||||
#include "../../lib/GameConstants.h"
|
||||
|
@ -155,15 +155,21 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
void AINodeStorage::calculateNeighbours(
|
||||
std::vector<CGPathNode *> & result,
|
||||
const PathNodeInfo & source,
|
||||
EPathfindingLayer layer,
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
const CPathfinderHelper * pathfinderHelper)
|
||||
{
|
||||
std::vector<CGPathNode *> neighbours;
|
||||
neighbours.reserve(16);
|
||||
std::vector<int3> accessibleNeighbourTiles;
|
||||
|
||||
result.clear();
|
||||
accessibleNeighbourTiles.reserve(8);
|
||||
|
||||
pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
|
||||
|
||||
const AIPathNode * srcNode = getAINode(source.node);
|
||||
auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
|
||||
|
||||
for(auto & neighbour : accessibleNeighbourTiles)
|
||||
{
|
||||
@ -174,11 +180,9 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
|
||||
continue;
|
||||
|
||||
neighbours.push_back(nextNode.value());
|
||||
result.push_back(nextNode.value());
|
||||
}
|
||||
}
|
||||
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
void AINodeStorage::setHero(HeroPtr heroPtr, const VCAI * _ai)
|
||||
|
@ -89,8 +89,10 @@ public:
|
||||
|
||||
std::vector<CGPathNode *> getInitialNodes() override;
|
||||
|
||||
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||
virtual void calculateNeighbours(
|
||||
std::vector<CGPathNode *> & result,
|
||||
const PathNodeInfo & source,
|
||||
EPathfindingLayer layer,
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
const CPathfinderHelper * pathfinderHelper) override;
|
||||
|
||||
|
@ -1 +1,10 @@
|
||||
/*
|
||||
* StdInc.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"
|
||||
|
@ -1,3 +1,12 @@
|
||||
/*
|
||||
* StdInc.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 "../../Global.h"
|
||||
|
||||
|
@ -180,6 +180,14 @@ void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dst
|
||||
sendRequest(&bma);
|
||||
}
|
||||
|
||||
void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left)
|
||||
{
|
||||
ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SCROLL_RIGHT);
|
||||
if(left)
|
||||
mba.cmd = ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT;
|
||||
sendRequest(&mba);
|
||||
}
|
||||
|
||||
void CCallback::eraseArtifactByClient(const ArtifactLocation & al)
|
||||
{
|
||||
EraseArtifactByClient ea(al);
|
||||
|
@ -90,6 +90,7 @@ public:
|
||||
virtual int splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val)=0;//split creatures from the first stack
|
||||
//virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes
|
||||
virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
|
||||
virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0;
|
||||
virtual void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
|
||||
virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
|
||||
virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
|
||||
@ -174,6 +175,7 @@ public:
|
||||
bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override;
|
||||
void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
|
||||
void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped = true, bool backpack = true) override;
|
||||
void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override;
|
||||
void eraseArtifactByClient(const ArtifactLocation & al) override;
|
||||
bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;
|
||||
void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override;
|
||||
|
@ -3,7 +3,7 @@
|
||||
sudo apt-get update
|
||||
|
||||
# Dependencies
|
||||
sudo apt-get install libboost-all-dev \
|
||||
sudo apt-get install libboost-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-locale-dev \
|
||||
libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
|
||||
qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools \
|
||||
ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \
|
||||
|
@ -3,7 +3,7 @@
|
||||
sudo apt-get update
|
||||
|
||||
# Dependencies
|
||||
sudo apt-get install libboost-all-dev \
|
||||
sudo apt-get install libboost-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-locale-dev \
|
||||
libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
|
||||
qtbase5-dev \
|
||||
ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \
|
||||
|
@ -5,11 +5,6 @@ cmake_minimum_required(VERSION 3.16.0)
|
||||
|
||||
project(VCMI)
|
||||
# TODO
|
||||
# macOS:
|
||||
# - There is problem with running fixup_bundle in main project after subdirectories.
|
||||
# Cmake put them after all install code of main CMakelists in cmake_install.cmake
|
||||
# Currently I just added extra add_subdirectory and CMakeLists.txt in osx directory to bypass that.
|
||||
#
|
||||
# Vckpg:
|
||||
# - Improve install code once there is better way to deploy DLLs and Qt plugins
|
||||
#
|
||||
@ -23,7 +18,7 @@ project(VCMI)
|
||||
# - Make FindFuzzyLite check for the right version and disable FORCE_BUNDLED_FL by default
|
||||
|
||||
if(APPLE)
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(APPLE_MACOS 1)
|
||||
else()
|
||||
set(APPLE_IOS 1)
|
||||
@ -43,11 +38,13 @@ endif()
|
||||
|
||||
# Platform-independent options
|
||||
|
||||
option(ENABLE_CLIENT "Enable compilation of game client" ON)
|
||||
option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
|
||||
option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF)
|
||||
option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON)
|
||||
option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON)
|
||||
option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON)
|
||||
option(ENABLE_MINIMAL_LIB "Build only core parts of vcmi library that are required for game lobby" OFF)
|
||||
|
||||
# Compilation options
|
||||
|
||||
@ -252,6 +249,10 @@ if(ENABLE_SINGLE_APP_BUILD)
|
||||
add_definitions(-DENABLE_SINGLE_APP_BUILD)
|
||||
endif()
|
||||
|
||||
if(ENABLE_MINIMAL_LIB)
|
||||
add_definitions(-DENABLE_MINIMAL_LIB)
|
||||
endif()
|
||||
|
||||
if(APPLE_IOS)
|
||||
set(CMAKE_MACOSX_RPATH 1)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 12.0)
|
||||
@ -455,12 +456,6 @@ if(TARGET zlib::zlib)
|
||||
add_library(ZLIB::ZLIB ALIAS zlib::zlib)
|
||||
endif()
|
||||
|
||||
set(FFMPEG_COMPONENTS avutil swscale avformat avcodec)
|
||||
if(APPLE_IOS AND NOT USING_CONAN)
|
||||
list(APPEND FFMPEG_COMPONENTS swresample)
|
||||
endif()
|
||||
find_package(ffmpeg COMPONENTS ${FFMPEG_COMPONENTS})
|
||||
|
||||
option(FORCE_BUNDLED_MINIZIP "Force bundled Minizip library" OFF)
|
||||
if(NOT FORCE_BUNDLED_MINIZIP)
|
||||
find_package(minizip)
|
||||
@ -469,18 +464,26 @@ if(NOT FORCE_BUNDLED_MINIZIP)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(SDL2_image REQUIRED)
|
||||
if(TARGET SDL2_image::SDL2_image)
|
||||
add_library(SDL2::Image ALIAS SDL2_image::SDL2_image)
|
||||
endif()
|
||||
find_package(SDL2_mixer REQUIRED)
|
||||
if(TARGET SDL2_mixer::SDL2_mixer)
|
||||
add_library(SDL2::Mixer ALIAS SDL2_mixer::SDL2_mixer)
|
||||
endif()
|
||||
find_package(SDL2_ttf REQUIRED)
|
||||
if(TARGET SDL2_ttf::SDL2_ttf)
|
||||
add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf)
|
||||
if (ENABLE_CLIENT)
|
||||
set(FFMPEG_COMPONENTS avutil swscale avformat avcodec)
|
||||
if(APPLE_IOS AND NOT USING_CONAN)
|
||||
list(APPEND FFMPEG_COMPONENTS swresample)
|
||||
endif()
|
||||
find_package(ffmpeg COMPONENTS ${FFMPEG_COMPONENTS})
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(SDL2_image REQUIRED)
|
||||
if(TARGET SDL2_image::SDL2_image)
|
||||
add_library(SDL2::Image ALIAS SDL2_image::SDL2_image)
|
||||
endif()
|
||||
find_package(SDL2_mixer REQUIRED)
|
||||
if(TARGET SDL2_mixer::SDL2_mixer)
|
||||
add_library(SDL2::Mixer ALIAS SDL2_mixer::SDL2_mixer)
|
||||
endif()
|
||||
find_package(SDL2_ttf REQUIRED)
|
||||
if(TARGET SDL2_ttf::SDL2_ttf)
|
||||
add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_LOBBY)
|
||||
@ -498,7 +501,7 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_NULLKILLER_AI)
|
||||
if(ENABLE_NULLKILLER_AI AND ENABLE_CLIENT)
|
||||
find_package(TBB REQUIRED)
|
||||
endif()
|
||||
|
||||
@ -608,10 +611,15 @@ if(APPLE_IOS)
|
||||
add_subdirectory(ios)
|
||||
endif()
|
||||
|
||||
add_subdirectory_with_folder("AI" AI)
|
||||
if (ENABLE_CLIENT)
|
||||
add_subdirectory_with_folder("AI" AI)
|
||||
endif()
|
||||
|
||||
add_subdirectory(lib)
|
||||
add_subdirectory(server)
|
||||
|
||||
if (ENABLE_CLIENT OR ENABLE_SERVER)
|
||||
add_subdirectory(server)
|
||||
endif()
|
||||
|
||||
if(ENABLE_ERM)
|
||||
add_subdirectory(scripting/erm)
|
||||
@ -638,7 +646,9 @@ if(ENABLE_LOBBY)
|
||||
add_subdirectory(lobby)
|
||||
endif()
|
||||
|
||||
add_subdirectory(client)
|
||||
if (ENABLE_CLIENT)
|
||||
add_subdirectory(client)
|
||||
endif()
|
||||
|
||||
if(ENABLE_SERVER)
|
||||
add_subdirectory(serverapp)
|
||||
@ -682,7 +692,9 @@ if(ANDROID)
|
||||
")
|
||||
else()
|
||||
install(DIRECTORY config DESTINATION ${DATA_DIR})
|
||||
install(DIRECTORY Mods DESTINATION ${DATA_DIR})
|
||||
if (ENABLE_CLIENT OR ENABLE_SERVER)
|
||||
install(DIRECTORY Mods DESTINATION ${DATA_DIR})
|
||||
endif()
|
||||
endif()
|
||||
if(ENABLE_LUA)
|
||||
install(DIRECTORY scripts DESTINATION ${DATA_DIR})
|
||||
|
4
Global.h
@ -215,10 +215,10 @@ using TLockGuardRec = std::lock_guard<std::recursive_mutex>;
|
||||
/* ---------------------------------------------------------------------------- */
|
||||
// Import + Export macro declarations
|
||||
#ifdef VCMI_WINDOWS
|
||||
#ifdef VCMI_DLL_STATIC
|
||||
# ifdef VCMI_DLL_STATIC
|
||||
# define DLL_IMPORT
|
||||
# define DLL_EXPORT
|
||||
#elif defined(__GNUC__)
|
||||
# elif defined(__GNUC__)
|
||||
# define DLL_IMPORT __attribute__((dllimport))
|
||||
# define DLL_EXPORT __attribute__((dllexport))
|
||||
# else
|
||||
|
BIN
Mods/vcmi/Data/lobby/iconEnter.png
Normal file
After Width: | Height: | Size: 145 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 742 B |
BIN
Mods/vcmi/Data/lobby/iconPlayer.png
Normal file
After Width: | Height: | Size: 204 B |
BIN
Mods/vcmi/Data/lobby/iconSend.png
Normal file
After Width: | Height: | Size: 185 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 994 B |
Before Width: | Height: | Size: 592 B After Width: | Height: | Size: 121 B |